feat: modrinth search implementation
This commit is contained in:
parent
089fbd3e26
commit
3fa8e7f150
6 changed files with 203 additions and 21 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use onelauncher_core::{api::{cluster, packages::{self, download_package, provider::ProviderExt}, proxy::ProxyDynamic}, error::LauncherResult, initialize_core, store::{CoreOptions, Dirs}};
|
||||
use onelauncher_core::{api::{cluster, packages::{self, data::{SearchQuery, Sort}, download_package, provider::ProviderExt}, proxy::ProxyDynamic}, error::LauncherResult, initialize_core, store::{CoreOptions, Dirs}};
|
||||
use onelauncher_entity::{loader::GameLoader, package::Provider};
|
||||
|
||||
#[tokio::main]
|
||||
|
|
@ -19,6 +19,15 @@ pub async fn main() -> LauncherResult<()> {
|
|||
|
||||
// Fetch our package and its versions
|
||||
let provider = Provider::Modrinth;
|
||||
|
||||
println!("search results: {:#?}", provider.search(&SearchQuery {
|
||||
query: Some("chatting".to_string()),
|
||||
offset: Some(0),
|
||||
limit: Some(10),
|
||||
sort: Some(Sort::Newest),
|
||||
filters: None,
|
||||
}).await?);
|
||||
|
||||
let package = &provider.get("chatting").await?;
|
||||
let versions = provider.get_versions(package.version_ids.as_slice()).await?;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,16 +26,52 @@ pub enum Sort {
|
|||
Updated
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Sort {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
Self::Relevance => "relevance",
|
||||
Self::Downloads => "downloads",
|
||||
Self::Newest => "newest",
|
||||
Self::Updated => "updated",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SearchQuery {
|
||||
pub query: Option<String>,
|
||||
pub offset: Option<usize>,
|
||||
pub limit: Option<usize>,
|
||||
pub sort: Option<String>,
|
||||
pub sort: Option<Sort>,
|
||||
pub filters: Option<Filters>,
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SearchResult {
|
||||
pub project_id: String,
|
||||
pub project_type: String,
|
||||
pub slug: String,
|
||||
pub author: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub categories: Vec<String>,
|
||||
pub display_categories: Vec<String>,
|
||||
pub versions: Vec<String>,
|
||||
pub downloads: usize,
|
||||
pub icon_url: String,
|
||||
pub date_created: DateTime<Utc>,
|
||||
pub date_modified: DateTime<Utc>,
|
||||
pub latest_version: String,
|
||||
pub license: String,
|
||||
pub client_side: PackageSide,
|
||||
pub server_side: PackageSide,
|
||||
/// List of URLs to images
|
||||
pub gallery: Vec<String>,
|
||||
// pub color: i64,
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use onelauncher_entity::loader::GameLoader;
|
||||
use onelauncher_entity::package::Provider;
|
||||
|
||||
use crate::error::LauncherResult;
|
||||
use crate::{api::packages::data::SearchResult, error::LauncherResult};
|
||||
use crate::utils::pagination::Paginated;
|
||||
|
||||
use super::data::{ManagedPackage, ManagedUser, ManagedVersion, PackageAuthor, SearchQuery};
|
||||
|
|
@ -12,7 +12,7 @@ pub use modrinth::ModrinthProviderImpl;
|
|||
|
||||
#[async_trait::async_trait]
|
||||
pub trait ProviderExt {
|
||||
async fn search(&self, query: &SearchQuery) -> LauncherResult<Paginated<ManagedPackage>>;
|
||||
async fn search(&self, query: &SearchQuery) -> LauncherResult<Paginated<SearchResult>>;
|
||||
async fn get(&self, slug: &str) -> LauncherResult<ManagedPackage>;
|
||||
async fn get_multiple(&self, slugs: &[String]) -> LauncherResult<Vec<ManagedPackage>>;
|
||||
async fn get_versions_by_hashes(
|
||||
|
|
@ -44,7 +44,7 @@ pub trait ProviderExt {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl ProviderExt for Provider {
|
||||
async fn search(&self, query: &SearchQuery) -> LauncherResult<Paginated<ManagedPackage>> {
|
||||
async fn search(&self, query: &SearchQuery) -> LauncherResult<Paginated<SearchResult>> {
|
||||
match self {
|
||||
Self::Modrinth => ModrinthProviderImpl.search(query).await,
|
||||
_ => todo!("unimplemented provider"),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
use super::ProviderExt;
|
||||
use crate::api::packages::data::{
|
||||
ManagedPackage, ManagedUser, ManagedVersion, ManagedVersionDependency, ManagedVersionFile,
|
||||
PackageAuthor, PackageDependencyType, PackageDonationUrl, PackageLicense, PackageLinks,
|
||||
PackageReleaseType, PackageSide, PackageStatus, SearchQuery, DEFAULT_LIMIT,
|
||||
ManagedPackage, ManagedUser, ManagedVersion, ManagedVersionDependency, ManagedVersionFile, PackageAuthor, PackageDependencyType, PackageDonationUrl, PackageLicense, PackageLinks, PackageReleaseType, PackageSide, PackageStatus, SearchQuery, SearchResult, DEFAULT_LIMIT
|
||||
};
|
||||
use crate::error::LauncherResult;
|
||||
use crate::utils::http;
|
||||
|
|
@ -39,14 +37,69 @@ impl ModrinthProviderImpl {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl ProviderExt for ModrinthProviderImpl {
|
||||
async fn search(&self, query: &SearchQuery) -> LauncherResult<Paginated<ManagedPackage>> {
|
||||
async fn search(&self, query: &SearchQuery) -> LauncherResult<Paginated<SearchResult>> {
|
||||
let mut url = Url::parse(url!("/search"))?;
|
||||
let mut params = url.query_pairs_mut();
|
||||
|
||||
params.append_pair("limit", &query.limit.unwrap_or(DEFAULT_LIMIT).to_string());
|
||||
params.append_pair("offset", &query.offset.unwrap_or(0).to_string());
|
||||
{
|
||||
let mut params = url.query_pairs_mut();
|
||||
|
||||
todo!()
|
||||
params.append_pair("limit", &query.limit.unwrap_or(DEFAULT_LIMIT).to_string());
|
||||
params.append_pair("offset", &query.offset.unwrap_or(0).to_string());
|
||||
params.append_pair("query", query.query.clone().unwrap_or_default().as_ref());
|
||||
|
||||
if let Some(filters) = &query.filters {
|
||||
let mut builder = FacetBuilder::builder();
|
||||
|
||||
if let Some(categories) = &filters.categories {
|
||||
for category in categories {
|
||||
builder.and(Facet("categories".to_string(), FacetOperation::Eq, category.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mc_versions) = &filters.game_versions {
|
||||
for mc_version in mc_versions {
|
||||
builder.and(Facet("versions".to_string(), FacetOperation::Eq, mc_version.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(package_types) = &filters.package_types {
|
||||
for package_type in package_types {
|
||||
builder.and(Facet("project_type".to_string(), FacetOperation::Eq, package_type_to_string(package_type)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(loaders) = &filters.loaders {
|
||||
for loader in loaders {
|
||||
builder.and(Facet("loaders".to_string(), FacetOperation::Eq, loader.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
params.append_pair("facets", &builder.build());
|
||||
}
|
||||
|
||||
if let Some(sort) = &query.sort {
|
||||
params.append_pair("index", &sort.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let url = url.as_str();
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SearchResults {
|
||||
pub hits: Vec<SearchResult>,
|
||||
pub offset: usize,
|
||||
pub limit: usize,
|
||||
pub total_hits: usize,
|
||||
}
|
||||
|
||||
let response = http::fetch_json::<SearchResults>(Method::GET, url, None, None).await?;
|
||||
|
||||
Ok(Paginated {
|
||||
offset: response.offset,
|
||||
limit: response.limit,
|
||||
total: response.total_hits,
|
||||
items: response.hits,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get(&self, slug: &str) -> LauncherResult<ManagedPackage> {
|
||||
|
|
@ -250,6 +303,17 @@ impl ProviderExt for ModrinthProviderImpl {
|
|||
}
|
||||
}
|
||||
|
||||
fn package_type_to_string(package_type: &PackageType) -> String {
|
||||
match package_type {
|
||||
PackageType::Mod => "mod",
|
||||
PackageType::ResourcePack => "resourcepack",
|
||||
PackageType::Shader => "shader",
|
||||
PackageType::DataPack => "datapack",
|
||||
PackageType::ModPack => "modpack",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize)]
|
||||
struct ModrinthPackage {
|
||||
|
|
@ -471,3 +535,77 @@ impl From<ModrinthUser> for ManagedUser {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
enum FacetOperation {
|
||||
NotEq,
|
||||
LargeEq,
|
||||
Large,
|
||||
SmallEq,
|
||||
Small,
|
||||
Eq,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FacetOperation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::NotEq => "!=",
|
||||
Self::LargeEq => ">=",
|
||||
Self::Large => ">",
|
||||
Self::SmallEq => "<=",
|
||||
Self::Small => "<",
|
||||
Self::Eq => "=",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Facet(pub String, pub FacetOperation, pub String);
|
||||
|
||||
impl std::fmt::Display for Facet {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(format!("{}{}{}", self.0, self.1, self.2).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
struct FacetBuilder {
|
||||
pub facets: Vec<Vec<Facet>>,
|
||||
}
|
||||
|
||||
impl FacetBuilder {
|
||||
pub const fn builder() -> Self {
|
||||
Self { facets: vec![] }
|
||||
}
|
||||
|
||||
pub fn and(&mut self, facet: Facet) -> &Self {
|
||||
self.facets.push(vec![facet]);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn or(&mut self, facet: Facet) -> &Self {
|
||||
let mut last_facet = self.facets.pop().unwrap_or_default();
|
||||
last_facet.push(facet);
|
||||
self.facets.push(last_facet);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&self) -> String {
|
||||
let mut builder: Vec<String> = vec![];
|
||||
|
||||
for facet in &self.facets {
|
||||
let mut stringified = String::new();
|
||||
stringified.push('[');
|
||||
for (i, f) in facet.iter().enumerate() {
|
||||
if i != 0 {
|
||||
stringified.push(',');
|
||||
}
|
||||
stringified.push_str(format!("\"{f}\"").as_str());
|
||||
}
|
||||
stringified.push(']');
|
||||
|
||||
builder.push(stringified);
|
||||
}
|
||||
|
||||
builder.join(",")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use interpulse::api::minecraft::Version;
|
||||
use onelauncher_entity::{clusters, icon::Icon, loader::GameLoader, package::Provider, packages, prelude::entity, resolution::Resolution};
|
||||
use onelauncher_entity::{icon::Icon, loader::GameLoader, package::Provider, packages, resolution::Resolution};
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use serde::Serialize;
|
||||
use crate::{api::packages::{data::{ManagedPackage, ManagedUser, ManagedVersion, SearchQuery}, provider::ProviderExt}, store::{Settings, State}, utils::pagination::Paginated};
|
||||
use crate::{api::packages::{data::{SearchResult, ManagedPackage, ManagedUser, ManagedVersion, SearchQuery}, provider::ProviderExt}, store::{Settings, State}, utils::pagination::Paginated};
|
||||
use tauri::{AppHandle, Runtime};
|
||||
|
||||
use crate::{api::{self, cluster::dao::ClusterId}, error::{LauncherError, LauncherResult}, store::{credentials::MinecraftCredentials, Core}};
|
||||
|
|
@ -84,7 +83,7 @@ pub trait TauriLauncherApi {
|
|||
|
||||
// Packages
|
||||
#[taurpc(alias = "searchPackages")]
|
||||
async fn search_packages(provider: Provider, query: SearchQuery) -> LauncherResult<Paginated<ManagedPackage>>;
|
||||
async fn search_packages(provider: Provider, query: SearchQuery) -> LauncherResult<Paginated<SearchResult>>;
|
||||
|
||||
#[taurpc(alias = "getPackage")]
|
||||
async fn get_package(provider: Provider, slug: String) -> LauncherResult<ManagedPackage>;
|
||||
|
|
@ -390,7 +389,7 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
}
|
||||
|
||||
// Packages
|
||||
async fn search_packages(self, provider: Provider, query: SearchQuery) -> LauncherResult<Paginated<ManagedPackage>> {
|
||||
async fn search_packages(self, provider: Provider, query: SearchQuery) -> LauncherResult<Paginated<SearchResult>> {
|
||||
Ok(provider.search(&query).await?)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ use serde::{Deserialize, Serialize};
|
|||
#[onelauncher_macro::specta]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Paginated<T>
|
||||
where T: Clone + Serialize + Send {
|
||||
where T: Clone + Serialize + Send + Sync {
|
||||
pub total: usize,
|
||||
pub offset: usize,
|
||||
pub limit: usize,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: Clone + Serialize + Send> Debug for Paginated<T> {
|
||||
impl<T: Clone + Serialize + Send + Sync> Debug for Paginated<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Paginated")
|
||||
.field("total", &self.total)
|
||||
|
|
@ -23,7 +23,7 @@ impl<T: Clone + Serialize + Send> Debug for Paginated<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl <T: Clone + Serialize + Send> Paginated<T> {
|
||||
impl <T: Clone + Serialize + Send + Sync> Paginated<T> {
|
||||
#[must_use]
|
||||
pub const fn new(total: usize, offset: usize, limit: usize, items: Vec<T>) -> Self {
|
||||
Self { total, offset, limit, items }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue