Skip to content

TNXG/skland-api

Repository files navigation

skland-server

基于 ElysiaJS + Bun 的技术研究项目,仅供学习交流使用。

⚠️ 免责声明

  1. 本项目仅为技术研究用途,旨在探索 HTTP 签名认证、OAuth2 授权流程、Base64 数据解码等计算机技术原理。
  2. 本项目为学习研究用途,不用于任何商业目的。禁止将本项目用于盈利或商业服务。
  3. 本项目的所有代码均为独立编写,不包含任何第三方平台的源代码、密钥、证书或机密信息。
  4. 使用者须自行承担全部责任。下载、运行或修改本项目代码即表示您同意:
    • 遵守相关法律法规及第三方平台的服务条款
    • 自行获取必要的授权和许可
    • 不会将本项目用于任何非法目的
    • 不会利用本项目侵犯他人知识产权或隐私权
  5. 本项目作者不对使用者的行为承担任何责任,包括但不限于因使用本项目导致的账号封禁、数据丢失、法律纠纷等后果。
  6. 如任何第三方平台认为本项目侵犯了其权益,请联系项目作者,我们将积极配合处理。
  7. 文档中出现的数字 ID 均为随机生成的示例占位符,不指向任何真实用户或数据。

目录


快速开始

bun install
bun run dev

服务默认监听 http://localhost:3000

游戏数据来自 GameData/ArknightsGameData 目录中的 Kengxxiao/ArknightsGameData Git 子模块。首次克隆后请初始化子模块:

git submodule update --init --recursive

更新游戏数据后,请在主仓库提交新的子模块版本:

git -C GameData/ArknightsGameData pull --ff-only
git add GameData/ArknightsGameData
git commit -m "chore: update ArknightsGameData"

架构概览

flowchart TB
    subgraph Client[客户端]
        Browser["浏览器 / curl"]
    end

    subgraph Server[skland-server :3000]
        Elysia["Elysia HTTP Server"]
        Login["/auth/login<br>/auth/qr<br>/auth/qr-sse"]
        Player["/games/:game/player|card<br>/profile/:profileId"]
        Status["/auth/status<br>/auth/logout"]

        subgraph Core["核心模块"]
            Crypto["lib/crypto<br>签名算法"]
            Skland["lib/skland<br>登录链路"]
            Auth["lib/auth<br>凭证/玩家数据"]
        end

        DB[("bun:sqlite<br>凭证缓存")]
    end

    subgraph Upstream[上游 API]
        HG["OAuth2 授权服务器"]
        Zonai["数据 API 服务器"]
    end

    Browser -->|HTTP| Elysia
    Elysia --> Login
    Elysia --> Player
    Elysia --> Status
    Login --> Skland
    Player --> Auth
    Auth --> Crypto
    Auth --> DB
    Skland --> HG
    Auth --> Zonai
Loading

授权流程

登录授权链路(6 步)

sequenceDiagram
    participant User as 用户
    participant Server as skland-server
    participant HG as OAuth2 服务器
    participant API as 数据 API 服务器
    participant SQLite as bun:sqlite

    User->>Server: GET /auth/qr-sse

    Note over Server,HG: Step 1 — 生成扫码会话
    Server->>HG: POST /gen_scan/login
    HG-->>Server: scanId
    Server-->>User: 显示二维码 (ASCII / PNG)

    Note over Server,HG: Step 2 — 轮询扫码状态 (每 2s,最长 120s)
    loop 直到扫码或超时
        Server->>HG: GET /scan_status?scanId=xxx
        HG-->>Server: 未扫码 → 继续等待
    end
    HG-->>Server: scanCode (扫码成功)

    Note over Server,HG: Step 3 — scanCode → access_token
    Server->>HG: POST /token_by_scan_code
    HG-->>Server: access_token (24位)

    Note over Server,HG: Step 4 — access_token → grant code
    Server->>HG: POST /oauth2/v2/grant
    HG-->>Server: grant code

    Note over Server,API: Step 5 — grant code → CRED
    Server->>API: POST /generate_cred_by_code
    API-->>Server: cred + cred_token

    Note over Server,API: Step 6 — 获取完整用户标识 (签名请求)
    Server->>API: GET /user/teenager
    API-->>Server: userId

    Server->>SQLite: 保存凭证
    Server-->>User: ✓ 登录成功
Loading

OAuth2 授权类型转换关系

flowchart LR
    subgraph 扫码阶段
        Scan["scanId"] -->|轮询| ScanCode["scanCode"]
        ScanCode -->|POST| Token["access_token<br>24位"]
    end

    subgraph 凭证交换
        Token -->|grant_type: 0| Grant0["森空岛 grant code"]
        Token -->|grant_type: 1| Grant1["官网通行证 token<br>+ hgId 信息"]
        Grant0 --> Cred["cred + cred_token"]
    end

    subgraph 会话管理
        Cred --> Sign["HMAC-SHA256 签名"]
        Cred -->|cred_token 过期| Refresh["刷新 cred_token"]
        Cred -->|cred 过期| ReAuth["通过 access_token 重新获取"]
    end
