平安志 · 玩家开发文档 · API v1

把你的平安京,放进自己的展柜

这份文档面向首页组件作者与本机工具开发者。个人游戏数据来自读取快照,保存在玩家电脑;NAS 仅分发公共公告和活动资料。

下载 OpenAPI 3.1 契约

本机 HTTP 接口

平安志运行时自动监听 127.0.0.1,端口可能随启动变化。打开「帮助/设置 → 设置 → 本机数据接口」复制地址和令牌。请求使用 Authorization: Bearer <token>;重置令牌会立即使旧令牌失效。首版不开放局域网或互联网访问。只读接口不提供会员购买、兑换、密钥、游戏登录凭据或修改游戏的能力。

curl -H "Authorization: Bearer YOUR_LOCAL_TOKEN" \
  "http://127.0.0.1:YOUR_PORT/v1/guild"
接口 内容 权限
GET /v1/context 档案与权限 基础访问
GET /v1/tasks 原始任务与手动记录 任务收费字段过滤
GET /v1/realm-cards 结界卡状态 realm_cards
GET /v1/guild 寮概况与成员 基础访问
GET /v1/profile-summary 档案摘要 基础访问
GET /v1/activities 本机活动查询 基础访问
GET /v1/announcements 本机公告查询 基础访问

免费用户可调用基础接口;每周任务和结界卡继续使用已有会员权限。HTTP 令牌不替代会员授权。每令牌每分钟 120 次;超限返回 429 和 Retry-After。只有受信任的应用来源允许浏览器跨域访问;其他本机程序可使用 HTTP 客户端。不要把本机令牌放进插件包或分享出去。

{"apiVersion":1,"profileId":null,"capturedAt":null,"fetchedAt":"2026-10-10T00:00:00Z","data":null}

capturedAt 是数据快照时间;fetchedAt 是本次查询时间。空值表示未知,不是零。tasks 返回 records(采集事实)与 manual(手动勾选);二者不能混为游戏已完成状态。manual 按档案、每日日期、周一日期隔离,过期勾选不沿用。realm-cards 返回 own/foster;startedAt、endsAt 是 Unix 秒,预计收益可能为 null。到期仅表示“预计已到期,待读取确认”。寮成员 offlineAt、joinedAt、createTime 也是 Unix 秒;offlineAt=0 表示快照采集时在线,不表示现在在线。

activities/announcements 支持 server=live|test、q、offset、limit(默认 50,最大 100),返回 version、total、items。HTTP 错误格式为 {"error":{"code":"..."}}:UNAUTHORIZED、MEMBERSHIP_REQUIRED、SCOPE_DENIED(SDK)、CONTEXT_CHANGED、RATE_LIMITED、DATA_UNAVAILABLE。409 时清空旧档案结果再重试;429 按 Retry-After 等待。

响应中的 snapshotId 标识当前档案版本;切换档案或版本后丢弃旧响应。tasks 的 states.daily/weekly 分别为 missing(缺少读取)、unknown(时间未知)、current(当前周期)、stale(过期),收费字段不可读时为 restricted。realm-cards 的 own/foster 各自增加 status:unknown、inactive、active、expired;expired 不继续返回旧收益。接口不把手动勾选变成读取事实。每日与每周手动记录沿用软件的本地日历周期。

组件 JavaScript SDK

SDK 由平安志自动注入,无需依赖、构建工具或令牌。data.get 返回与本机 HTTP 相同的响应;宿主检查组件声明权限并代理 HTTP 查询。每组件每分钟最多 30 次宿主调用;倒计时应使用本地时钟,避免每秒取数。

async function render() {
  const response = await yys.data.get('guild');
  document.getElementById('title').textContent =
    response.data?.name ?? '暂无寮信息';
}
const unsubscribe = yys.data.subscribe(render);
render();
// 配置:await yys.config.get()
// 首页皮肤:await yys.theme.get() → { skin: 'scroll' | 'moon' | 'barrier' }
// 查询:await yys.data.get('activities', {server:'live', q:'关键词', limit:50})

