feat: SkyClient Part 1
This commit is contained in:
parent
2554d233f4
commit
f49dc71e9d
15 changed files with 410 additions and 85 deletions
BIN
apps/frontend/src/assets/logos/skyclient.png
Normal file
BIN
apps/frontend/src/assets/logos/skyclient.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 751 B |
|
|
@ -1,6 +1,7 @@
|
|||
import type { ManagedPackage, Providers } from '@onelauncher/client/bindings';
|
||||
import CurseforgeIcon from '~assets/logos/curseforge.svg?component-solid';
|
||||
import ModrinthImage from '~assets/logos/modrinth.svg?component-solid';
|
||||
import SkyClientImage from '~assets/logos/skyclient.png';
|
||||
import { type Component, type JSX, Match, Show, splitProps, Switch } from 'solid-js';
|
||||
|
||||
export function getProviderLogoElement(provider: ManagedPackage | Providers): string | Component {
|
||||
|
|
@ -10,6 +11,7 @@ export function getProviderLogoElement(provider: ManagedPackage | Providers): st
|
|||
const mapping: Record<Lowercase<Providers>, string | Component> = {
|
||||
modrinth: ModrinthImage,
|
||||
curseforge: CurseforgeIcon,
|
||||
skyclient: SkyClientImage,
|
||||
};
|
||||
|
||||
return mapping[providerName];
|
||||
|
|
|
|||
|
|
@ -160,16 +160,18 @@ function PackageItem(props: SearchResult & { provider: Providers; row?: boolean
|
|||
<p class="max-h-22 flex-1 overflow-hidden text-sm text-fg-secondary line-height-snug">{props.description}</p>
|
||||
|
||||
<div class="flex flex-row gap-4 text-xs">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Download01Icon class="h-4 w-4" />
|
||||
{abbreviateNumber(props.downloads)}
|
||||
</div>
|
||||
|
||||
<Show when={props.follows > 0}>
|
||||
<Show when={props.provider !== 'SkyClient'}>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<HeartIcon class="h-4 w-4" />
|
||||
{abbreviateNumber(props.follows)}
|
||||
<Download01Icon class="h-4 w-4" />
|
||||
{abbreviateNumber(props.downloads)}
|
||||
</div>
|
||||
|
||||
<Show when={props.follows > 0}>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<HeartIcon class="h-4 w-4" />
|
||||
{abbreviateNumber(props.follows)}
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -137,8 +137,8 @@ export default BrowserPackage;
|
|||
|
||||
function BrowserSidebar(props: { package: ManagedPackage }) {
|
||||
const [authors] = useCommand(() => props.package, () => bridge.commands.getProviderAuthors(props.package.provider, props.package.author));
|
||||
const createdAt = () => new Date(props.package.created);
|
||||
const updatedAt = () => new Date(props.package.updated);
|
||||
const createdAt = createMemo(() => props.package.created ? new Date(props.package.created) : null);
|
||||
const updatedAt = createMemo(() => props.package.updated ? new Date(props.package.updated) : null);
|
||||
const promptOpen = usePromptOpener();
|
||||
|
||||
return (
|
||||
|
|
@ -166,16 +166,18 @@ function BrowserSidebar(props: { package: ManagedPackage }) {
|
|||
<p class="max-h-22 flex-1 overflow-hidden text-sm text-fg-secondary line-height-snug">{props.package.description}</p>
|
||||
|
||||
<div class="flex flex-row gap-4 text-xs">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<Download01Icon class="h-4 w-4" />
|
||||
{abbreviateNumber(props.package.downloads)}
|
||||
</div>
|
||||
|
||||
<Show when={props.package.followers > 0}>
|
||||
<Show when={props.package.provider !== 'SkyClient'}>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<HeartIcon class="h-4 w-4" />
|
||||
{abbreviateNumber(props.package.followers)}
|
||||
<Download01Icon class="h-4 w-4" />
|
||||
{abbreviateNumber(props.package.downloads)}
|
||||
</div>
|
||||
|
||||
<Show when={props.package.followers > 0}>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<HeartIcon class="h-4 w-4" />
|
||||
{abbreviateNumber(props.package.followers)}
|
||||
</div>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -235,25 +237,29 @@ function BrowserSidebar(props: { package: ManagedPackage }) {
|
|||
</div>
|
||||
</Show>
|
||||
|
||||
<Tooltip text={createdAt().toLocaleString()}>
|
||||
<div class="flex flex-row items-center gap-x-1">
|
||||
<CalendarIcon class="h-3 min-w-3 w-3" />
|
||||
Created
|
||||
<span class="text-fg-primary font-medium">
|
||||
{formatAsRelative(createdAt().getTime(), 'en', 'long')}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Show when={createdAt() !== null}>
|
||||
<Tooltip text={createdAt()!.toLocaleString()}>
|
||||
<div class="flex flex-row items-center gap-x-1">
|
||||
<CalendarIcon class="h-3 min-w-3 w-3" />
|
||||
Created
|
||||
<span class="text-fg-primary font-medium">
|
||||
{formatAsRelative(createdAt()!.getTime(), 'en', 'long')}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
|
||||
<Tooltip text={updatedAt().toLocaleString()}>
|
||||
<div class="flex flex-row items-center gap-x-1">
|
||||
<ClockRewindIcon class="h-3 min-w-3 w-3" />
|
||||
Last Updated
|
||||
<span class="text-fg-primary font-medium">
|
||||
{formatAsRelative(updatedAt().getTime(), 'en', 'long')}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Show when={updatedAt() !== null}>
|
||||
<Tooltip text={updatedAt()!.toLocaleString()}>
|
||||
<div class="flex flex-row items-center gap-x-1">
|
||||
<ClockRewindIcon class="h-3 min-w-3 w-3" />
|
||||
Last Updated
|
||||
<span class="text-fg-primary font-medium">
|
||||
{formatAsRelative(updatedAt()!.getTime(), 'en', 'long')}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import ProviderIcon from '~ui/components/content/ProviderIcon';
|
|||
import useBrowser from '~ui/hooks/useBrowser';
|
||||
import { PROVIDERS } from '~utils';
|
||||
import { browserCategories } from '~utils/browser';
|
||||
import { For, type JSX, type ParentProps } from 'solid-js';
|
||||
import { createMemo, For, type JSX, type ParentProps, Show } from 'solid-js';
|
||||
import BrowserMain from './BrowserMain';
|
||||
import BrowserPackage from './BrowserPackage';
|
||||
import BrowserSearch from './BrowserSearch';
|
||||
|
|
@ -87,23 +87,29 @@ function BrowserCategories() {
|
|||
browser.search();
|
||||
};
|
||||
|
||||
const categories = createMemo(() => {
|
||||
return browserCategories.byPackageType(browser.packageType(), browser.searchQuery().provider);
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="top-0 grid grid-cols-[1fr_auto] h-fit min-w-50 gap-y-6">
|
||||
<div />
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<h6 class="my-1">Categories</h6>
|
||||
<For each={browserCategories.byPackageType(browser.packageType(), browser.searchQuery().provider)}>
|
||||
{category => (
|
||||
<p
|
||||
class={`text-md capitalize text-fg-primary hover:text-fg-primary-hover line-height-snug ${isEnabled(category.id) ? 'text-opacity-100! hover:text-opacity-90!' : 'text-opacity-60! hover:text-opacity-70!'}`}
|
||||
onClick={() => toggleCategory(category.id)}
|
||||
>
|
||||
{category.display}
|
||||
</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Show when={categories().length > 0}>
|
||||
<div class="flex flex-col gap-y-2">
|
||||
<h6 class="my-1">Categories</h6>
|
||||
<For each={categories()}>
|
||||
{category => (
|
||||
<p
|
||||
class={`text-md capitalize text-fg-primary hover:text-fg-primary-hover line-height-snug ${isEnabled(category.id) ? 'text-opacity-100! hover:text-opacity-90!' : 'text-opacity-60! hover:text-opacity-70!'}`}
|
||||
onClick={() => toggleCategory(category.id)}
|
||||
>
|
||||
{category.display}
|
||||
</p>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -93,4 +93,5 @@ const modMapping: Record<Providers, CategoryItem[]> = {
|
|||
{ display: 'Utility', id: 'utility' },
|
||||
{ display: 'World Generation', id: 'worldgen' },
|
||||
],
|
||||
SkyClient: [],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -174,13 +174,14 @@ export function getPackageUrl(pkg: ManagedPackage): string {
|
|||
|
||||
return `https://www.curseforge.com/minecraft/${packageTypeMapping[pkg.package_type]}/${pkg.main}`;
|
||||
},
|
||||
SkyClient: () => 'TODO',
|
||||
};
|
||||
|
||||
return mapping[pkg.provider]();
|
||||
}
|
||||
|
||||
export const LOADERS: Loader[] = ['vanilla', 'fabric', 'forge', 'neoforge', 'quilt'] as const;
|
||||
export const PROVIDERS: Providers[] = ['Modrinth', 'Curseforge'] as const;
|
||||
export const PROVIDERS: Providers[] = ['Modrinth', 'Curseforge', 'SkyClient'] as const;
|
||||
export const PACKAGE_TYPES: PackageType[] = ['mod', 'resourcepack', 'datapack', 'shaderpack'] as const;
|
||||
export const LAUNCHER_IMPORT_TYPES: ImportType[] = [
|
||||
'PrismLauncher',
|
||||
|
|
|
|||
6
packages/client/src/bindings.ts
generated
6
packages/client/src/bindings.ts
generated
|
|
@ -857,7 +857,7 @@ export interface ManagedDependency { version_id: string | null; package_id: stri
|
|||
/**
|
||||
* Universal metadata for any managed package from a Mod distribution platform.
|
||||
*/
|
||||
export interface ManagedPackage { provider: Providers; id: string; package_type: PackageType; title: string; description: string; body: PackageBody; main: string; versions: string[]; game_versions: string[]; loaders: Loader[]; icon_url: string | null; created: string; updated: string; client: PackageSide; server: PackageSide; downloads: bigint; followers: number; categories: string[]; optional_categories: string[] | null; license: License | null; author: Author; is_archived: boolean };
|
||||
export interface ManagedPackage { provider: Providers; id: string; package_type: PackageType; title: string; description: string; body: PackageBody; main: string; versions: string[]; game_versions: string[]; loaders: Loader[]; icon_url: string | null; created: string | null; updated: string | null; client: PackageSide; server: PackageSide; downloads: bigint; followers: number; categories: string[]; optional_categories: string[] | null; license: License | null; author: Author; is_archived: boolean };
|
||||
/**
|
||||
* Universal metadata for any managed user from a Mod distribution platform.
|
||||
*/
|
||||
|
|
@ -989,12 +989,12 @@ export interface ProviderSearchResults { provider: Providers; results: SearchRes
|
|||
/**
|
||||
* Providers for content packages
|
||||
*/
|
||||
export type Providers = 'Modrinth' | 'Curseforge';
|
||||
export type Providers = 'Modrinth' | 'Curseforge' | 'SkyClient';
|
||||
/**
|
||||
* Global Minecraft resolution.
|
||||
*/
|
||||
export type Resolution = [number, number];
|
||||
export interface SearchResult { slug: string; title: string; description: string; categories?: string[]; client_side: PackageSide; server_side: PackageSide; project_type: PackageType; downloads: bigint; icon_url?: string; project_id: string; author: string; display_categories?: string[]; versions: string[]; follows: number; date_created: string; date_modified: string };
|
||||
export interface SearchResult { slug: string; title: string; description: string; categories?: string[]; client_side: PackageSide; server_side: PackageSide; project_type: PackageType; downloads: bigint; icon_url?: string; project_id: string; author: string; versions: string[]; follows: number; date_created: string | null; date_modified: string | null };
|
||||
/**
|
||||
* A global settings state for the launcher.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -146,8 +146,8 @@ impl From<CurseforgePackage> for ManagedPackage {
|
|||
game_versions: vec![],
|
||||
loaders: vec![],
|
||||
icon_url: package.logo.and_then(|l| Some(l.url)),
|
||||
created: package.date_created,
|
||||
updated: package.date_modified,
|
||||
created: Some(package.date_created),
|
||||
updated: Some(package.date_modified),
|
||||
client: crate::store::PackageSide::Unknown,
|
||||
server: crate::store::PackageSide::Unknown,
|
||||
downloads: package.download_count,
|
||||
|
|
@ -193,11 +193,10 @@ impl Into<SearchResult> for CurseforgePackage {
|
|||
downloads: self.download_count,
|
||||
icon_url: self.logo.map_or(String::new(), |l| l.url),
|
||||
categories: vec![], // TODO
|
||||
display_categories: vec![],
|
||||
versions: vec![],
|
||||
follows: self.thumbs_up_count,
|
||||
date_created: self.date_created,
|
||||
date_modified: self.date_modified,
|
||||
date_created: Some(self.date_created),
|
||||
date_modified: Some(self.date_modified),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use crate::{Result, State};
|
|||
|
||||
mod curseforge;
|
||||
mod modrinth;
|
||||
mod skyclient;
|
||||
|
||||
/// Providers for content packages
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
|
|
@ -24,6 +25,7 @@ mod modrinth;
|
|||
pub enum Providers {
|
||||
Modrinth,
|
||||
Curseforge,
|
||||
SkyClient,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Providers {
|
||||
|
|
@ -39,6 +41,7 @@ impl Providers {
|
|||
match self {
|
||||
Self::Modrinth => "Modrinth",
|
||||
Self::Curseforge => "Curseforge",
|
||||
Self::SkyClient => "SkyClient",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,11 +51,12 @@ impl Providers {
|
|||
match self {
|
||||
Self::Modrinth => "https://modrinth.com",
|
||||
Self::Curseforge => "https://curseforge.com",
|
||||
Self::SkyClient => "https://skyclient.co",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_providers() -> &'static [Providers] {
|
||||
&[Self::Modrinth, Self::Curseforge]
|
||||
&[Self::Modrinth, Self::Curseforge, Self::SkyClient]
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
@ -98,35 +102,37 @@ impl Providers {
|
|||
)
|
||||
.await
|
||||
}
|
||||
Self::SkyClient => {
|
||||
skyclient::search(
|
||||
query,
|
||||
limit,
|
||||
offset,
|
||||
game_versions,
|
||||
loaders
|
||||
).await
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(&self, slug_or_id: &str) -> Result<ManagedPackage> {
|
||||
Ok(match self {
|
||||
Self::Modrinth => modrinth::get(slug_or_id).await?.into(),
|
||||
Self::Curseforge => curseforge::get(
|
||||
slug_or_id
|
||||
.parse::<u32>()
|
||||
.map_err(|err| anyhow::anyhow!(err))?,
|
||||
)
|
||||
.await?
|
||||
.into(),
|
||||
Self::Curseforge => curseforge::get(slug_or_id.parse::<u32>().map_err(|err| anyhow::anyhow!(err))?).await?.into(),
|
||||
Self::SkyClient => skyclient::get(slug_or_id).await?.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_multiple(&self, slug_or_ids: &[String]) -> Result<Vec<ManagedPackage>> {
|
||||
Ok(match self {
|
||||
Self::Modrinth => {
|
||||
if slug_or_ids.len() <= 0 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
if slug_or_ids.len() <= 0 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
modrinth::get_multiple(slug_or_ids)
|
||||
Ok(match self {
|
||||
Self::Modrinth => modrinth::get_multiple(slug_or_ids)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
},
|
||||
.collect(),
|
||||
Self::Curseforge => {
|
||||
let parsed_ids = slug_or_ids
|
||||
.iter()
|
||||
|
|
@ -145,6 +151,11 @@ impl Providers {
|
|||
.map(Into::into)
|
||||
.collect()
|
||||
},
|
||||
Self::SkyClient => skyclient::get_multiple(slug_or_ids)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -166,6 +177,8 @@ impl Providers {
|
|||
let data = curseforge::get_all_versions(project_id, game_versions, loaders, page, page_size).await?;
|
||||
(data.0.into_iter().map(Into::into).collect(), data.1)
|
||||
}
|
||||
|
||||
Self::SkyClient => todo!(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -181,6 +194,7 @@ impl Providers {
|
|||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
Self::SkyClient => todo!(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +229,8 @@ impl Providers {
|
|||
.await?
|
||||
.into_iter()
|
||||
.map(|(hash, version)| (hash, version.into()))
|
||||
.collect()
|
||||
.collect(),
|
||||
Self::SkyClient => todo!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ impl From<ModrinthPackage> for ManagedPackage {
|
|||
game_versions: value.game_versions,
|
||||
loaders: value.loaders,
|
||||
icon_url: value.icon_url,
|
||||
created: value.published,
|
||||
updated: value.updated,
|
||||
created: Some(value.published),
|
||||
updated: Some(value.updated),
|
||||
client: value.client_side,
|
||||
server: value.server_side,
|
||||
downloads: value.downloads as u64,
|
||||
|
|
|
|||
288
packages/core/src/api/package/content/skyclient.rs
Normal file
288
packages/core/src/api/package/content/skyclient.rs
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use tokio::sync::{OnceCell, RwLock};
|
||||
|
||||
use crate::{data::{Loader, ManagedPackage, ManagedUser, ManagedVersion}, store::{ProviderSearchResults, SearchResult}, utils::{http, pagination::Pagination}, Result, State};
|
||||
|
||||
async fn fetch<T: DeserializeOwned>(url: &str) -> Result<T> {
|
||||
let state = State::get().await?;
|
||||
Ok(serde_json::from_slice(
|
||||
&http::fetch(
|
||||
format!("{}/{}", crate::constants::SKYCLIENT_BASE_URL, url).as_str(),
|
||||
None,
|
||||
&state.fetch_semaphore
|
||||
).await?
|
||||
)?)
|
||||
}
|
||||
|
||||
static SKYCLIENTSTORE_STATIC: OnceCell<RwLock<SkyClientStore>> = OnceCell::const_new();
|
||||
|
||||
struct SkyClientStore {
|
||||
pub mods: Option<Vec<SkyClientMod>>
|
||||
}
|
||||
|
||||
impl SkyClientStore {
|
||||
pub async fn get() -> Result<Arc<tokio::sync::RwLockReadGuard<'static, Self>>> {
|
||||
Ok(Arc::new(
|
||||
SKYCLIENTSTORE_STATIC
|
||||
.get_or_try_init(Self::initialize)
|
||||
.await?
|
||||
.read()
|
||||
.await,
|
||||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[onelauncher_macros::memory]
|
||||
async fn initialize() -> Result<RwLock<Self>> {
|
||||
let mods: Vec<SkyClientMod> = fetch("/mods/mods.json").await?;
|
||||
|
||||
Ok(RwLock::new(SkyClientStore {
|
||||
mods: Some(mods)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SkyClientMod {
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub nicknames: Vec<String>,
|
||||
pub display: String,
|
||||
pub creator: String,
|
||||
pub discord_code: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
#[serde(default)]
|
||||
pub links: Vec<SkyClientModLink>,
|
||||
pub versions: Vec<SkyClientModVersion>,
|
||||
#[serde(default)]
|
||||
pub commands: Vec<String>,
|
||||
pub icon_scaling: Option<String>,
|
||||
pub oneconfig: Option<bool>,
|
||||
}
|
||||
|
||||
impl Into<SearchResult> for SkyClientMod {
|
||||
fn into(self) -> SearchResult {
|
||||
SearchResult {
|
||||
slug: self.id.clone(),
|
||||
project_id: self.id,
|
||||
author: self.creator,
|
||||
categories: vec![],
|
||||
title: self.display,
|
||||
client_side: crate::store::PackageSide::Required,
|
||||
server_side: crate::store::PackageSide::Unknown,
|
||||
date_created: None,
|
||||
date_modified: None,
|
||||
description: self.description.unwrap_or_default(),
|
||||
project_type: crate::data::PackageType::Mod,
|
||||
downloads: 0,
|
||||
icon_url: self.icon.map(|i| format!("{}/icons/{}", crate::constants::SKYCLIENT_BASE_URL, i)).unwrap_or_default(),
|
||||
versions: self.versions.iter().map(|v| v.version.clone()).collect(),
|
||||
follows: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ManagedPackage> for SkyClientMod {
|
||||
fn into(self) -> ManagedPackage {
|
||||
ManagedPackage {
|
||||
id: self.id.clone(),
|
||||
main: self.id,
|
||||
title: self.display,
|
||||
body: crate::store::PackageBody::Markdown(self.description.clone().unwrap_or_default()),
|
||||
categories: vec![],
|
||||
client: crate::store::PackageSide::Required,
|
||||
server: crate::store::PackageSide::Unknown,
|
||||
created: None,
|
||||
updated: None,
|
||||
description: self.description.unwrap_or_default(),
|
||||
downloads: 0,
|
||||
followers: 0,
|
||||
game_versions: self.versions.iter().flat_map(|v| v.game_versions.clone()).collect(),
|
||||
icon_url: self.icon.map(|i| format!("{}/icons/{}", crate::constants::SKYCLIENT_BASE_URL, i)),
|
||||
is_archived: false,
|
||||
license: None,
|
||||
loaders: self.versions.iter().flat_map(|v| v.loaders.clone().into_iter().map(Loader::from_string)).collect(),
|
||||
versions: self.versions.iter().map(|v| v.version.clone()).collect(),
|
||||
optional_categories: None,
|
||||
author: crate::store::Author::Users(vec![
|
||||
ManagedUser {
|
||||
id: self.creator.clone(),
|
||||
username: self.creator,
|
||||
is_organization_user: false,
|
||||
avatar_url: None,
|
||||
bio: None,
|
||||
role: None,
|
||||
url: self.discord_code.map(|code| format!("https://discord.gg/{}", code)),
|
||||
}
|
||||
]),
|
||||
package_type: crate::data::PackageType::Mod,
|
||||
provider: super::Providers::SkyClient,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SkyClientModLink {
|
||||
pub icon: String,
|
||||
pub text: String,
|
||||
pub link: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SkyClientModVersion {
|
||||
pub version: String,
|
||||
pub game_versions: Vec<String>,
|
||||
pub loaders: Vec<String>,
|
||||
pub file: String,
|
||||
pub url: String,
|
||||
pub hash: String,
|
||||
pub mod_id: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get(id: &str) -> Result<SkyClientMod> {
|
||||
let store = SkyClientStore::get().await?;
|
||||
|
||||
let mods = match &store.mods {
|
||||
Some(mods) => mods,
|
||||
None => return Err(anyhow::anyhow!("mod not found").into())
|
||||
};
|
||||
|
||||
for m in mods {
|
||||
if m.id == id {
|
||||
return Ok(m.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow::anyhow!("mod not found").into())
|
||||
}
|
||||
|
||||
pub async fn get_multiple(slug_or_ids: &[String]) -> Result<Vec<SkyClientMod>> {
|
||||
let store = SkyClientStore::get().await?;
|
||||
|
||||
let mods = match &store.mods {
|
||||
Some(mods) => mods,
|
||||
None => return Ok(vec![])
|
||||
};
|
||||
|
||||
let mut results = vec![];
|
||||
|
||||
for id in slug_or_ids {
|
||||
for m in mods {
|
||||
if m.id == *id {
|
||||
results.push(m.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
// pub async fn get_all_versions(
|
||||
// project_id: &str,
|
||||
// game_versions: Option<Vec<String>>,
|
||||
// loaders: Option<Vec<Loader>>,
|
||||
// page: Option<u32>,
|
||||
// page_size: Option<u16>,
|
||||
// ) -> Result<(Vec<ManagedVersion>, Pagination)> {
|
||||
// let store = SkyClientStore::get().await?;
|
||||
|
||||
// let mods = match &store.mods {
|
||||
// Some(mods) => mods,
|
||||
// None => return Ok((vec![], Pagination::default()))
|
||||
// };
|
||||
|
||||
|
||||
// let mut versions = vec![];
|
||||
|
||||
// for m in mods {
|
||||
// if m.id == project_id {
|
||||
// for v in m.versions {
|
||||
// let mut can_add = true;
|
||||
|
||||
// if let Some(game_versions) = &game_versions {
|
||||
// can_add = v.game_versions.iter().any(|gv| game_versions.contains(gv))
|
||||
// }
|
||||
|
||||
// if can_add {
|
||||
// if let Some(loaders) = &loaders {
|
||||
// can_add = v.loaders.iter().any(|l| loaders.contains(&Loader::from_string(l)))
|
||||
// }
|
||||
// }
|
||||
|
||||
// if can_add {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
pub async fn search(
|
||||
query: Option<String>,
|
||||
limit: Option<u8>,
|
||||
offset: Option<u32>,
|
||||
game_versions: Option<Vec<String>>,
|
||||
// package_types: Option<Vec<PackageType>>,
|
||||
loaders: Option<Vec<Loader>>,
|
||||
) -> Result<ProviderSearchResults> {
|
||||
let store = SkyClientStore::get().await?;
|
||||
|
||||
let mut results = ProviderSearchResults {
|
||||
provider: super::Providers::SkyClient,
|
||||
results: vec![],
|
||||
total: 0,
|
||||
};
|
||||
|
||||
let mods = match &store.mods {
|
||||
Some(mods) => mods,
|
||||
None => return Ok(results),
|
||||
};
|
||||
|
||||
let query = query.map(|q| q.trim().to_lowercase());
|
||||
let limit = limit.map(|l| l as usize);
|
||||
let offset = offset.unwrap_or(0);
|
||||
|
||||
for m in mods {
|
||||
let mut can_add = true;
|
||||
|
||||
if let Some(query) = &query {
|
||||
if !query.is_empty() {
|
||||
let display = m.display.to_lowercase();
|
||||
|
||||
can_add = display.contains(query) || m.id.contains(query) || m.nicknames.contains(query)
|
||||
}
|
||||
}
|
||||
|
||||
if can_add {
|
||||
if let Some(game_versions) = &game_versions {
|
||||
if !game_versions.is_empty() {
|
||||
can_add = m.versions.iter().any(|v| v.game_versions.iter().any(|gv| game_versions.contains(&gv)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if can_add {
|
||||
if let Some(loaders) = &loaders {
|
||||
if !loaders.is_empty() {
|
||||
can_add = m.versions.iter().any(|v| v.loaders.iter().any(|l| loaders.contains(&Loader::from_string(l.to_owned()))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if can_add {
|
||||
if limit.map(|l| results.results.len() < l).unwrap_or(true) && offset <= results.total {
|
||||
let result: SearchResult = m.clone().into();
|
||||
results.results.push(result);
|
||||
}
|
||||
|
||||
results.total += 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
|
@ -48,6 +48,8 @@ pub const METADATA_API_URL: &str = "https://meta.polyfrost.org";
|
|||
pub const FEATURED_PACKAGES_URL: &str = "https://polyfrost.org/meta/onelauncher/featured.json";
|
||||
/// <https://mclo.gs>/ API base url.
|
||||
pub const MCLOGS_API_URL: &str = "https://api.mclo.gs/1";
|
||||
/// https://skyclient.co/ metadata base url.
|
||||
pub const SKYCLIENT_BASE_URL: &str = "https://raw.githubusercontent.com/SkyblockClient/SkyblockClient-REPO/refs/heads/main/v1";
|
||||
|
||||
// =========== Paths ===========
|
||||
/// The public `settings.json` file used to store the global [`Settings`] state.
|
||||
|
|
|
|||
|
|
@ -376,7 +376,12 @@ impl Loader {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_string(val: &str) -> Self {
|
||||
pub fn from_string(val: String) -> Self {
|
||||
Self::from_str(val.as_str())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_str(val: &str) -> Self {
|
||||
match val {
|
||||
"forge" => Self::Forge,
|
||||
"fabric" => Self::Fabric,
|
||||
|
|
|
|||
|
|
@ -148,12 +148,10 @@ pub struct SearchResult {
|
|||
pub icon_url: String,
|
||||
pub project_id: String,
|
||||
pub author: String,
|
||||
#[serde(default)]
|
||||
pub display_categories: Vec<String>,
|
||||
pub versions: Vec<String>,
|
||||
pub follows: u32,
|
||||
pub date_created: DateTime<Utc>,
|
||||
pub date_modified: DateTime<Utc>,
|
||||
pub date_created: Option<DateTime<Utc>>,
|
||||
pub date_modified: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
|
@ -719,8 +717,8 @@ pub struct ManagedPackage {
|
|||
pub loaders: Vec<Loader>,
|
||||
pub icon_url: Option<String>,
|
||||
|
||||
pub created: DateTime<Utc>,
|
||||
pub updated: DateTime<Utc>,
|
||||
pub created: Option<DateTime<Utc>>,
|
||||
pub updated: Option<DateTime<Utc>>,
|
||||
pub client: PackageSide,
|
||||
pub server: PackageSide,
|
||||
pub downloads: u64,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue