# WebSocket 事件推送 第三方软件可通过 WebSocket 实时获取房间内容、房间码与运行状态,无需轮询。1.4.2_4 起还支持第三方服务发送指令(查询 / 重启 / 关闭 / 开启 / 加入房间)。 ## 两个端点与鉴权差异 | 端点 | 面向对象 | 鉴权方式 | |---|---|---| | `ws://<服务器地址>:<端口>/api/ws` | 浏览器面板 | 与 REST API 一致:浏览器自动携带登录 Cookie;程序内调用可使用 Basic Auth(`ws://admin:password@host:port/api/ws`,或使用带鉴权头的 WebSocket 客户端) | | `ws://<服务器地址>:<端口>/api/ws/service?key=<密钥>&name=<服务名称>&type=<服务类型>` | AstrBOT 等第三方插件 / 服务 | 系统随机密钥 `ws_key`,不走面板账号鉴权 | 连接建立后**立即收到一条当前快照**(`event: "snapshot"`);之后每次状态变动都会推送。 ### 第三方服务端点说明 - 密钥在服务器启动时自动生成(也可在 `config.toml` 的 `[astral_server]` 中手动指定 `ws_key`),可在面板「设置 → 第三方服务」查看与复制。 - 手动**刷新密钥**后,旧密钥立即失效,所有已连接服务会被断开(面板可实时看到在线服务列表)。 - `name` / `type` 由连接方上报(如 `name=MyBot&type=AstrBOT`),服务器记录**初次连接时间**与远端地址。 - 连接成功后先收到一条 `event: "hello"`(含连接 ID、名称、类型、初次连接时间),随后收到当前快照,之后与面板端一样实时接收事件。 ## 事件类型 | event | 触发时机 | room 字段 | |---|---|---| | `snapshot` | 连接建立后立即推送 | 当前房间或 `null` | | `server.started` | 服务启动完成(重启后自动恢复房间) | 当前房间或 `null` | | `room.created` | 服务器创建新房间 | 新房间 | | `room.joined` | 服务器加入目标房间 | 目标房间 | | `room.left` | 服务器离开房间 | `null` | | `audit` | 产生操作记录(登录 / 登出 / 修改操作)时推送(1.5.1_1 起) | 无(见下方 `entry` 字段) | ### `audit` 事件(1.5.1_1 起) 面板端与第三方服务端点都会收到。格式: ```json { "event": "audit", "timestamp": 1792059565, "entry": {"ts": 1792059565, "kind": "login", "action": "登录成功", "ip": "192.168.1.10"} } ``` - `entry.kind`:`login` / `login_failed` / `logout` / `modify` / `modify_denied`(未授权的非 GET 请求尝试)。 - `entry.ip`:操作者 IP(反向代理 `X-Forwarded-For` / `X-Real-IP` 头优先,否则取 TCP 源地址)。 - 第三方 WS 控制指令的 `action` 带「第三方服务指令:」前缀。 ## 消息格式 ```json { "event": "room.created", "timestamp": 1728038400, "running": true, "room": { "is_host": true, "game_id": "minecraft", "game_name": "我的世界", "network_name": "ag_ab12cd34ef", "network_secret": "xxxxxxxxxxxxxxxx", "display_name": "我的世界", "short_code": "ABC123", "offline_invite": "AG1.xxxxx…", "peers": [{ "uri": "tcp://game.example.com:11010" }] }, "share_url": "https://next.astral.fan/j?c=ABC123", "node": { "my_ipv4": "10.126.0.2", "total_nodes": 3, "error": null, "server_version": "1.5.1_1", "easytier_version": "1.x.x" } } ``` 字段说明: - `event`:事件类型(见上表)。 - `timestamp`:事件发生时间(Unix 秒)。 - `running`:节点实例是否运行中。 - `room`:当前房间对象,离开房间后为 `null`。包含 `is_host`、`game_id`、`game_name`、`network_name`、`network_secret`、`display_name`、`short_code`、`offline_invite`(`AG1.` 离线邀请串)、`peers`(中继服务器列表)。 - `share_url`:房间邀请链接。 - `node`:节点运行信息,含 `my_ipv4`(本机虚拟 IP)、`total_nodes`(在线节点数)、`error`、`server_version`、`easytier_version`。 ## 调用示例 **浏览器 / 原生 JavaScript** ```javascript const proto = location.protocol === 'https:' ? 'wss' : 'ws'; const ws = new WebSocket(`${proto}://${location.host}/api/ws`); ws.onmessage = (e) => { const msg = JSON.parse(e.data); if (msg.room) { console.log('房间码:', msg.room.short_code); console.log('邀请链接:', msg.share_url); } }; ``` **Node.js**(使用 `ws` 库,Basic Auth) ```javascript const WebSocket = require('ws'); const ws = new WebSocket('ws://192.168.1.10:1786/api/ws', { headers: { Authorization: 'Basic ' + Buffer.from('admin:password').toString('base64') } }); ws.on('message', (data) => { const msg = JSON.parse(data.toString()); console.log(`[${msg.event}]`, msg.room?.short_code ?? '无房间'); }); ``` **Python**(`websockets` 库) ```python import asyncio, json, base64 import websockets async def listen(): auth = base64.b64encode(b"admin:password").decode() async with websockets.connect( "ws://192.168.1.10:1786/api/ws", additional_headers={"Authorization": f"Basic {auth}"} ) as ws: async for raw in ws: msg = json.loads(raw) room = msg.get("room") if room: print(msg["event"], room.get("short_code"), msg.get("share_url")) asyncio.run(listen()) ``` 如果只需要获取一次当前房间,也可以直接调用 REST: ```bash curl -u admin:password http://<服务器>:1786/api/room/current ``` ## 第三方服务指令协议(1.4.2_4 起) 连接成功后除被动接收事件外,可发送指令并收到应答。**控制类指令**(`restart` / `close` / `open` / `join`)需服务端开启 `service_control_enabled`(面板「设置 → 第三方服务 → 允许服务端控制」,默认关闭);只读指令 `get_room_info` 不受开关限制。 ### 请求 发送 JSON 文本帧,`id` 为可选关联号(应答会原样回显): | action | 说明 | 额外字段 | |---|---|---| | `get_room_info` | 查询房间类型、成员列表(含延迟)、运行时长 | 无 | | `restart` | 用最近一次配置重启实例 | 无 | | `close` | 关闭房间(停止实例并清空当前房间) | 无 | | `open` | 开启房间:带 `game_id` / `game_name` 走建房流程;不带则仅重放上次配置启动 | `game_id`、`game_name`、`display_name`(可选) | | `join` | 加入房间 | `input`(短码 / 完整链接 / AG1. 离线串) | ```json {"id":"r1","action":"get_room_info"} {"id":"r4","action":"open","game_id":"minecraft","game_name":"我的世界"} {"id":"r6","action":"join","input":"ABC123"} ``` ### 应答 所有指令统一返回 `event: "result"`,含 `id`(回显请求关联号)、`ok`(成功与否)、`error`(失败原因,成功为空)与可选 `data`: ```json {"event":"result","id":"r4","ok":true,"data":{"instance_id":"...","room":{...},"share_url":"..."}} {"event":"result","id":"r6","ok":false,"error":"第三方服务控制未开启(请在面板设置 → 第三方服务 中启用)"} ``` ### `get_room_info` 应答 `data` 结构 ```json { "running": true, "uptime_secs": 3600, "room_type": {"game_id":"minecraft","game_name":"我的世界","display_name":"...","is_host":true}, "room": { "...同事件消息格式..." }, "share_url": "https://next.astral.fan/j?c=ABC123", "node": {"my_ipv4":"10.126.0.2","total_nodes":3,"error":null}, "members": [{"peer_id":0,"hostname":"...","ipv4":"...","latency_ms":0,"loss_rate":0,"conn_type":"Local",...}] } ``` - `uptime_secs`:房间运行时长(实例已运行秒数;未运行 / 从未启动为 `null`)。 - `room_type`:房间类型(游戏 ID / 名称 / 展示名 / 是否房主)。 - `members`:完整节点列表,含 EasyTier 实测延迟 `latency_ms` 与丢包率 `loss_rate`。 **Node.js 示例** ```javascript const WebSocket = require('ws'); const ws = new WebSocket('ws://192.168.1.10:1786/api/ws/service?key=你的密钥&name=MyBot&type=AstrBOT'); ws.on('open', () => ws.send(JSON.stringify({ id: 'r1', action: 'get_room_info' }))); ws.on('message', (data) => { const msg = JSON.parse(data.toString()); if (msg.event === 'result') { console.log(msg.id, msg.ok ? JSON.stringify(msg.data) : msg.error); } }); ``` ### 控制开关与权限 - 控制类指令(`restart` / `close` / `open` / `join`)在执行前统一校验 `service_control_enabled`,未开启时返回 `ok: false` 与提示错误。 - 密钥刷新(`rotate`)会使所有已连接服务被踢下线,旧密钥立即失效。 ## 管理接口 以下管理接口需面板 Cookie / Basic Auth: ```bash # 查看当前密钥 curl -u admin:password http://<服务器>:1786/api/ws/key # 刷新密钥(旧密钥立即失效,已连接服务被断开) curl -u admin:password -X POST http://<服务器>:1786/api/ws/key/rotate # 查看在线服务列表 curl -u admin:password http://<服务器>:1786/api/ws/services ``` ## 相关页面 - [REST API 参考](rest-api.md) - [配置参考](configuration.md) - [联机房间操作](rooms.md)