diff --git a/src/types/curseforge/files.ts b/src/types/curseforge/files.ts new file mode 100644 index 0000000..298dc82 --- /dev/null +++ b/src/types/curseforge/files.ts @@ -0,0 +1,199 @@ +import { z } from "zod"; + +// region FileStatus — 文件状态 +export const FileStatusSchema = z.union([ + z.literal(1), // Processing + z.literal(4), // Approved + z.literal(10), // Released + z.literal(12), // Deprecated +]); +export type FileStatus = z.infer; +// endregion + +// region FileReleaseType — 文件的发布类型(数值枚举) +export const FileReleaseTypeSchema = z.union([ + z.literal(1), // Release + z.literal(2), // Beta + z.literal(3), // Alpha +]); +export type FileReleaseType = z.infer; +// endregion + +// region HashAlgo — 哈希算法 +export const HashAlgoSchema = z.union([ + z.literal(1), // Sha1 + z.literal(2), // Md5 +]); +export type HashAlgo = z.infer; +// endregion + +// region FileRelationType — 依赖关系类型(数值枚举) +export const FileRelationTypeSchema = z.union([ + z.literal(1), // EmbeddedLibrary + z.literal(2), // OptionalDependency + z.literal(3), // RequiredDependency + z.literal(4), // Tool + z.literal(5), // Incompatible + z.literal(6), // Include +]); +export type FileRelationType = z.infer; +// endregion + +// region ModLoaderType — Mod Loader 类型(查询参数用) +export const ModLoaderTypeSchema = z.union([ + z.literal(0), // Any + z.literal(1), // Forge + z.literal(2), // Cauldron + z.literal(3), // LiteLoader + z.literal(4), // Fabric + z.literal(5), // Quilt + z.literal(6), // NeoForge +]); +export type ModLoaderType = z.infer; +// endregion + +// region FileHash — 文件哈希 +export const FileHashSchema = z.object({ + /** 哈希值 */ + value: z.string(), + /** 哈希算法 */ + algo: HashAlgoSchema, +}); +export type FileHash = z.infer; +// endregion + +// region SortableGameVersion — 排序用版本信息 +export const SortableGameVersionSchema = z.object({ + /** 游戏版本名称,如 "1.21.5" */ + gameVersionName: z.string(), + /** 补齐前缀的游戏版本号,用于排序,如 "0000000001.0000000021.0000000005" */ + gameVersionPadded: z.string(), + /** 游戏版本,如 "1.21.5" */ + gameVersion: z.string(), + /** 游戏版本发布日期(ISO-8601) */ + gameVersionReleaseDate: z.string(), + /** 版本类型 ID,对应 Game Version Types API 中的 id */ + gameVersionTypeId: z.number().int(), +}); +export type SortableGameVersion = z.infer; +// endregion + +// region FileDependency — 文件依赖 +export const FileDependencySchema = z.object({ + /** 依赖的项目 ID */ + modId: z.number().int(), + /** 关系类型 */ + relationType: FileRelationTypeSchema, +}); +export type FileDependency = z.infer; +// endregion + +// region FileModule — 文件模块 +export const FileModuleSchema = z.object({ + /** 模块名称 */ + name: z.string(), + /** 模块指纹 */ + fingerprint: z.number().int(), +}); +export type FileModule = z.infer; +// endregion + +// region CurseForgeFile — CurseForge 文件完整对象 +export const CurseForgeFileSchema = z.object({ + /** 文件 ID */ + id: z.number().int(), + /** 游戏 ID */ + gameId: z.number().int(), + /** 所属项目 ID */ + modId: z.number().int(), + /** 是否可下载 */ + isAvailable: z.boolean(), + /** 显示名称 */ + displayName: z.string(), + /** 文件名 */ + fileName: z.string(), + /** 发布类型 */ + releaseType: FileReleaseTypeSchema, + /** 文件状态 */ + fileStatus: FileStatusSchema, + /** 文件哈希列表 */ + hashes: z.array(FileHashSchema), + /** 文件时间戳(ISO-8601) */ + fileDate: z.string(), + /** 文件大小(字节) */ + fileLength: z.number().int(), + /** 下载次数 */ + downloadCount: z.number().int(), + /** 磁盘占用(字节) */ + fileSizeOnDisk: z.number().int(), + /** 下载地址 */ + downloadUrl: z.string(), + /** 关联的游戏版本(字符串列表) */ + gameVersions: z.array(z.string()), + /** 排序用版本信息 */ + sortableGameVersions: z.array(SortableGameVersionSchema), + /** 依赖关系列表 */ + dependencies: z.array(FileDependencySchema), + /** 是否作为替代文件暴露 */ + exposeAsAlternative: z.boolean(), + /** 父项目文件 ID,无则为 null */ + parentProjectFileId: z.number().int().nullable(), + /** 替代文件 ID,无则为 null */ + alternateFileId: z.number().int().nullable(), + /** 是否为服务端整合包 */ + isServerPack: z.boolean(), + /** 服务端整合包文件 ID,无则为 null */ + serverPackFileId: z.number().int().nullable(), + /** 是否为抢先体验内容 */ + isEarlyAccessContent: z.boolean(), + /** 抢先体验截止日期(ISO-8601),无则为 null */ + earlyAccessEndDate: z.string().nullable(), + /** 文件指纹 */ + fileFingerprint: z.number().int(), + /** 文件模块列表 */ + modules: z.array(FileModuleSchema), +}); +export type CurseForgeFile = z.infer; +// endregion + +// region Pagination — 分页信息 +export const PaginationSchema = z.object({ + /** 本次返回的起始索引 */ + index: z.number().int(), + /** 请求的页大小 */ + pageSize: z.number().int(), + /** 本次实际返回数量 */ + resultCount: z.number().int(), + /** 总文件数 */ + totalCount: z.number().int(), +}); +export type Pagination = z.infer; +// endregion + +// region GetModFilesParams — GET /v1/mods/{modId}/files 查询参数 +export const GetModFilesParamsSchema = z.object({ + /** 项目 ID(路径参数) */ + modId: z.number().int(), + /** 按游戏版本过滤 */ + gameVersion: z.string().optional(), + /** 按 Mod Loader 类型过滤 */ + modLoaderType: ModLoaderTypeSchema.optional(), + /** 按 gameVersionTypeId 过滤 */ + gameVersionTypeId: z.number().int().optional(), + /** 分页起始索引(从 0 开始) */ + index: z.number().int().optional(), + /** 每页数量,默认/最大 50 */ + pageSize: z.number().int().optional(), +}); +export type GetModFilesParams = z.infer; +// endregion + +// region GetModFilesResponse — GET /v1/mods/{modId}/files 成功响应体 +export const GetModFilesResponseSchema = z.object({ + /** 文件列表 */ + data: z.array(CurseForgeFileSchema), + /** 分页信息 */ + pagination: PaginationSchema, +}); +export type GetModFilesResponse = z.infer; +// endregion diff --git a/src/types/curseforge/game.ts b/src/types/curseforge/game.ts new file mode 100644 index 0000000..d6c3c2d --- /dev/null +++ b/src/types/curseforge/game.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; + +// region GameVersionType — GET /api/game/version-types 响应元素 +export const GameVersionTypeSchema = z.object({ + /** 版本类型 ID(即 gameVersionTypeID) */ + id: z.number().int(), + /** 类型名称,如 "Modloader"、"Environment" */ + name: z.string(), + /** 类型标识 */ + slug: z.string(), +}); +export type GameVersionType = z.infer; +// endregion + +// region GameVersion — GET /api/game/versions 响应元素 +export const GameVersionSchema = z.object({ + /** 版本 ID(用于上传时的 gameVersions 字段) */ + id: z.number().int(), + /** 版本类型 ID,对应 GameVersionType.id */ + gameVersionTypeID: z.number().int(), + /** 版本名称,如 "1.21.5" */ + name: z.string(), + /** 版本标识,如 "1-21-5" */ + slug: z.string(), + /** API 版本号,无则为 null */ + apiVersion: z.number().int().nullable(), +}); +export type GameVersion = z.infer; +// endregion diff --git a/src/types/curseforge/index.ts b/src/types/curseforge/index.ts new file mode 100644 index 0000000..2c65e2a --- /dev/null +++ b/src/types/curseforge/index.ts @@ -0,0 +1,54 @@ +export { + FileStatusSchema, + FileReleaseTypeSchema, + HashAlgoSchema, + FileRelationTypeSchema, + ModLoaderTypeSchema, + FileHashSchema, + SortableGameVersionSchema, + FileDependencySchema, + FileModuleSchema, + CurseForgeFileSchema, + PaginationSchema, + GetModFilesParamsSchema, + GetModFilesResponseSchema, + type FileStatus, + type FileReleaseType, + type HashAlgo, + type FileRelationType, + type ModLoaderType, + type FileHash, + type SortableGameVersion, + type FileDependency, + type FileModule, + type CurseForgeFile, + type Pagination, + type GetModFilesParams, + type GetModFilesResponse, +} from "./files"; + +export { + GameVersionTypeSchema, + GameVersionSchema, + type GameVersionType, + type GameVersion, +} from "./game"; + +export { + UploadReleaseTypeSchema, + ChangelogTypeSchema, + UploadRelationTypeSchema, + RelationProjectSchema, + RelationsSchema, + UploadMetadataSchema, + UpdateMetadataSchema, + UploadResponseSchema, + type UploadReleaseType, + type ChangelogType, + type UploadRelationType, + type RelationProject, + type Relations, + type UploadMetadata, + type UpdateMetadata, + type UploadResponse, +} from "./upload"; diff --git a/src/types/curseforge/upload.ts b/src/types/curseforge/upload.ts new file mode 100644 index 0000000..0a9879c --- /dev/null +++ b/src/types/curseforge/upload.ts @@ -0,0 +1,103 @@ +import { z } from "zod"; + +// region UploadReleaseType — 上传时的发布类型(字符串枚举) +export const UploadReleaseTypeSchema = z.enum([ + "alpha", + "beta", + "release", +]); +export type UploadReleaseType = z.infer; +// endregion + +// region ChangelogType — 更新日志格式 +export const ChangelogTypeSchema = z.enum([ + "text", + "html", + "markdown", +]); +export type ChangelogType = z.infer; +// endregion + +// region UploadRelationType — 上传时的依赖关系类型(字符串枚举) +export const UploadRelationTypeSchema = z.enum([ + "embeddedLibrary", + "incompatible", + "optionalDependency", + "requiredDependency", + "tool", +]); +export type UploadRelationType = z.infer; +// endregion + +// region RelationProject — 关联项目 +export const RelationProjectSchema = z.object({ + /** 关联项目的 slug */ + slug: z.string(), + /** 关联项目的 ID(精确匹配),可选 */ + projectID: z.string().optional(), + /** 关联类型 */ + type: UploadRelationTypeSchema, +}); +export type RelationProject = z.infer; +// endregion + +// region Relations — 关联项目集合 +export const RelationsSchema = z.object({ + projects: z.array(RelationProjectSchema), +}); +export type Relations = z.infer; +// endregion + +// region UploadMetadata — POST /api/projects/{projectId}/upload-file 的 metadata JSON +export const UploadMetadataSchema = z.object({ + /** 更新日志内容,必填 */ + changelog: z.string(), + /** 更新日志格式,默认 "text" */ + changelogType: ChangelogTypeSchema.optional(), + /** 站点上显示的文件友好名称 */ + displayName: z.string().optional(), + /** 父文件 ID */ + parentFileID: z.number().int().optional(), + /** + * 支持的 Game Version ID 列表(通过 Game Versions API 获取)。 + * 若提供 parentFileID 则不支持此字段。 + */ + gameVersions: z.array(z.number().int()).optional(), + /** 发布类型,必填 */ + releaseType: UploadReleaseTypeSchema, + /** 若为 true,审核通过后不会立即发布,可手动选择发布时间 */ + isMarkedForManualRelease: z.boolean().optional(), + /** 关联项目 */ + relations: RelationsSchema.optional(), +}); +export type UploadMetadata = z.infer; +// endregion + +// region UpdateMetadata — POST /api/projects/{projectId}/update-file 的 metadata JSON +export const UpdateMetadataSchema = z.object({ + /** 要更新的文件 ID,必填 */ + fileID: z.number().int(), + + /** 更新日志内容 */ + changelog: z.string().optional(), + /** 更新日志格式 */ + changelogType: ChangelogTypeSchema.optional(), + /** 站点上显示的文件友好名称 */ + displayName: z.string().optional(), + /** 支持的 Game Version ID 列表 */ + gameVersions: z.array(z.number().int()).optional(), + /** 发布类型 */ + releaseType: UploadReleaseTypeSchema.optional(), + /** 关联项目 */ + relations: RelationsSchema.optional(), +}); +export type UpdateMetadata = z.infer; +// endregion + +// region UploadResponse — 上传 / 更新文件的成功响应体 +export const UploadResponseSchema = z.object({ + /** 新建 / 被更新文件的 ID */ + id: z.number().int(), +}); +export type UploadResponse = z.infer; +// endregion diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..296fef4 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,3 @@ +export * from "./modrinth"; +export * from "./curseforge"; +// export * from "./config"; // TODO diff --git a/src/types/modrinth/create-version.ts b/src/types/modrinth/create-version.ts new file mode 100644 index 0000000..1d4bf06 --- /dev/null +++ b/src/types/modrinth/create-version.ts @@ -0,0 +1,49 @@ +import { z } from "zod"; +import { BaseVersionSchema, FileTypeEnumSchema } from "./version"; + +// region CreatableVersion — 创建版本请求中的 data JSON +export const CreatableVersionSchema = BaseVersionSchema.extend({ + /** 目标项目的 ID(8 位 base62) */ + project_id: z.string().length(8), + /** 所有承载文件的 multipart 字段名列表 */ + file_parts: z.array(z.string()), + /** 主文件的 multipart 字段名;不提供时推断第一个文件为主文件 */ + primary_file: z.string().optional(), + /** 目标环境 */ + environment: z + .enum([ + "unknown", + "client_only", + "server_only", + "dedicated_server_only", + "client_and_server", + "server_only_client_optional", + "client_only_server_optional", + "client_or_server_prefers_both", + "client_or_server", + "singleplayer_only", + ]) + .optional(), + /** multipart 字段名 → 文件类型的映射,用于标记 sources-jar 等附属文件 */ + file_types: z.record(FileTypeEnumSchema).optional(), +}); +export type CreatableVersion = z.infer; +// endregion + +// region CreateVersionBody — multipart/form-data 请求的最外层包装 +export const CreateVersionBodySchema = z.object({ + /** JSON 字符串形式的 CreatableVersion 元数据 */ + data: CreatableVersionSchema, +}); +export type CreateVersionBody = z.infer; +// endregion + +/** + * POST /version 的实际请求体为 multipart/form-data,结构如下: + * + * - `data`(文本字段):JSON 字符串 → 解析为 {@link CreatableVersion} + * - 若干个文件字段:字段名需与 `file_parts` / `primary_file` / `file_types` 中的值一致 + * + * 因此 Zod schema 仅校验 `data` 字段的结构。 + * 文件字段的校验(大小、格式等)需在运行时单独处理。 + */ diff --git a/src/types/modrinth/index.ts b/src/types/modrinth/index.ts new file mode 100644 index 0000000..fec15ba --- /dev/null +++ b/src/types/modrinth/index.ts @@ -0,0 +1,31 @@ +export { + LoaderTagSchema, + GameVersionTagSchema, + CategoryTagSchema, + type LoaderTag, + type GameVersionTag, + type CategoryTag, + type ProjectTypeTag, +} from "./tags"; + +export { + VersionDependencySchema, + FileTypeEnumSchema, + VersionFileHashesSchema, + VersionFileSchema, + BaseVersionSchema, + VersionSchema, + type VersionDependency, + type FileTypeEnum, + type VersionFileHashes, + type VersionFile, + type BaseVersion, + type Version, +} from "./version"; + +export { + CreatableVersionSchema, + CreateVersionBodySchema, + type CreatableVersion, + type CreateVersionBody, +} from "./create-version"; diff --git a/src/types/modrinth/tags.ts b/src/types/modrinth/tags.ts new file mode 100644 index 0000000..b2677fe --- /dev/null +++ b/src/types/modrinth/tags.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; + +// region /tag/loader — 加载器列表 +export const LoaderTagSchema = z.object({ + /** 加载器的 SVG 图标 */ + icon: z.string(), + /** 加载器名称,如 "fabric"、"forge" */ + name: z.string(), + /** 该加载器适用的项目类型列表 */ + supported_project_types: z.array(z.string()), +}); +export type LoaderTag = z.infer; +// endregion + +// region /tag/game_version — 游戏版本列表 +export const GameVersionTagSchema = z.object({ + /** 游戏版本号,如 "1.18.1" */ + version: z.string(), + /** 版本类型:release | snapshot | alpha | beta */ + version_type: z.enum(["release", "snapshot", "alpha", "beta"]), + /** 版本发布日期(ISO-8601) */ + date: z.string().datetime(), + /** 是否为主要版本,用于 Featured Versions 标记 */ + major: z.boolean(), +}); +export type GameVersionTag = z.infer; +// endregion + +// region /tag/category — 分类标签列表 +export const CategoryTagSchema = z.object({ + /** 分类的 SVG 图标 */ + icon: z.string(), + /** 分类名称 */ + name: z.string(), + /** 该分类适用的项目类型 */ + project_type: z.string(), + /** 该分类所属的标题分组 */ + header: z.string(), +}); +export type CategoryTag = z.infer; +// endregion + +// region /tag/project_type — 项目类型(纯字符串数组) +/** GET /tag/project_type 的响应类型 */ +export type ProjectTypeTag = string[]; +// endregion diff --git a/src/types/modrinth/version.ts b/src/types/modrinth/version.ts new file mode 100644 index 0000000..a52729f --- /dev/null +++ b/src/types/modrinth/version.ts @@ -0,0 +1,100 @@ +import { z } from "zod"; + +// region VersionDependency — 依赖项 +export const VersionDependencySchema = z.object({ + /** 依赖的具体版本 ID;为 null 时匹配最新版 */ + version_id: z.string().nullable(), + /** 依赖的项目 ID */ + project_id: z.string().nullable(), + /** 依赖文件名,主要用于整合包中的外部依赖 */ + file_name: z.string().nullable(), + /** 依赖类型 */ + dependency_type: z.enum(["required", "optional", "incompatible", "embedded"]), +}); +export type VersionDependency = z.infer; +// endregion + +// region FileTypeEnum — 文件类型枚举 +export const FileTypeEnumSchema = z.enum([ + "required-resource-pack", + "optional-resource-pack", + "sources-jar", + "dev-jar", + "javadoc-jar", + "unknown", + "signature", +]); +export type FileTypeEnum = z.infer; +// endregion + +// region VersionFileHashes — 文件哈希 +export const VersionFileHashesSchema = z.object({ + sha512: z.string(), + sha1: z.string(), +}); +export type VersionFileHashes = z.infer; +// endregion + +// region VersionFile — 版本文件 +export const VersionFileSchema = z.object({ + /** 文件哈希,包含 sha512 和 sha1 */ + hashes: VersionFileHashesSchema, + /** 文件直链下载地址 */ + url: z.string().url(), + /** 文件名 */ + filename: z.string(), + /** 是否为主文件。每版本最多一个主文件;若无则为第一个文件 */ + primary: z.boolean(), + /** 文件大小(字节) */ + size: z.number().int().min(0), + /** 文件类型;普通主 jar 为 null */ + file_type: FileTypeEnumSchema.nullable(), +}); +export type VersionFile = z.infer; +// endregion + +// region BaseVersion — 版本基础字段(Version 和 CreatableVersion 的公共基类) +export const BaseVersionSchema = z.object({ + /** 版本名称 */ + name: z.string(), + /** 版本号,建议遵循语义化版本 */ + version_number: z.string(), + /** 更新日志(Markdown/纯文本);使用 include_changelog=false 时为 null */ + changelog: z.string().nullable(), + /** 依赖列表 */ + dependencies: z.array(VersionDependencySchema), + /** 支持的 Minecraft 版本列表 */ + game_versions: z.array(z.string()), + /** 发布渠道 */ + version_type: z.enum(["release", "beta", "alpha"]), + /** 支持的模组加载器列表 */ + loaders: z.array(z.string()), + /** 是否为精选版本 */ + featured: z.boolean(), + /** 版本状态 */ + status: z.enum(["listed", "archived", "draft", "unlisted", "scheduled"]), + /** 请求的状态(用于审核流程) */ + requested_status: z.enum(["listed", "archived", "draft", "unlisted"]).nullable(), +}); +export type BaseVersion = z.infer; +// endregion + +// region Version — 版本完整响应体(GET /version/{id}、GET /project/{id}/version 等返回) +export const VersionSchema = BaseVersionSchema.extend({ + /** 版本唯一 ID(8 位 base62) */ + id: z.string().length(8), + /** 所属项目 ID(8 位 base62) */ + project_id: z.string().length(8), + /** 发布者用户 ID */ + author_id: z.string(), + /** 发布时间(ISO-8601) */ + date_published: z.string().datetime(), + /** 下载次数 */ + downloads: z.number().int().min(0), + /** changelog URL(已废弃,始终为 null) */ + changelog_url: z.string().nullable(), + /** 该版本的所有文件列表 */ + files: z.array(VersionFileSchema), +}); +export type Version = z.infer; +// endregion