Files
par/docs/API.md

13 KiB
Raw Blame History

PAR Server 集成开发文档

版本: v1.0 | Base URL: http://<host>:9012

目录


1. 认证鉴权

PSK 签名方式

X-Timestamp:  1782800000
X-Signature:  Base64( HMAC-SHA256(PSK, "METHOD:path:timestamp") )
X-Email:      your@email.com  (身份标识,可传多个)

签名步骤

① 获取服务端时间
   GET /api/v1/time  →  {"timestamp": 1782800000}

② 拼接签名内容
   signContent = "POST:/api/v1/sites/pthome/submit:1782800000"

③ 计算签名
   signature = Base64( HMAC-SHA256(PSK, signContent) )

④ 发送请求
   curl -H "X-Timestamp: 1782800000" \
        -H "X-Signature: oNTk7GIA..." \
        -H "X-Email: mediabot@example.com" \
        ...

注意事项

  • 时间窗口默认 60 秒(可配置)
  • 建议先调 /api/v1/time 获取服务端时间
  • PSK 由管理员在 PAR 管理后台生成和分发
  • X-Email 不参与签名,仅用于审计和身份关联

Bearer Token 方式(管理后台用)

Authorization: Bearer <token>

Token 通过 POST /api/v1/auth/login 获取,24 小时有效。


2. 接口参考

2.1 系统

方法 路径 认证 说明
GET /health 无 健康检查
GET /api/v1/time 无 服务端时间戳,签名前获取避免时钟偏差

GET /api/v1/time

// Response 200
{ "timestamp": 1782800000, "unit": "seconds" }

2.2 认证

方法 路径 认证 说明
POST /api/v1/auth/register 无 注册账户(密码可选)
POST /api/v1/auth/login 无 登录,返回 Bearer token
POST /api/v1/auth/recover PSK PSK 签名恢复账户(自动创建+提权)

POST /api/v1/auth/register

// Request
{ "email": "mediabot@example.com", "password": "optional" }

// Response 200
{
  "code": 200,
  "data": {
    "account": {
      "id": 5,
      "email": "mediabot@example.com",
      "trustLevel": "MEMBER",
      "isActive": true,
      "createdAt": "2026-06-29T14:00:00"
    }
  }
}

💡 携带 X-Medibot-Timestamp + X-Medibot-Signature PSK 签名头可自动提权为 TRUSTED

POST /api/v1/auth/recover

// Request (带 PSK 签名头)
{ "email": "mediabot@example.com" }

// Response 200
{
  "code": 200,
  "data": {
    "account": { "id": 5, "email": "...", "trustLevel": "TRUSTED", ... }
  }
}

2.3 全局清单

GET /api/v1/manifest

支持 If-None-Match 条件请求(ETag = 所有站点+配置哈希的 SHA-256)。 无变化时返回 304 Not Modified,无数据传输。

# 轮询示例
curl -H "If-None-Match: a1b2c3d4..." http://par:9012/api/v1/manifest
# → 304 Not Modified  (内容未变)
// Response 200  ETag: "a1b2c3d4..."
{
  "code": 200,
  "data": {
    "version": "1.0",
    "sites": [
      {
        "siteId": "pthome",
        "names": ["PThome", "PThome镜像"],
        "urls": ["https://pthome.net", "https://pthome.mirror"],
        "siteType": "private",
        "schemaVersion": "1.0.0",
        "status": "active",
        "siteType": "nexusphp",
        "latestVersion": "1.2.0",
        "configHash": "a1b2c3d4...",
        "hasScript": false,
        "updatedAt": "2026-06-29T14:00:00"
      }
    ]
  }
}

2.4 站点

方法 路径 认证 说明
GET /api/v1/sites 无 所有活跃站点
GET /api/v1/sites/{siteId} 无 单个站点详情
POST /api/v1/sites/{siteId}/submit PSK 提交站点收录

POST /api/v1/sites/{siteId}/submit

// Request (多名称/多地址)
{
  "names":       ["PThome", "PThome镜像"],        // 必填
  "urls":        ["https://pthome.net", "https://pthome.mirror"], // 必填
  // 或向后兼容单值: "name": "PThome", "baseUrl": "https://..."
  "description": "PT 资源站",
  "iconUrl":     "https://...",
  "siteType":    "private"
}

// Response 200
{
  "code": 200,
  "data": {
    "id": 1,
    "siteId": "pthome",
    "names": ["PThome", "PThome镜像"],
    "urls": ["https://pthome.net", "https://pthome.mirror"],
    "siteType": "private",
    "schemaVersion": "1.0.0",
    "status": "active",
    "createdAt": "2026-06-29T14:00:00"
  }
}

