use crate::{PackageIdent, VersionIdent};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, hash::Hash};
use url::Url;
use uuid::Uuid;
#[derive(Serialize, Deserialize, Debug, Clone, Eq)]
#[non_exhaustive]
pub struct PackageV1 {
#[serde(rename = "uuid4")]
pub uuid: Uuid,
#[serde(rename = "owner")]
pub namespace: String,
pub name: String,
#[serde(rename = "full_name")]
pub ident: PackageIdent,
pub categories: HashSet<String>,
pub date_created: DateTime<Utc>,
pub date_updated: DateTime<Utc>,
pub donation_link: Option<Url>,
pub has_nsfw_content: bool,
pub is_deprecated: bool,
pub is_pinned: bool,
pub package_url: Url,
pub rating_score: u32,
pub versions: Vec<PackageVersionV1>,
}
impl PackageV1 {
pub fn latest(&self) -> &PackageVersionV1 {
&self.versions[0]
}
pub fn is_modpack(&self) -> bool {
self.categories.contains("Modpacks")
}
pub fn version_by_id(&self, uuid: &Uuid) -> Option<&PackageVersionV1> {
self.versions.iter().find(|v| v.uuid == *uuid)
}
pub fn version_by_name(&self, version: &semver::Version) -> Option<&PackageVersionV1> {
self.versions.iter().find(|v| v.number == *version)
}
pub fn total_downloads(&self) -> u32 {
self.versions.iter().map(|v| v.downloads).sum()
}
}
impl Hash for PackageV1 {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.uuid.hash(state);
}
}
impl PartialEq for PackageV1 {
fn eq(&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq)]
#[non_exhaustive]
pub struct PackageVersionV1 {
#[serde(rename = "uuid4")]
pub uuid: Uuid,
pub name: String,
#[serde(rename = "version_number")]
pub number: semver::Version,
#[serde(rename = "full_name")]
pub ident: VersionIdent,
pub date_created: DateTime<Utc>,
pub dependencies: Vec<VersionIdent>,
pub description: String,
pub download_url: Url,
pub downloads: u32,
pub file_size: u64,
pub icon: Url,
pub is_active: bool,
pub website_url: String,
}
impl PartialEq for PackageVersionV1 {
fn eq(&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
impl Hash for PackageVersionV1 {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.uuid.hash(state);
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct LegacyProfileCreateResponse {
pub key: Uuid,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct UserMediaInitiateUploadParams {
#[serde(rename = "filename")]
pub name: String,
#[serde(rename = "file_size_bytes")]
pub size: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct UserMediaInitiateUploadResponse {
pub user_media: UserMedia,
pub upload_urls: Vec<UploadPartUrl>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct UserMedia {
pub uuid: Uuid,
#[serde(rename = "filename")]
pub name: String,
pub size: u64,
#[serde(rename = "datetime_created")]
pub date_created: DateTime<Utc>,
pub expiry: DateTime<Utc>,
pub status: UserMediaStatus,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum UserMediaStatus {
Initial,
UploadInitiated,
UploadCreated,
UploadError,
UploadComplete,
UploadAborted,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct UploadPartUrl {
#[serde(rename = "part_number")]
pub number: u32,
pub url: String,
pub offset: u64,
pub length: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct UserMediaFinishUploadParams {
pub parts: Vec<CompletedPart>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CompletedPart {
#[serde(rename = "ETag")]
pub tag: String,
#[serde(rename = "PartNumber")]
pub number: u32,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct PackageSubmissionResult {
pub package_version: PackageVersion,
pub available_communities: Vec<AvailableCommunity>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct AvailableCommunity {
pub community: Community,
pub categories: CommunityCategory,
pub url: Url,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct CommunityCategory {
pub name: String,
pub slug: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct PackageVersionMetrics {
pub downloads: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct PackageMetrics {
pub downloads: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct RenderMarkdownParams {
pub markdown: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct RenderMarkdownResponse {
pub html: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct MarkdownResponse {
pub markdown: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub struct PackageIndexEntry {
pub namespace: String,
pub name: String,
pub version_number: semver::Version,
pub file_format: Option<String>,
pub file_size: u64,
pub dependencies: Vec<VersionIdent>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq)]
#[non_exhaustive]
pub struct PackageVersion {
#[serde(rename = "full_name")]
pub ident: VersionIdent,
pub description: String,
pub icon: Url,
pub dependencies: Vec<VersionIdent>,
pub download_url: Url,
pub downloads: u32,
pub date_created: DateTime<Utc>,
pub website_url: String,
pub is_active: bool,
}
impl PartialEq for PackageVersion {
fn eq(&self, other: &Self) -> bool {
self.ident == other.ident
}
}
impl Hash for PackageVersion {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.ident.hash(state);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq)]
#[non_exhaustive]
pub struct Package {
#[serde(rename = "full_name")]
pub ident: PackageIdent,
pub package_url: Url,
pub date_created: DateTime<Utc>,
pub date_updated: DateTime<Utc>,
pub rating_score: i32,
pub is_pinned: bool,
pub is_deprecated: bool,
pub total_downloads: i32,
pub latest: PackageVersion,
pub community_listings: Vec<PackageListingExperimental>,
}
impl PartialEq for Package {
fn eq(&self, other: &Self) -> bool {
self.ident == other.ident
}
}
impl Hash for Package {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.ident.hash(state);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PackageListingExperimental {
pub has_nsfw_content: bool,
pub categories: HashSet<String>,
pub community: String,
pub review_status: ReviewStatus,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ReviewStatus {
Unreviewed,
Approved,
Rejected,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct PaginatedResponse<T> {
pub pagination: Pagination,
pub results: Vec<T>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct Pagination {
pub next_link: Option<Url>,
pub previous_link: Option<Url>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct Community {
#[serde(rename = "identifier")]
pub ident: String,
pub name: String,
pub discord_url: Option<Url>,
pub wiki_url: Option<Url>,
pub require_package_listing_approval: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct IconValidatorParams {
pub icon_data: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct ManifestV1ValidatorParams {
pub namespace: String,
pub manifest_data: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct ReadmeValidatorParams {
pub readme_data: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct ValidatorResponse {
pub success: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct WikisResponse {
pub results: Vec<ListedWiki>,
pub cursor: DateTime<Utc>,
pub has_more: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct ListedWiki {
pub namespace: String,
pub name: String,
pub wiki: Wiki,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct Wiki {
pub id: String,
pub title: String,
pub slug: String,
#[serde(rename = "datetime_created")]
pub created_at: DateTime<Utc>,
#[serde(rename = "datetime_updated")]
pub updated_at: DateTime<Utc>,
pub pages: Vec<WikiPage>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct WikiPage {
pub id: String,
pub title: String,
pub slug: String,
#[serde(rename = "datetime_created")]
pub created_at: DateTime<Utc>,
#[serde(rename = "datetime_updated")]
pub updated_at: DateTime<Utc>,
#[serde(default, rename = "markdown_content")]
pub content: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct WikiPageUpsert {
pub id: Option<String>,
pub title: String,
#[serde(rename = "markdown_content")]
pub content: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub struct WikiPageDelete {
pub id: String,
}
pub mod schema {
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use uuid::Uuid;
use crate::PackageIdent;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Schema {
pub schema_version: String,
pub games: BTreeMap<String, Game>,
pub communities: BTreeMap<String, Community>,
pub package_installers: HashMap<String, PackageInstaller>,
pub modloader_packages: Vec<ModloaderPackage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Game {
pub uuid: Uuid,
pub label: String,
pub meta: Meta,
pub distributions: Vec<Distribution>,
pub r2modman: Option<Vec<R2ModmanConfig>>,
pub thunderstore: Option<Community>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Meta {
pub display_name: String,
pub icon_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Distribution {
pub platform: Platform,
pub identifier: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct R2ModmanConfig {
pub meta: Meta,
pub internal_folder_name: String,
pub data_folder_name: String,
pub distributions: Vec<Distribution>,
pub settings_identifier: String,
pub package_index: String,
pub steam_folder_name: String,
pub exe_names: Vec<String>,
pub game_instance_type: String,
pub game_selection_display_mode: String,
pub additional_search_strings: Vec<String>,
pub package_loader: Loader,
pub install_rules: Vec<InstallRule>,
pub relative_file_exclusions: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstallRule {
pub route: String,
pub default_file_extensions: Vec<String>,
pub tracking_method: TrackingMethod,
pub sub_routes: Vec<InstallRule>,
pub is_default_location: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Community {
pub display_name: String,
#[serde(default)]
pub listed: bool,
pub meta: Option<ThunderstoreMeta>,
pub categories: BTreeMap<String, Category>,
pub sections: BTreeMap<String, Section>,
pub wiki_url: Option<String>,
pub discord_url: Option<String>,
pub autolist_package_ids: Option<Vec<String>>,
pub short_description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThunderstoreMeta {
pub icon: Option<String>,
pub cover: Option<String>,
pub background: Option<String>,
pub hero: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Category {
pub label: String,
#[serde(default)]
pub hidden: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Section {
pub name: String,
pub require_categories: Option<Vec<String>>,
pub exclude_categories: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageInstaller {
pub name: String,
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModloaderPackage {
pub package_id: PackageIdent,
pub root_folder: String,
pub loader: Loader,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum Platform {
Steam,
EpicGamesStore,
XboxGamePass,
Other,
OculusStore,
SteamDirect,
Origin,
}
#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum Loader {
#[serde(rename = "bepinex")]
BepInEx,
#[serde(rename = "melonloader")]
MelonLoader,
#[serde(rename = "recursive-melonloader")]
RecursiveMelonLoader,
None,
Lovely,
#[serde(rename = "godotml")]
GodotML,
ReturnOfModding,
Northstar,
Shimloader,
#[serde(rename = "gdweave")]
GDWeave,
Umm,
#[serde(rename = "bepisloader")]
BepisLoader,
Rivet,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum TrackingMethod {
Subdir,
SubdirNoFlatten,
State,
PackageZip,
None,
}
}