ModReleaser/docs/modrinth/upload.md
2026-07-06 04:56:50 +08:00

408 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Modrinth 新建版本 API 使用文档
本文档详细描述如何通过 Modrinth API 创建一个新版本,包括上传模组主文件(`.jar`)和一个 `-sources.jar` 附属文件。
---
## 基本信息
| 项目 | 内容 |
|------|------|
| **端点** | `POST /version` |
| **生产环境** | `https://api.modrinth.com/v2/version` |
| **测试环境** | `https://staging-api.modrinth.com/v2/version` |
| **Content-Type** | `multipart/form-data` |
| **认证** | 必需 — Personal Access Token (PAT) |
| **所需 Scope** | `VERSION_CREATE` |
| **成功响应码** | `200` |
| **成功响应体** | [Version](#响应格式) 对象 |
---
## 认证
所有创建数据的请求都需要认证。你需要一个 **Personal Access Token**PAT在 [Modrinth 用户设置](https://modrinth.com/settings/account) 中生成。
创建版本需要 PAT 具有 `VERSION_CREATE` 权限范围。
将 Token 放在 `Authorization` 请求头中:
```
Authorization: mrp_RNtLRSPmGj2pd1v1ubi52nX7TJJM9sznrmwhAuj511oe4t1jAqAQ3D6Wc8Ic
```
> **注意:** 需要在 `upload.md` 中提供有效的 Token。
同时Modrinth **要求**提供一个可唯一标识的 `User-Agent` 请求头:
```
User-Agent: github_username/project_name/1.0.0 (contact@example.com)
```
---
## 请求格式
请求是一个 **multipart/form-data** 请求,必须包含至少两个表单字段:
- **`data`**:一个 JSON 字符串,包含版本元数据(见下方 [data JSON 结构](#data-json-结构))。
- **一个或多个文件字段**:包含要上传的 `.jar`、`.mrpack`、`.zip` 或 `.litemod` 文件。文件字段名可自定义。
### 重要概念区分multipart 字段名 vs 文件名
在 multipart 请求中,每个文件字段都有**两个不同的名字**
```
-F "main-file=@./build/libs/my-mod-1.0.0.jar"
^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
multipart 字段名 (field name) 文件名 (filename)
```
- **multipart 字段名**(如 `main-file`):在请求体中标识这一部分数据的 key由你自定义。
- **文件名**(如 `my-mod-1.0.0.jar`):被上传文件的原始名称。
> **关键规则:`file_parts`、`primary_file`、`file_types` 的 key 全部使用 multipart 字段名,而不是文件名。**
### `file_parts` — 声明有哪些文件
`data` JSON 的 `file_parts` 数组中列出所有承载文件的 **multipart 字段名**,并在 `primary_file` 中指定哪个是主文件。
```json
{
"file_parts": ["main-file", "sources-file", "dev-file"],
"primary_file": "main-file"
}
```
### `file_types` — 标记附属文件类型
`file_types` 是一个 **对象map****key 是 multipart 字段名**(与 `file_parts` 中的值一致value 是该文件的类型枚举值。用于标记 `-sources.jar`、`-dev.jar` 等附属文件。
可用的文件类型(`FileTypeEnum`
| 值 | 含义 |
|----|------|
| `required-resource-pack` | 必需资源包 |
| `optional-resource-pack` | 可选资源包 |
| `sources-jar` | 源代码 JAR |
| `dev-jar` | 开发版 JAR包含未混淆的类 |
| `javadoc-jar` | Javadoc JAR |
| `unknown` | 未知类型 |
| `signature` | 签名文件 |
**标记 sources.jar 的示例:**
```json
{
"file_types": {
"sources-file": "sources-jar"
}
}
```
> 上例中 `"sources-file"` 是 multipart 字段名,需要在 `file_parts` 中同步出现。
---
## data JSON 结构
`data` 字段的值是一个 JSON 字符串,对应 `CreatableVersion` schema它扩展了 `BaseVersion`
### 顶层结构(`CreateVersionBody`
```json
{
"data": "{ ... CreatableVersion JSON ... }"
}
```
### CreatableVersion 字段一览
#### 必填字段
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `project_id` | `string` | 目标项目的 ID8 位 base62 | `"AABBCCDD"` |
| `file_parts` | `string[]` | 所有承载文件的 **multipart 字段名** 列表 | `["main-file", "sources-file"]` |
| `name` | `string` | 版本名称 | `"Version 1.0.0"` |
| `version_number` | `string` | 版本号,建议遵循语义化版本 | `"1.0.0"` |
| `game_versions` | `string[]` | 支持的 Minecraft 版本列表 | `["1.20.1", "1.20.4"]` |
| `version_type` | `string` | 发布渠道:`release`、`beta`、`alpha` | `"release"` |
| `loaders` | `string[]` | 支持的模组加载器列表 | `["fabric", "forge"]` |
| `featured` | `boolean` | 是否为精选版本 | `false` |
| `dependencies` | `object[]` | 依赖列表(可为空数组 `[]` | 见下方 [依赖结构](#依赖结构) |
#### 可选字段
| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `primary_file` | `string` | 主文件的 **multipart 字段名**。不提供时推断第一个文件为主文件 | `"main-file"` |
| `changelog` | `string \| null` | 更新日志Markdown/纯文本) | `"修复了若干 Bug"` |
| `status` | `string` | 版本状态:`listed`、`archived`、`draft`、`unlisted`、`scheduled`、`unknown`。默认为 `listed` | `"listed"` |
| `requested_status` | `string \| null` | 请求的状态(用于审核流程):`listed`、`archived`、`draft`、`unlisted` | `"listed"` |
| `environment` | `string` | 目标环境,见下方枚举 | `"client_and_server"` |
| `file_types` | `object` | **multipart 字段名** → 文件类型的映射 | `{"sources-file": "sources-jar"}` |
#### `environment` 枚举值
所有值来自 Modrinth 前端源码 [environments.ts](https://github.com/modrinth/code/blob/main/packages/ui/src/components/project/settings/environment/environments.ts)。以下按前端 UI 的层级结构排列,只有 **Server-side only****Client and server** 是带子选项的大类,其余为单选:
| 大类(前端 UI | 子项标题 | API 值 | 说明 |
|----------------|---------|--------|------|
| *(无分类,单选)* | Unknown environment | `unknown` | 未指定或无法确定环境 |
| Client-side only | — | `client_only` | 所有功能在客户端运行,兼容原版服务端 |
| **Server-side only** | Works in singleplayer too | `server_only` | 所有功能在服务端运行,兼容原版客户端;也支持单人模式的内置服务端 |
| | Dedicated server only | `dedicated_server_only` | 所有功能在服务端运行,兼容原版客户端;仅在专用服务器上工作 |
| **Client and server** | Required on both | `client_and_server` | 客户端和服务端都必须安装 |
| | Optional on client | `server_only_client_optional` | 主要为服务端功能,客户端安装可增强体验 |
| | Optional on server | `client_only_server_optional` | 主要为客户端功能,服务端安装可增强体验 |
| | Optional on both, works best when installed on both sides | `client_or_server_prefers_both` | 双方都装体验最佳 |
| | Optional on both, works the same if installed on either side | `client_or_server` | 单独安装任一侧效果相同 |
| Singleplayer only | — | `singleplayer_only` | 仅在单人模式或未连接多人服务器时可用 |
### 依赖结构
`dependencies` 数组中每个依赖对象至少需要 `dependency_type` 字段:
| 字段 | 类型 | 必填 | 说明 | 示例 |
|------|------|------|------|------|
| `dependency_type` | `string` | ✅ | 依赖类型:`required`、`optional`、`incompatible`、`embedded` | `"required"` |
| `project_id` | `string \| null` | ❌ | 依赖的项目 ID | `"P7dR8mSH"` (Fabric API) |
| `version_id` | `string \| null` | ❌ | 依赖的具体版本 ID`null` 时匹配最新版 | `"IIJJKKLL"` |
| `file_name` | `string \| null` | ❌ | 依赖文件名,主要用于整合包中的外部依赖 | `"fabric-api-0.92.0.jar"` |
> **注意:** `project_id` 和 `version_id` 二者至少提供一个,否则依赖关系无法解析。
**Fabric API 依赖示例:**
```json
{
"dependency_type": "required",
"project_id": "P7dR8mSH"
}
```
---
## 完整请求示例
以下是一个使用 cURL 上传一个主 jar 和一个 sources.jar 的完整请求。
### 准备
假设:
- 项目 ID 为 `AABBCCDD`
- 主文件为 `my-mod-1.0.0.jar`
- 源代码文件为 `my-mod-1.0.0-sources.jar`
### cURL 命令
```bash
curl -X POST "https://api.modrinth.com/v2/version" \
-H "Authorization: YOUR_PAT_TOKEN" \
-H "User-Agent: your_username/your_project/1.0.0" \
-F "data={
\"project_id\": \"AABBCCDD\",
\"file_parts\": [\"main-file\", \"sources-file\"],
\"primary_file\": \"main-file\",
\"name\": \"Version 1.0.0\",
\"version_number\": \"1.0.0\",
\"changelog\": \"## 更新内容\n\n- 新增了某某功能\n- 修复了某某 Bug\",
\"dependencies\": [
{
\"dependency_type\": \"required\",
\"project_id\": \"P7dR8mSH\"
}
],
\"game_versions\": [\"1.20.1\", \"1.20.4\"],
\"version_type\": \"release\",
\"loaders\": [\"fabric\"],
\"featured\": false,
\"status\": \"listed\",
\"environment\": \"client_and_server\",
\"file_types\": {
\"sources-file\": \"sources-jar\"
}
}" \
-F "main-file=@./build/libs/my-mod-1.0.0.jar" \
-F "sources-file=@./build/libs/my-mod-1.0.0-sources.jar"
```
### Node.js (fetch) 示例
```js
const fs = require("fs");
const TOKEN = "YOUR_PAT_TOKEN";
const PROJECT_ID = "AABBCCDD";
const form = new FormData();
const metadata = {
project_id: PROJECT_ID,
file_parts: ["main-file", "sources-file"],
primary_file: "main-file",
name: "Version 1.0.0",
version_number: "1.0.0",
changelog: "## 更新内容\n\n- 新增了某某功能\n- 修复了某某 Bug",
dependencies: [
{ dependency_type: "required", project_id: "P7dR8mSH" }
],
game_versions: ["1.20.1", "1.20.4"],
version_type: "release",
loaders: ["fabric"],
featured: false,
status: "listed",
environment: "client_and_server",
file_types: {
"sources-file": "sources-jar"
}
};
form.append("data", JSON.stringify(metadata));
form.append("main-file", new Blob([fs.readFileSync("./build/libs/my-mod-1.0.0.jar")]), "my-mod-1.0.0.jar");
form.append("sources-file", new Blob([fs.readFileSync("./build/libs/my-mod-1.0.0-sources.jar")]), "my-mod-1.0.0-sources.jar");
const response = await fetch("https://api.modrinth.com/v2/version", {
method: "POST",
headers: {
"Authorization": TOKEN,
"User-Agent": "your_username/your_project/1.0.0"
},
body: form
});
const result = await response.json();
console.log(result);
```
---
## 响应格式
成功创建后返回一个 [Version](#version-对象) 对象HTTP 状态码 `200`
### Version 对象
```json
{
"id": "IIJJKKLL",
"project_id": "AABBCCDD",
"author_id": "EEFFGGHH",
"name": "Version 1.0.0",
"version_number": "1.0.0",
"changelog": "## 更新内容\n\n- 新增了某某功能\n- 修复了某某 Bug",
"date_published": "2025-01-01T00:00:00Z",
"downloads": 0,
"version_type": "release",
"status": "listed",
"requested_status": null,
"game_versions": ["1.20.1", "1.20.4"],
"loaders": ["fabric"],
"featured": false,
"files": [
{
"hashes": {
"sha512": "93ecf5fe...",
"sha1": "c84dd4b3..."
},
"url": "https://cdn.modrinth.com/data/AABBCCDD/versions/1.0.0/my-mod-1.0.0.jar",
"filename": "my-mod-1.0.0.jar",
"primary": true,
"size": 1097270,
"file_type": null
},
{
"hashes": {
"sha512": "ab12cd34...",
"sha1": "ef56gh78..."
},
"url": "https://cdn.modrinth.com/data/AABBCCDD/versions/1.0.0/my-mod-1.0.0-sources.jar",
"filename": "my-mod-1.0.0-sources.jar",
"primary": false,
"size": 543210,
"file_type": "sources-jar"
}
]
}
```
响应中 `files` 数组的每个文件对象包含:
| 字段 | 说明 |
|------|------|
| `hashes` | 文件哈希,包含 `sha512``sha1` |
| `url` | 文件直链 |
| `filename` | 文件名 |
| `primary` | 是否为主文件 |
| `size` | 文件大小(字节) |
| `file_type` | 文件类型(对于 sources.jar 为 `"sources-jar"` |
---
## 错误码
| 状态码 | 错误类型 | 说明 |
|--------|----------|------|
| `400` | `InvalidInputError` | 请求参数无效,详见响应中的 `description`。常见原因:缺少必填字段、`game_versions` 或 `loaders` 值不合法、文件格式不被接受 |
| `401` | `AuthError` | Token 无效、未提供 Token或 Token 不包含 `VERSION_CREATE` 权限范围 |
| `404` | — | 目标项目 `project_id` 不存在 |
---
## 注意事项与最佳实践
### 1. 必须至少上传一个文件
除非版本 `status` 设置为 `draft`,否则每个新版本必须附带至少一个文件。接受的格式:`.mrpack`、`.jar`、`.zip`、`.litemod`。
### 2. `file_parts` / `file_types` / `primary_file` 全部使用 multipart 字段名
这三处字符串和实际表单字段名必须**严格匹配**,且它们指向的都是 **multipart 字段名**(如 `"main-file"`),不是文件名(如 `"my-mod-1.0.0.jar"`)。否则上传的文件不会被正确关联。
### 3. sources.jar 的正确做法
- 给 sources 文件随意命名 multipart 字段名(如 `"sources-file"`),将其列入 `file_parts`
-`file_types` 中映射该 **multipart 字段名**`"sources-jar"`
- **不要**将 sources 文件设为主文件(`primary_file` 应指向主 jar 的 multipart 字段名)。
### 4. `dependencies` 可以是空数组
如果该模组没有依赖(极少数情况),直接传 `[]` 即可。但 `dependencies` 字段本身是必填的。
### 5. 文件大小限制
Modrinth 对上传文件有大小限制。如果文件过大,可能收到 `400` 错误。建议保持在合理范围内(一般单个文件不超过 100MB
### 6. 版本号建议
`version_number` 理想上应遵循[语义化版本规范](https://semver.org/lang/zh-CN/),便于 Modrinth 进行版本比较和用户理解。
### 7. 草稿版本
如果你想先上传版本而不立即公开,可将 `status` 设为 `draft`。草稿版本可以不附带文件。后续可通过 PATCH `/version/{id}` 修改状态为 `listed` 来发布。
### 8. 已有版本追加文件
如果版本已创建但需要追加文件,应使用 `POST /version/{id}/file` 端点(需要 `VERSION_WRITE` 权限范围)。
### 9. 环境与加载器的校验
`loaders``game_versions` 必须使用 Modrinth 认可的值。你可以通过以下端口查询可用值:
- `GET /tag/loader` — 可用加载器
- `GET /tag/game_version` — 可用 Minecraft 版本
### 10. Rate Limit
Modrinth API 有频率限制:每 IP 每分钟最多 **300** 个请求。响应头中包含 `X-Ratelimit-Limit`、`X-Ratelimit-Remaining`、`X-Ratelimit-Reset`。
---
## 参考
- [Modrinth API 文档](https://docs.modrinth.com)
- [Modrinth 创建 PAT](https://modrinth.com/settings/account)
- [OpenAPI 规范文件](openapi.yml)
- [Modrinth Labrinth 源码](https://github.com/modrinth/labrinth)
- [语义化版本规范](https://semver.org)