2.5 配置/适配脚本

方法 路径 认证 说明
GET /api/v1/configs/{siteId} 无 已批准配置版本列表
GET /api/v1/configs/{siteId}/{version} 无 下载指定版本配置
POST /api/v1/configs/{siteId}/submit PSK 提交新配置版本

GET /api/v1/configs/{siteId}

// Response 200
{
  "code": 200,
  "data": [
    {
      "id": 10,
      "siteId": "pthome",
      "version": "1.2.0",
      "configHash": "a1b2c3...",
      "fileSize": 8192,
      "schemaVersion": "1.0.0",
      "status": "approved",
      "isLatest": true,
      "createdAt": "2026-06-29T14:00:00"
    }
  ]
}

GET /api/v1/configs/{siteId}/{version}

支持 If-None-Match 条件请求(ETag = configHash),返回 304 表示内容未变化。

// Response 200 (Content-Type: application/json)
"{\"rules\": [...]}"   // 原始配置 JSON 内容

POST /api/v1/configs/{siteId}/submit

// Request
{
  "version":       "1.3.0",           // 必填
  "config":        "{...json...}",    // 必填:适配脚本 JSON
  "schemaVersion": "1.0.0"
}

// Response 200
{
  "code": 200,
  "data": {
    "id": 11,
    "siteId": "pthome",
    "version": "1.3.0",
    "configHash": "e5f6a7...",
    "status": "pending",
    "createdAt": "2026-06-29T14:00:00"
  }
}

2.6 Groovy 适配脚本

方法 路径 认证 说明
GET /api/v1/configs/{siteId}/script 无 下载最新脚本(推荐)
GET /api/v1/configs/{siteId}/{version}/script 无 下载指定版本脚本

GET /api/v1/configs/{siteId}/script

推荐使用。不需要指定版本号,自动返回该站点可用的最新脚本。

优先级:自定义脚本 > 站点类型默认模板 > 404

# 下载 hdfans 脚本
curl http://par:9012/api/v1/configs/hdfans/script

# 有自定义脚本 → 返回自定义 Groovy
# 无自定义脚本 → 按 siteType 返回默认模板(如 nexusphp 类型)
# siteType 未知 → 404

返回 Content-Type: text/plain;charset=UTF-8。

GET /api/v1/configs/{siteId}/{version}/script

指定版本下载(仅返回该版本的自定义脚本,不回退默认模板)。

集成流程

mediabot 启动
  │
  ▼
GET /api/v1/manifest
  │  { "siteId":"hdfans","siteType":"nexusphp","hasScript":false,... }
  │  { "siteId":"mteam","siteType":"mtorrent","hasScript":true,... }
  │
  ▼
GET /api/v1/configs/{siteId}/script      ← 不传版本号,自动选最优
  │
  ├─ 自定义脚本存在 → 返回自定义 Groovy
  └─ 无自定义 → 按 siteType 返回默认模板(nexusphp/gazelle/unit3d/mtorrent)
  │
  ▼
编译 Groovy 脚本 → 调用 sync(ctx) / search(ctx,params) / sign(ctx)

Manifest 新增字段

{
  "siteId": "mteam",
  "latestVersion": "1",
  "hasScript": true,       // 新增:是否有 Groovy 脚本
  "configHash": "abc...",  // JSON 配置 hash(可能为 null)
  "updatedAt": "2026-07-01T10:00:00"
}

脚本入口

Groovy 脚本定义以下方法,mediabot 按需调用:

方法 参数 返回值 说明
parse(content, url) content=页面内容, url=访问URL Map{pageName,fields,extracted} PAR 校验用(mediabot 不调)
sync(ctx) ctx=PluginContext Map{upload,download,bonus,seeding,...} 同步站点数据
search(ctx, params) ctx=PluginContext, params={keyword,page} List[Map] 搜索种子
sign(ctx) ctx=PluginContext Map{success,message} 签到

PluginContext 由 mediabot 提供,包含 site.auth(认证信息)、siteUrl、httpClient 等。


3. 数据结构

统一响应格式

{
  "code": 200,
  "message": "success",
  "data": { ... },
  "timestamp": 1782800000
}
code 说明
200 成功
400 参数错误
401 认证失败
403 权限不足
404 资源不存在
500 服务器错误

站点 (Site)

字段 类型 说明
id Long 内部 ID
siteId String 站点唯一标识(如 pthome)
names String[] 站点名称列表
urls String[] 站点地址列表
description String 描述
iconUrl String 图标 URL
siteType String private / public
schemaVersion String 当前 Schema 版本
status String active / pending / deprecated
createdAt DateTime 创建时间

