# Nanami API 文档

所有公开接口均无需认证，供客户端软件直接调用。

基础 URL: `https://nui.rucky.cn`

---

# 一、插件市场 API

供启动器拉取插件列表、详情、版本和下载。

---

## 1.1 获取插件列表

### 请求

```
GET /api/addons
```

### 参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| category | string | 否 | 按分类筛选（如 `ui`, `quest`, `combat`, `general`） |
| search | string | 否 | 按名称或简介模糊搜索 |
| published | string | 否 | 默认 `true` 只返回已发布插件，传 `false` 返回全部 |

### 响应示例

```json
[
  {
    "id": "cxxx001",
    "name": "Nanami",
    "slug": "nanami",
    "summary": "一站式乌龟服插件管理器",
    "description": "详细描述...",
    "iconUrl": "/uploads/nanami-icon.png",
    "category": "general",
    "published": true,
    "totalDownloads": 1234,
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-03-18T10:00:00.000Z",
    "releases": [
      {
        "id": "rel001",
        "version": "1.0.0",
        "isLatest": true
      }
    ],
    "_count": {
      "releases": 3
    }
  }
]
```

### 响应字段说明

| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 插件唯一 ID |
| name | string | 插件名称 |
| slug | string | 插件标识（URL 友好） |
| summary | string | 简短描述 |
| description | string | 详细描述（支持长文本） |
| iconUrl | string\|null | 图标地址 |
| category | string | 分类标签 |
| published | boolean | 是否已发布 |
| totalDownloads | int | 累计下载次数 |
| releases | array | 最新版本信息（仅包含 isLatest=true 的版本） |
| _count.releases | int | 版本总数 |

---

## 1.2 获取插件详情

通过 ID 或 slug 获取单个插件的完整信息，包含所有版本和截图。

### 请求

```
GET /api/addons/{id_or_slug}
```

### 参数

| 参数 | 说明 |
|---|---|
| id_or_slug | 插件 ID 或 slug，如 `nanami` |

### 响应示例

```json
{
  "id": "cxxx001",
  "name": "Nanami",
  "slug": "nanami",
  "summary": "一站式乌龟服插件管理器",
  "description": "详细的 Markdown 描述...",
  "iconUrl": "/uploads/nanami-icon.png",
  "category": "general",
  "published": true,
  "totalDownloads": 1234,
  "createdAt": "2026-01-01T00:00:00.000Z",
  "updatedAt": "2026-03-18T10:00:00.000Z",
  "releases": [
    {
      "id": "rel003",
      "addonId": "cxxx001",
      "version": "1.2.0",
      "changelog": "- 新增XX功能\n- 修复YY问题",
      "downloadType": "local",
      "filePath": "uploads/nanami-1.2.0.zip",
      "externalUrl": null,
      "gameVersion": "1.12.1",
      "downloadCount": 500,
      "isLatest": true,
      "createdAt": "2026-03-18T10:00:00.000Z"
    },
    {
      "id": "rel002",
      "addonId": "cxxx001",
      "version": "1.1.0",
      "changelog": "- 修复界面问题",
      "downloadType": "local",
      "filePath": "uploads/nanami-1.1.0.zip",
      "externalUrl": null,
      "gameVersion": "1.12.1",
      "downloadCount": 300,
      "isLatest": false,
      "createdAt": "2026-02-15T10:00:00.000Z"
    }
  ],
  "screenshots": [
    {
      "id": "ss001",
      "addonId": "cxxx001",
      "imageUrl": "/uploads/screenshot1.png",
      "sortOrder": 0
    }
  ]
}
```

### 错误响应

| HTTP 状态码 | 说明 |
|---|---|
| 404 | 插件不存在 |

---

## 1.3 获取版本列表

获取所有版本，可按插件 ID 筛选。

### 请求

```
GET /api/releases?addonId={addonId}
```

### 参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| addonId | string | 否 | 按插件 ID 筛选 |

### 响应示例

```json
[
  {
    "id": "rel003",
    "addonId": "cxxx001",
    "version": "1.2.0",
    "changelog": "- 新增XX功能",
    "downloadType": "local",
    "filePath": "uploads/nanami-1.2.0.zip",
    "externalUrl": null,
    "gameVersion": "1.12.1",
    "downloadCount": 500,
    "isLatest": true,
    "createdAt": "2026-03-18T10:00:00.000Z",
    "addon": {
      "id": "cxxx001",
      "name": "Nanami",
      "slug": "nanami"
    }
  }
]
```