Loading

凭证刷新机制

flowchart TD
    Start["signedGet() 发起签名请求"] --> Call["调用 API"]
    Call --> Code{响应 code?}

    Code -->|"code = 0"| OK["返回 data"]
    Code -->|"code = 10000"| Expired["cred_token 失效"]
    Code -->|"code = 10002"| CredFail["cred 失效"]
    Code -->|其他| Throw["抛出异常"]

    Expired --> Refresh["refreshToken(cred) → 新 cred_token"]
    Refresh --> Save1["更新 SQLite"]
    Save1 --> Retry1["重试请求"]
    Retry1 --> OK

    CredFail --> HasToken{"有 access_token?"}
    HasToken -->|是| NewGrant["getGrantCode → getCred → 新 cred"]
    HasToken -->|否| Rebind["提示用户重新扫码登录"]
    NewGrant --> Save2["更新 SQLite"]
    Save2 --> Retry2["重试请求"]
    Retry2 --> OK
Loading

签名算法

所有需要鉴权的 API 请求均需在 Header 中携带签名,算法如下:

flowchart LR
    subgraph Input[输入参数]
        C["cred<br>32位凭证"]
        CT["credToken<br>HMAC 密钥"]
        U["请求 URL"]
        M["请求方法"]
    end

    subgraph Step[计算步骤]
        direction TB
        S1["1. 构造 header_ca<br>{ platform, timestamp, dId, vName }"]
        S2["2. 提取 query_params<br>GET → URL query<br>POST → JSON body"]
        S3["3. 拼接签名原文<br>path + query_params<br>+ timestamp<br>+ JSON(header_ca)"]
        S4["4. 计算签名<br>HMAC-SHA256(credToken, secret)<br>→ MD5(hex)"]
    end

    subgraph Output[输出请求头]
        O1["{ cred, sign, platform,<br>timestamp, dId, vName }"]
    end

    C --> S1
    CT --> S4
    U --> S2
    M --> S2
    S1 --> S3
    S2 --> S3
    S3 --> S4
    S4 --> O1
    C --> O1
Loading

注:credToken 仅用于本地计算 HMAC,不会出现在任何网络请求中


ID 体系与解析链路

以下 ID 均为示例占位符,不代表真实用户。

用户标识体系

flowchart TB
    subgraph Source[用户可见来源]
        Profile["个人主页 URL<br>profile?id=xxx"]
        App["App 账号设置页"]
    end

    subgraph IDs[身份标识]
        HGID["hgId<br>鹰角通行证 ID<br>✗ 不可用于查询"]
        ProfileID["Profile ID<br>个人主页展示 ID<br>✓ 可解析"]
        UserID["userId<br>森空岛内部 ID<br>仅内部解析使用"]
        GameUID["game UID<br>游戏角色账户 ID"]
        ServerID["serverId<br>游戏区服 ID"]
    end

    subgraph API[解析接口]
        Center["/user/center<br>ID 解析服务"]
        Binding["/player/binding<br>角色绑定查询"]
        Info["/player/info<br>玩家数据查询"]
    end

    App --> HGID
    Profile --> ProfileID
    ProfileID -->|✓| Center
    Center -->|返回 userId| UserID

    HGID -->|✗ 不支持| Fail["解析失败"]

    UserID --> Binding
    Binding --> GameUID
    GameUID --> Info
    Info -->|响应数据| Result["结构化玩家数据"]
Loading

ID 类型对照表

ID 类型 示例(纯虚构) 位数 说明
hgId 1111111111111 13 鹰角通行证 ID
Profile ID 2222222222222 变长 个人主页展示 ID
userId 42 变长 森空岛内部 ID,不作为公开查询参数
game UID 333333333 变长 服务端根据绑定信息解析的游戏角色账户 ID
serverId 1 变长 服务端根据绑定信息解析的游戏区服 ID

查询入口拆分

flowchart TD
    Profile["Profile 路由<br>只传 profileId"] --> Center["user/center<br>解析内部 userId"]
    Center --> Binding["player/binding<br>解析 Game UID 与 serverId"]
    Binding --> Result["获取玩家数据"]

Loading

Profile 路由用于查询公开个人主页,不要求调用方提供区服;服务端会自行解析绑定的游戏角色与区服。


API 接口