subscribe 在游戏档案、数据、配置、皮肤变化时触发,返回取消订阅函数。切换档案过程中旧响应会被拒绝,调用方应处理 CONTEXT_CHANGED。会员失效后停止自定义组件,会员皮肤回退至默认亮色,保存的卡片装修仍生效。组件使用沙箱 iframe;不直接调用 Tauri,不访问主窗口 DOM,不获取主令牌。样式局限于卡片内,禁止外部依赖下载与直接网络请求。用户数据请通过 textContent 绘制。

导入代码组件

会员进入「工作台 → 装修我的庭院 → 导入代码组件」,选择 .yys-widget.json,查看声明的数据范围后导入。可启用、禁用、导出和删除。组件包最多 4 MiB;HTML/CSS/JS 每字段最多 1 MiB。HTML 填写 body 片段,JS 可直接使用 yys SDK,CSS 背景建议透明。

{
  "id": "guild-ranking",
  "name": "寮贡献排行",
  "permissions": [
    "guild"
  ],
  "configFields": [
    {
      "key": "limit",
      "label": "显示人数",
      "type": "number",
      "default": 5
    }
  ],
  "html": "<h3 id=\"title\">寮贡献排行</h3><ul id=\"ranking\"></ul><small id=\"stamp\"></small>",
  "js": "const root=document.getElementById('ranking');async function render(){try{const config=await yys.config.get();const result=await yys.data.get('guild');root.replaceChildren();if(!result.data){document.getElementById('title').textContent='暂无寮信息';return;}document.getElementById('title').textContent=result.data.name;const count=Math.max(1,Math.min(20,Number(config.limit)||5));for(const [index,member] of [...result.data.members].sort((a,b)=>b.weeklyHonor-a.weeklyHonor).slice(0,count).entries()){const row=document.createElement('li');row.textContent=`${index+1}. ${member.name} · ${member.weeklyHonor} 寮勋`;root.append(row);}document.getElementById('stamp').textContent=`快照 ${result.capturedAt??'未知'},非实时状态`;}catch(error){root.textContent=error.message;}}yys.data.subscribe(render);render();",
  "apiVersion": 1,
  "version": "1.0.0",
  "css": "body{margin:0;padding:18px;background:transparent;color:#8d9ea6;font:12px/1.7 sans-serif}h3{color:#a98b67;margin:0 0 12px}ul{padding:0;list-style:none}li{padding:7px 0;border-bottom:1px solid #8d9ea633}small{opacity:.7}strong{color:#a98b67}"
}

id 仅允许英文字母、数字、下划线和连字符,长度 1–100;API 版本为 1。permissions 只能是表中接口的资源名。configFields 支持 text、number、boolean,用户值通过 config.get 读取。相同 id 导入会更新组件;不同布局可保存为 .yys-layout.json。

字段与类型参考

档案摘要沿用 snake_case,其余为 camelCase。不存在、null 或 available=false 表示未知。结界卡和寮时间为 Unix 秒;活动时间为带时区的 ISO 8601。枚举及收费字段见下表。

Error
{
  "type": "object",
  "properties": {
    "error": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string"
        }
      },
      "required": [
        "code"
      ]
    }
  },
  "required": [
    "error"
  ]
}
Envelope
{
  "type": "object",
  "properties": {
    "apiVersion": {
      "const": 1
    },
    "profileId": {
      "type": [
        "string",
        "null"
      ]
    },
    "capturedAt": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "fetchedAt": {
      "type": "string",
      "format": "date-time"
    },
    "data": {},
    "snapshotId": {
      "type": [
        "string",
        "null"
      ]
    }
  },
  "required": [
    "apiVersion",
    "profileId",
    "capturedAt",
    "fetchedAt",
    "data",
    "snapshotId"
  ]
}
Context
{
  "type": "object",
  "properties": {
    "profileId": {
      "type": [
        "string",
        "null"
      ]
    },
    "member": {
      "type": "boolean"
    },
    "permissions": {
      "type": "object",
      "properties": {
        "weekly_tasks": {
          "type": "boolean"
        },
        "realm_cards": {
          "type": "boolean"
        }
      }
    },
    "activityVersion": {
      "type": [
        "string",
        "null"
      ]
    },
    "snapshotId": {
      "type": [
        "string",
        "null"
      ]
    }
  }
}
ManualPeriod
{
  "type": "object",
  "properties": {
    "period": {
      "type": "string",
      "format": "date"
    },
    "ids": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": [
    "period",
    "ids"
  ]
}
RealmStatus
{
  "type": "object",
  "properties": {
    "available": {
      "type": "boolean"
    },
    "active": {
      "type": "boolean"
    },
    "itemId": {
      "type": [
        "integer",
        "null"
      ]
    },
    "startedAt": {
      "type": [
        "number",
        "null"
      ],
      "description": "Unix 秒"
    },
    "endsAt": {
      "type": [
        "number",
        "null"
      ],
      "description": "Unix 秒;到期仅表示快照推算"
    },
    "expectedJade": {
      "type": [
        "number",
        "null"
      ]
    },
    "expectedStamina": {
      "type": [
        "number",
        "null"
      ]
    },
    "status": {
      "type": "string",
      "enum": [
        "unknown",
        "inactive",
        "active",
        "expired"
      ]
    }
  },
  "required": [
    "available"
  ]
}
RealmCards
{
  "type": [
    "object",
    "null"
  ],
  "properties": {
    "own": {
      "$ref": "#/components/schemas/RealmStatus"
    },
    "foster": {
      "$ref": "#/components/schemas/RealmStatus"
    }
  }
}
Tasks
{
  "type": "object",
  "properties": {
    "records": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "schemaVersion": {
          "type": "integer"
        },
        "capturedAt": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "daily": {
          "type": "object",
          "additionalProperties": true
        },
        "weekly": {
          "type": "object",
          "additionalProperties": true
        },
        "realmCardStatus": {
          "$ref": "#/components/schemas/RealmCards"
        }
      }
    },
    "manual": {
      "type": [
        "object",
        "null"
      ],
      "properties": {
        "daily": {
          "$ref": "#/components/schemas/ManualPeriod"
        },
        "weekly": {
          "$ref": "#/components/schemas/ManualPeriod"
        }
      }
    },
    "states": {
      "type": "object",
      "required": [
        "daily",
        "weekly"
      ],
      "properties": {
        "daily": {
          "type": "string",
          "enum": [
            "missing",
            "unknown",
            "current",
            "stale",
            "restricted"
          ]
        },
        "weekly": {
          "type": "string",
          "enum": [
            "missing",
            "unknown",
            "current",
            "stale",
            "restricted"
          ]
        }
      }
    }
  },
  "required": [
    "records",
    "manual",
    "states"
  ]
}
Stage
{
  "type": "object",
  "properties": {
    "title": {
      "type": "string"
    },
    "startsAt": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "endsAt": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    }
  },
  "required": [
    "title"
  ]
}
Announcement
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "server": {
      "enum": [
        "live",
        "test"
      ]
    },
    "title": {
      "type": "string"
    },
    "publishedAt": {
      "type": "string",
      "format": "date-time"
    },
    "body": {
      "type": "string"
    },
    "sourceUrl": {
      "type": "string",
      "format": "uri"
    }
  },
  "required": [
    "id",
    "server",
    "title",
    "publishedAt",
    "body"
  ]
}
Activity
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "server": {
      "enum": [
        "live",
        "test"
      ]
    },
    "title": {
      "type": "string"
    },
    "summary": {
      "type": "string"
    },
    "startsAt": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "endsAt": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "exchangeEndsAt": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "announcementIds": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "stages": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Stage"
      }
    }
  },
  "required": [
    "id",
    "server",
    "title",
    "announcementIds"
  ]
}
Manifest
{
  "type": "object",
  "properties": {
    "schemaVersion": {
      "const": 1
    },
    "version": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "bytes": {
      "type": "integer"
    },
    "publishedAt": {
      "type": "string",
      "format": "date-time"
    },
    "path": {
      "type": "string"
    }
  },
  "required": [
    "schemaVersion",
    "version",
    "sha256",
    "bytes",
    "publishedAt",
    "path"
  ]
}
Packet
{
  "type": "object",
  "properties": {
    "schemaVersion": {
      "const": 1
    },
    "version": {
      "type": "string"
    },
    "activities": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Activity"
      }
    },
    "announcements": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Announcement"
      }
    }
  },
  "required": [
    "schemaVersion",
    "version",
    "activities",
    "announcements"
  ]
}
GuildMember
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "dutyCode": {
      "type": "number"
    },
    "dutyLabel": {
      "type": "string"
    },
    "level": {
      "type": "number"
    },
    "joinedAt": {
      "type": "number"
    },
    "joinedDate": {
      "type": "string"
    },
    "weeklyHonor": {
      "type": "number"
    },
    "weeklyFeats": {
      "type": "number"
    },
    "lastWeeklyFeats": {
      "type": [
        "number",
        "null"
      ]
    },
    "totalFeats": {
      "type": "number"
    },
    "historyDonate": {
      "type": "number"
    },
    "offlineAt": {
      "type": "number"
    },
    "offlineAtLabel": {
      "type": "string"
    },
    "pvpScore": {
      "type": "number"
    },
    "taskDayFinished": {
      "type": [
        "number",
        "null"
      ]
    },
    "taskWeekFinished": {
      "type": [
        "number",
        "null"
      ]
    }
  }
}
Guild
{
  "type": [
    "object",
    "null"
  ],
  "properties": {
    "id": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "level": {
      "type": "number"
    },
    "funds": {
      "type": "number"
    },
    "activeMemberCount": {
      "type": "number"
    },
    "memberCount": {
      "type": "number"
    },
    "serverId": {
      "type": "number"
    },
    "createTime": {
      "type": "number"
    },
    "createDate": {
      "type": "string"
    },
    "badge": {
      "type": "number"
    },
    "pvpScore": {
      "type": "number"
    },
    "leaderName": {
      "type": "string"
    },
    "declare": {
      "type": "string"
    },
    "activeRank": {
      "type": "number"
    },
    "pvpRank": {
      "type": "number"
    },
    "activeScore": {
      "type": "number"
    },
    "construction": {
      "type": "number"
    },
    "insigniaCount": {
      "type": "number"
    },
    "pvpSeason": {
      "type": "number"
    },
    "maxGveDifficulty": {
      "type": "number"
    },
    "creviceDifficulty": {
      "type": "number"
    },
    "creviceDefeatedDifficulty": {
      "type": "number"
    },
    "members": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/GuildMember"
      }
    }
  }
}
ProfileSummary
{
  "type": "object",
  "properties": {
    "file_name": {
      "type": [
        "string",
        "null"
      ]
    },
    "source_kind": {
      "type": [
        "string",
        "null"
      ]
    },
    "completeness": {
      "type": [
        "string",
        "null"
      ]
    },
    "imported_at": {
      "type": [
        "string",
        "null"
      ]
    },
    "guild_name": {
      "type": [
        "string",
        "null"
      ]
    },
    "account_name": {
      "type": [
        "string",
        "null"
      ]
    },
    "server_name": {
      "type": [
        "string",
        "null"
      ]
    },
    "short_id": {
      "type": [
        "string",
        "null"
      ]
    },
    "platform": {
      "type": [
        "string",
        "null"
      ]
    },
    "soul_count": {
      "type": "integer"
    },
    "shikigami_count": {
      "type": "integer"
    },
    "item_count": {
      "type": "integer"
    },
    "realm_card_count": {
      "type": "integer"
    },
    "guild_member_count": {
      "type": "integer"
    },
    "exists": {
      "type": "boolean"
    }
  }
}

独立活动服务与缓存

NAS 的 GET /v1/manifest 返回版本、SHA-256、字节数和包路径;GET /v1/packages/<version>.json 返回不可变数据包。客户端首次获取后保存本机缓存,启动时最多每 24 小时检查一次,也可手动更新。同一版本只下载一次,多个组件共享本机结果。请求支持 ETag/If-None-Match;网络故障和校验失败保留旧缓存。

数据服务与会员服务分开部署。每来源 IP 每分钟 60 次、突发 10 次;全服务每分钟 600 次、并发 8。外部开发者也应缓存资料,不高频轮询。数据包没有会员后台接口或个人游戏数据。

维护者提供原公告,经核对整理为 schemaVersion=1、announcements[]、activities[];公告包含 id、server、title、publishedAt、body 和可选 sourceUrl,活动包含来源公告 ID 和可选时间、阶段。未知时间留空;重复 id 更新当前记录,历史导入保留在 revisions。下载空白导入模板。维护脚本只在管理环境运行:

node server/activity/import.mjs /独立活动数据目录 公告.json --dry-run
node server/activity/import.mjs /独立活动数据目录 公告.json

示例响应与组件代码仅用于说明接口,不代表当前游戏活动或玩家状态。