### 版本字段说明

| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 版本唯一 ID |
| addonId | string | 所属插件 ID |
| version | string | 版本号（如 "1.2.0"） |
| changelog | string | 更新日志 |
| downloadType | string | `local` 本地文件 / `url` 外部链接 |
| gameVersion | string | 适配的游戏版本（如 "1.12.1"） |
| downloadCount | int | 下载次数 |
| isLatest | boolean | 是否为最新版本 |
| addon | object | 所属插件基本信息 |

---

## 1.4 下载插件

通过版本 ID 下载对应的插件压缩包。

### 请求

```
GET /api/download/{releaseId}
```

### 行为

- **本地文件**：返回文件二进制流，`Content-Disposition` 设为 `attachment`
- **外部链接**：302 重定向到外部下载地址
- 每次调用自动递增版本下载计数和插件总下载计数

### 响应头（本地文件时）

```
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="nanami-1.2.0.zip"
Content-Length: 1048576
```

### 错误响应

| HTTP 状态码 | 说明 |
|---|---|
| 404 | 版本不存在或文件丢失 |

---

# 二、软件更新 API

供启动器检查自身版本更新和热更新。

---

## 软件标识（slug）

| slug | 用途 | 文件类型 |
|---|---|---|
| `nanami-launcher` | 启动器全量安装包 | Nanami-Launcher-x.x.x.exe |
| `nanami-launcher-patch` | 热更新补丁包 | app.asar |

---

## 2.1 检查更新

客户端启动时调用此接口，传入当前版本号以检测是否有新版本可用。

### 请求

```
GET /api/software/check-update?slug={slug}&versionCode={versionCode}
```

### 参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| slug | string | 是 | 软件标识 |
| versionCode | int | 否 | 当前客户端的版本代码（整数），不传默认为 0 |

### 响应示例

**有更新：**

```json
{
  "hasUpdate": true,
  "forceUpdate": false,
  "latest": {
    "version": "1.2.0",
    "versionCode": 120,
    "changelog": "- 新增XX功能\n- 修复YY问题",
    "downloadUrl": "https://nui.rucky.cn/api/software/download/cxxx123",
    "fileSize": 5242880,
    "minVersion": "1.0.0",
    "createdAt": "2026-03-18T10:00:00.000Z"
  }
}
```

**无更新：**

```json
{
  "hasUpdate": false,
  "forceUpdate": false,
  "latest": {
    "version": "1.0.0",
    "versionCode": 100,
    "changelog": "...",
    "downloadUrl": "https://nui.rucky.cn/api/software/download/cxxx123",
    "fileSize": 5242880,
    "minVersion": null,
    "createdAt": "2026-03-01T10:00:00.000Z"
  }
}
```

### 响应字段说明

| 字段 | 类型 | 说明 |
|---|---|---|
| hasUpdate | boolean | 是否有新版本 |
| forceUpdate | boolean | 是否强制更新（true 时客户端应阻止用户继续使用旧版） |
| latest.version | string | 最新版本号（如 "1.2.0"） |
| latest.versionCode | int | 最新版本代码（整数，用于比较大小） |
| latest.changelog | string | 更新日志 |
| latest.downloadUrl | string | 下载地址（直接用于下载） |
| latest.fileSize | int | 文件大小（字节） |
| latest.minVersion | string\|null | 最低兼容版本号 |
| latest.createdAt | string | 发布时间 (ISO 8601) |

### 错误响应

| HTTP 状态码 | 说明 |
|---|---|
| 400 | 缺少 slug 参数 |
| 404 | 软件不存在或暂无版本 |

---

## 2.2 下载软件包

通过版本 ID 下载对应的软件安装包。一般从"检查更新"接口返回的 `downloadUrl` 直接获取。

### 请求

```
GET /api/software/download/{versionId}
```

### 行为

- **本地文件**：返回文件二进制流，`Content-Disposition` 设为 `attachment`
- **外部链接**：302 重定向到外部下载地址
- 每次调用自动递增下载计数

### 响应头（本地文件时）

```
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="nanami-launcher-v1.2.0.exe"
Content-Length: 5242880
```

### 错误响应

| HTTP 状态码 | 说明 |
|---|---|
| 404 | 版本不存在或文件丢失 |

---

## 2.3 首页启动器最新版本下载

直接下载 `nanami-launcher` 的最新版本，用于首页下载按钮。

### 请求

```
GET /api/software/latest
```

下载最新全量包（自动递增下载计数）。

```
GET /api/software/latest?info=1
```

仅返回版本信息 JSON，不下载。

---

## 2.4 客户端更新检查流程

Nanami 启动器在启动后应按以下顺序检查更新：

```
客户端启动（当前 versionCode = 76）
│
├── 步骤 1: 检查全量更新
│   GET /api/software/check-update?slug=nanami-launcher&versionCode=76
│   │
│   ├── forceUpdate=true → 弹出强制更新对话框，下载完整安装包
│   │                       不再检查热更新
│   │
│   └── hasUpdate=true, forceUpdate=false → 显示"新版本"徽章（用户自行决定）
│       │
│       └── 继续步骤 2
│
├── 步骤 2: 检查热更新
│   GET /api/software/check-update?slug=nanami-launcher-patch&versionCode=76
│   │
│   ├── hasUpdate=true → 静默下载 app.asar，替换本地文件
│   │                     显示"就绪"徽章，提示重启生效
│   │
│   └── hasUpdate=false → 当前已是最新，无需操作
│
└── 正常运行
```

### 版本号对应关系示例

| 发布类型 | 版本号 | versionCode | slug |
|---|---|---|---|
| 全量安装包 | 0.7.6 | 76 | nanami-launcher |
| 热更新补丁 | 0.7.7 | 77 | nanami-launcher-patch |
| 全量大版本 | 0.8.0 | 80 | nanami-launcher |

### 后台操作

- **发布全量版本**：在「Nanami 启动器（全量包）」中上传 `.exe` 文件
- **发布热更新**：在「Nanami 热更新包」中上传 `app.asar` 文件
- 两者的 `versionCode` 应保持递增关系，以便客户端正确比较

---

---

# 三、客户端集成示例（Electron）

## 3.1 启动器自身更新

```javascript
const BASE_URL = "https://nui.rucky.cn/api/software/check-update";
const CURRENT_VERSION_CODE = 76;

async function checkUpdates() {
  // 步骤 1: 检查全量更新
  const fullResp = await fetch(
    `${BASE_URL}?slug=nanami-launcher&versionCode=${CURRENT_VERSION_CODE}`
  );
  const fullData = await fullResp.json();

  if (fullData.hasUpdate && fullData.forceUpdate) {
    showForceUpdateDialog(fullData.latest);
    return;
  }

  if (fullData.hasUpdate) {
    showUpdateBadge(fullData.latest);
  }

  // 步骤 2: 检查热更新
  const patchResp = await fetch(
    `${BASE_URL}?slug=nanami-launcher-patch&versionCode=${CURRENT_VERSION_CODE}`
  );
  const patchData = await patchResp.json();

  if (patchData.hasUpdate) {
    const asarResp = await fetch(patchData.latest.downloadUrl);
    const buffer = await asarResp.arrayBuffer();
    // 写入本地 app.asar 路径...
    showRestartBadge();
  }
}
```

## 3.2 插件市场拉取

```javascript
const API_BASE = "https://nui.rucky.cn";

// 获取全部已发布插件
async function fetchAddons(category?: string, search?: string) {
  const params = new URLSearchParams();
  if (category) params.set("category", category);
  if (search) params.set("search", search);

  const resp = await fetch(`${API_BASE}/api/addons?${params}`);
  return resp.json();
}

// 获取单个插件详情（含所有版本和截图）
async function fetchAddonDetail(slug: string) {
  const resp = await fetch(`${API_BASE}/api/addons/${slug}`);
  return resp.json();
}

// 下载插件最新版本
async function downloadAddon(addon: any) {
  const latestRelease = addon.releases?.find((r: any) => r.isLatest);
  if (!latestRelease) return;

  const downloadUrl = `${API_BASE}/api/download/${latestRelease.id}`;
  const resp = await fetch(downloadUrl);
  const buffer = await resp.arrayBuffer();
  // 保存到 WoW 插件目录 Interface/AddOns/...
}

// 检查插件是否有更新（对比本地已安装版本）
async function checkAddonUpdates(installedAddons: { slug: string; version: string }[]) {
  const addons = await fetchAddons();
  const updates = [];

  for (const installed of installedAddons) {
    const remote = addons.find((a: any) => a.slug === installed.slug);
    if (!remote) continue;

    const latestRelease = remote.releases?.[0];
    if (latestRelease && latestRelease.version !== installed.version) {
      updates.push({
        addon: remote,
        currentVersion: installed.version,
        newVersion: latestRelease.version,
      });
    }
  }

  return updates;
}
```