配置版本 (SiteConfig)

字段 类型 说明
id Long 内部 ID
siteId String 关联站点
version String 版本号(如 1.2.3)
configHash String 内容 SHA-256(用于 ETag)
fileSize Long 文件大小(字节)
schemaVersion String Schema 版本
status String pending / approved / rejected
isLatest Boolean 是否最新版本

4. 工作流程

4.1 首次接入

① 管理员在 PAR 生成 PSK → 提供给 mediabot

② medibot 用 PSK 注册(自动提权)
   POST /api/v1/auth/register
   Headers: X-Medibot-Timestamp, X-Medibot-Signature
   Body:   {"email": "mediabot@example.com"}

③ 提交站点
   POST /api/v1/sites/pthome/submit  (带 PSK 签名)

④ 提交适配脚本
   POST /api/v1/configs/pthome/submit  (带 PSK 签名)

⑤ 配置经管理员审核后变为 approved

4.2 系统重建恢复

① medibot 检测 401 → 调 POST /api/v1/auth/recover
   (PSK 签名 → 自动重建账户 + TRUSTED)

② 所有旧凭据无需修改,签名方式不变

4.3 配置更新

① GET /api/v1/configs/{siteId} 查看当前版本
② 准备新配置 JSON → POST /api/v1/configs/{siteId}/submit
③ 等待管理员审核通过

5. 代码示例

Python

import hashlib, hmac, base64, time, requests

PSK = "your-psk-from-admin"
BASE = "http://par-server:9012/api/v1"
EMAIL = "mediabot@example.com"

def get_server_time():
    return requests.get(f"{BASE}/time").json()["timestamp"]

def hmac_headers(method, path):
    ts = get_server_time()
    sign_content = f"{method}:{path}:{ts}"
    sig = base64.b64encode(
        hmac.new(PSK.encode(), sign_content.encode(), hashlib.sha256).digest()
    ).decode()
    return {
        "X-Timestamp": str(ts),
        "X-Signature": sig,
        "X-Email": EMAIL
    }

def submit_site(site_id, names, urls, site_type="private"):
    path = f"/api/v1/sites/{site_id}/submit"
    return requests.post(f"{BASE}{path}",
        json={"names": names, "urls": urls, "siteType": site_type},
        headers=hmac_headers("POST", path)
    ).json()

def submit_config(site_id, version, config_json, schema_version="1.0.0"):
    path = f"/api/v1/configs/{site_id}/submit"
    return requests.post(f"{BASE}{path}",
        json={"version": version, "config": config_json, "schemaVersion": schema_version},
        headers=hmac_headers("POST", path)
    ).json()

def list_configs(site_id):
    return requests.get(f"{BASE}/configs/{site_id}").json()

def download_config(site_id, version):
    resp = requests.get(f"{BASE}/configs/{site_id}/{version}")
    if resp.status_code == 304:
        return None  # 未变化
    return resp.json()

# ---- 使用 ----
submit_site("pthome", ["PThome", "PThome镜像"], ["https://pthome.net", "https://pthome.mirror"])
submit_config("pthome", "1.0.0", '{"rules":[...]}')
print(list_configs("pthome"))

JavaScript (Node.js)

const crypto = require('crypto');
const PSK = 'your-psk-from-admin';
const BASE = 'http://par-server:9012/api/v1';
const EMAIL = 'mediabot@example.com';

async function getServerTime() {
  const res = await fetch(`${BASE}/time`);
  return (await res.json()).timestamp;
}

function sign(method, path, timestamp) {
  const content = `${method}:${path}:${timestamp}`;
  const sig = crypto.createHmac('sha256', PSK).update(content).digest('base64');
  return sig;
}

async function hmacHeaders(method, path) {
  const ts = await getServerTime();
  return {
    'X-Timestamp': String(ts),
    'X-Signature': sign(method, path, ts),
    'X-Email': EMAIL
  };
}

async function submitSite(siteId, names, urls, siteType = 'private') {
  const path = `/api/v1/sites/${siteId}/submit`;
  const res = await fetch(`${BASE}${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...await hmacHeaders('POST', path) },
    body: JSON.stringify({ names, urls, siteType })
  });
  return res.json();
}

async function submitConfig(siteId, version, config, schemaVersion = '1.0.0') {
  const path = `/api/v1/configs/${siteId}/submit`;
  const res = await fetch(`${BASE}${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', ...await hmacHeaders('POST', path) },
    body: JSON.stringify({ version, config, schemaVersion })
  });
  return res.json();
}