方法 路径 说明
GET / 列出所有接口
GET /auth/login 扫码登录入口(返回 JSON 含 base64 二维码)
GET /auth/qr 直接返回二维码 PNG 图片
GET /auth/qr-sse 纯文本流:终端 ASCII 二维码 + 实时状态
GET /auth/status 查看登录状态
POST /auth/logout 退出登录
GET /games/arknights/player/profile/:profileId 通过 Profile ID 查询明日方舟玩家数据
GET /games/arknights/player/profile/:profileId/simple 通过 Profile ID 查询明日方舟玩家摘要
GET /games/arknights/player/profile/:profileId/raw 通过 Profile ID 查询明日方舟原始数据
GET /games/endfield/card/profile/:profileId 通过 Profile ID 查询终末地角色卡片

以上明日方舟路由使用 GameData/ArknightsGameData/zh_CN/gamedata/excel 中的客户端数据,将角色、技能、物品、皮肤、关卡、区域、模组、基建技能和蚀刻章 ID 转换为包含 idnametype 等字段的可读对象。无法可靠识别的值保持原样,原始响应可通过带 /raw 后缀的对应路由获取。

/simple 后缀的明日方舟路由仅返回玩家名称与等级、加入时间、主线进度、干员/时装/家具/蚀刻章数量,以及最多三位助战干员的等级、精英阶段、潜能、技能、模组和皮肤名称。

终末地角色卡片接口会保留上游 detail 的完整结构,可通过以下方式请求:

curl 'http://localhost:3000/games/endfield/card/profile/2222222222222'

上游字段使用内部命名,不能直接按英文键名翻译。项目中的字段语义按游戏内正式中文术语标注:

上游字段 游戏内含义
base 角色基础信息
chars 个人名片展示干员及养成信息
achieve 「光荣之路」蚀刻章
spaceShip 「O.M.V.帝江号」舱室与派驻干员
domain 地区建设与地区探索
dungeon 理智、理智上限及恢复时间,并非“地牢”玩法
bpSystem 「协议通行证」等级
dailyMission / weeklyMission 每日任务活跃度 / 每周任务进度
indieHard 「影拓丰碑」挑战进度
seekSuspicion 「蚀像寻遗」行动刻度
crisisContract 「危机合约」活动进度

/auth/qr-sse 使用示例

curl -N http://localhost:3000/auth/qr-sse

数据库

admin_credentials 表

字段 类型 说明
id INTEGER PK 自增主键
access_token TEXT 扫码获得的临时令牌
cred TEXT 长期登录凭证
cred_token TEXT 签名密钥(不对外暴露)
user_id TEXT 平台用户标识
created_at INTEGER 创建时间
updated_at INTEGER 更新时间
  • 单行设计:始终只存一条管理员凭证
  • SQLite:skland.dbjournal_mode = MEMORY
  • ORM:Drizzle ORM + bun:sqlite

项目结构

skland-server/
├── GameData/
│   └── ArknightsGameData/    # Kengxxiao/ArknightsGameData 子模块,提供 ID 字典
├── index.ts                  # Elysia 入口
├── drizzle.config.ts         # Drizzle Kit 配置
├── package.json
├── skland.db                 # SQLite 凭证缓存
└── src/
    ├── db/
    │   ├── schema.ts          # 表定义
    │   └── index.ts           # 数据库连接
    ├── hg/                    # Hypergryph 平台与游戏业务域
    │   ├── auth.ts            # 森空岛凭证管理
    │   ├── crypto.ts          # HMAC-SHA256 + MD5 签名算法
    │   ├── skland.ts          # 登录链路与平台 API
    │   ├── skland-client.ts   # 签名请求与凭证刷新
    │   ├── routes/            # 平台路由与游戏路由装配
    │   └── games/arknights/   # 明日方舟玩家服务、数据字典与路由
    └── lib/
        └── storage.ts         # 通用凭证文件存储

上游 API 参考

OAuth2 授权服务器

接口 方法 说明
/gen_scan/login POST 生成扫码会话
/scan_status GET 轮询扫码状态
/token_by_scan_code POST scanCode → access_token
/oauth2/v2/grant POST access_token → grant code / hgId

数据 API 服务器

接口 鉴权 说明
/generate_cred_by_code 无需签名 grant code → CRED
/auth/refresh 无需签名 刷新 cred_token
/user/teenager 签名 获取当前用户标识
/user/center 签名 Profile ID → 森空岛内部 userId
/player/binding 签名 userId → 游戏角色绑定列表
/player/info 签名 已绑定游戏角色 → 玩家完整数据(Base64)
/endfield/card/detail 签名 终末地角色完整卡片数据

已知限制

  • hgId 不可用于查询:通行证账户 ID 与数据平台的用户标识体系之间无公开的转换接口
  • 仅支持 Profile ID 查询:服务端会自动解析内部 userId 和游戏角色绑定信息

许可证

本项目代码仅供学习交流使用。使用者须自行评估法律风险并承担全部责任。

About

Skland API server with anonymized examples

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages