From bc4a38f5893eacef90678c359c86e9c0058a9ef8 Mon Sep 17 00:00:00 2001 From: qsc Date: Sat, 5 Sep 2026 03:05:34 +0800 Subject: [PATCH] fxdyz --- CHANGELOG.md | 4 ++- README.md | 2 +- core/jx3api_data.py | 43 ++++++++++++++++++++++++++++++ core/webui.py | 22 +++++++++++---- pages/server-management/app.js | 29 ++++++++++++++++++++ pages/server-management/index.html | 19 +++++++++++++ pages/server-management/style.css | 13 +++++++++ 7 files changed, 125 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e0fae..05caa30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### version: 3.4.3(2026-09-05): +接入 JX3API `POST /token/stats` 令牌统计接口,使用插件配置的 `jx3api_token` 查询等级、已用次数、剩余次数和有效状态;WebUI 顶部恢复为四项令牌统计,接口不可用或未配置令牌时显示未获取状态且不影响其他管理数据。 + 重构功能帮助图片:由不规则卡片区块改为统一的“功能分区 + 指令列表 + 说明”三列结构,每条指令行预留默认空白的说明区域,并整体放大分区、功能名、指令及辅助文字;按照当前 `command_map` 校正 18 个分区和 92 个真实触发词,角色记录指令由“聊天”更新为“发言”,同时补入“小药”“骗子”并移除未注册的“副本”指令。 新增基于 WebUI 与 SQLite 的“会话控制”:默认使用“全部会话”模式,也可切换为白名单或黑名单,并维护任意 AstrBot 会话 ID 及备注。访问策略同时作用于插件指令与实时事件推送;空白名单不放行任何会话,空黑名单放行全部会话。 @@ -10,7 +12,7 @@ 插件 WebUI 的区服别名和心法别名页新增“恢复默认”操作;采用页面内可见的二次点击确认,不依赖嵌入页面可能无法显示的浏览器原生确认框。确认后分别读取随插件分发的 `data/server_aliases.json` 和 `data/kungfu.json`,在事务中完整重写对应数据库表并刷新页面数据。 -移除 WebUI 顶部统计区域;“会话控制”移动为第一个默认页签,并新增醒目的当前生效模式状态。模式卡片明确区分当前模式与尚未保存的选择,切换按钮同步显示目标模式。“会话绑定”更名为“区服绑定”并保留在“事件推送”之后。 +移除 WebUI 原有的会话绑定和事件推送数量统计,顶部区域改为显示 JX3API 令牌等级、已用次数、剩余次数和有效状态;“会话控制”移动为第一个默认页签,并新增醒目的当前生效模式状态。模式卡片明确区分当前模式与尚未保存的选择,切换按钮同步显示目标模式。“会话绑定”更名为“区服绑定”并保留在“事件推送”之后。 重构 WebUI 区服绑定交互:顶部入口改为“添加绑定”,会话 ID 可自定义但绑定区服只能从标准区服下拉框选择;列表编辑时会话 ID 保持只读,仅在对应绑定区服列内切换下拉框进行保存,解除绑定改为点击后直接删除对应记录。Web API 同步拒绝别名及任意文本作为绑定区服。 diff --git a/README.md b/README.md index dd94590..23e72e8 100644 --- a/README.md +++ b/README.md @@ -291,7 +291,7 @@ JX3API Token 可从 [JX3API](https://www.jx3api.com/) 获取。推栏 Ticket 通 ### WebUI 插件管理 -AstrBot 插件详情页中的“剑网三插件管理”页面提供五个页签:会话控制、事件推送、区服绑定、区服别名和心法别名,不再显示顶部统计区域。会话控制为默认页签,页面会明确标出当前真正生效的模式,并区分尚未保存的模式选择。该功能默认使用“全部会话”,也可切换为白名单或黑名单;会话 ID 可从已有绑定和订阅记录中选择,也可直接输入,并可维护名单类型和备注。空白名单不放行任何会话,空黑名单放行全部会话,名单策略同时作用于插件指令和实时事件推送。 +AstrBot 插件详情页中的“剑网三插件管理”页面提供五个页签:会话控制、事件推送、区服绑定、区服别名和心法别名。顶部通过 JX3API `POST /token/stats` 展示当前配置令牌的等级、已用次数、剩余次数和有效状态;未配置令牌或接口暂时不可用时显示未获取,不影响其他管理功能。会话控制为默认页签,页面会明确标出当前真正生效的模式,并区分尚未保存的模式选择。该功能默认使用“全部会话”,也可切换为白名单或黑名单;会话 ID 可从已有绑定和订阅记录中选择,也可直接输入,并可维护名单类型和备注。空白名单不放行任何会话,空黑名单放行全部会话,名单策略同时作用于插件指令和实时事件推送。 区服绑定页可使用自定义会话 ID 添加记录,绑定区服只能从标准区服下拉框选择。编辑已有记录时会话 ID 保持只读,只在绑定区服列内修改,点击解除绑定会直接删除该行。事件推送页显示所有会话的总开关及订阅编号。区服别名页展示完整标准区服目录,标准名称只读,只能行内编辑别名;心法别名页隐藏 JX3BOX 配装 ID,标准心法名称只读,同样只允许行内编辑最多 5 个别名。两个别名页均可使用随插件分发的 JSON 种子完整覆盖数据库并恢复默认配置。管理页通过 AstrBot Plugin Pages 桥接调用插件 Web API,不直接访问 Dashboard 凭据。 diff --git a/core/jx3api_data.py b/core/jx3api_data.py index b8cc5f2..845316a 100644 --- a/core/jx3api_data.py +++ b/core/jx3api_data.py @@ -116,6 +116,49 @@ class JX3APIService: } ) + async def token_stats(self) -> Optional[Dict[str, Any]]: + """查询当前配置 JX3API Token 的等级、用量及有效状态。""" + if not str(self.token or "").strip(): + return None + + try: + data = await self._api.post( + "https://www.jx3api.com/token/stats", + data={"token": self.token}, + out_key="data", + success_codes=(200, "200"), + return_error=True, + ) + except Exception as exc: + logger.warning(f"查询 JX3API Token 统计失败: {exc}") + return None + + if isinstance(data, APIErrorResponse): + logger.warning( + f"JX3API Token 统计返回错误: " + f"code={data.code}, msg={data.message or '未知错误'}" + ) + return None + if not isinstance(data, dict): + return None + + def nonnegative_int(value: Any) -> Optional[int]: + if isinstance(value, bool): + return None + try: + number = int(value) + except (TypeError, ValueError): + return None + return number if number >= 0 else None + + valid = data.get("valid") + return { + "level": nonnegative_int(data.get("level")), + "used": nonnegative_int(data.get("used")), + "remaining": nonnegative_int(data.get("remaining")), + "valid": valid if isinstance(valid, bool) else None, + } + def _init_return_data(self) -> Dict[str, Any]: """初始化标准的返回数据结构""" diff --git a/core/webui.py b/core/webui.py index 37865f0..e643932 100644 --- a/core/webui.py +++ b/core/webui.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio import re from typing import TYPE_CHECKING, Any @@ -87,11 +88,21 @@ class WebUIService: raise ValueError("别名必须是字符串或数组") async def dashboard(self): - bindings = await self.server_binding.list_bindings() - subscriptions = await self.event_push.list_subscription_statuses() - aliases = await self.server_binding.list_aliases() - kungfu = await self.kungfu_alias.list_kungfu() - session_control = await self.session_control.get_state() + ( + bindings, + subscriptions, + aliases, + kungfu, + session_control, + token_stats, + ) = await asyncio.gather( + self.server_binding.list_bindings(), + self.event_push.list_subscription_statuses(), + self.server_binding.list_aliases(), + self.kungfu_alias.list_kungfu(), + self.session_control.get_state(), + self.jx3api.token_stats(), + ) return json_response( { "bindings": bindings, @@ -103,6 +114,7 @@ class WebUIService: str(action): name for action, name in EVENT_NAMES.items() }, "session_control": session_control, + "token_stats": token_stats, } ) diff --git a/pages/server-management/app.js b/pages/server-management/app.js index bef6740..a4da9a9 100644 --- a/pages/server-management/app.js +++ b/pages/server-management/app.js @@ -7,6 +7,7 @@ const state = { servers: [], events: {}, session_control: { mode: "all", entries: [] }, + token_stats: null, }; const editing = { bindingSession: null, controlSession: null, aliasServer: null, kungfuPzid: null }; const restoreConfirmationTimers = new WeakMap(); @@ -14,6 +15,33 @@ let toastTimer; const byId = (id) => document.getElementById(id); +function formatUsageCount(value) { + return Number.isSafeInteger(value) && value >= 0 + ? value.toLocaleString("zh-CN") + : "—"; +} + +function renderTokenStats() { + const stats = state.token_stats; + byId("token-level").textContent = Number.isSafeInteger(stats?.level) + ? `LV.${stats.level}` + : "—"; + byId("token-used").textContent = formatUsageCount(stats?.used); + byId("token-remaining").textContent = formatUsageCount(stats?.remaining); + + const status = byId("token-valid"); + status.classList.remove("token-status--valid", "token-status--invalid"); + if (stats?.valid === true) { + status.textContent = "有效"; + status.classList.add("token-status--valid"); + } else if (stats?.valid === false) { + status.textContent = "无效"; + status.classList.add("token-status--invalid"); + } else { + status.textContent = "未获取"; + } +} + function showToast(message, isError = false) { const toast = byId("toast"); toast.textContent = message; @@ -525,6 +553,7 @@ function renderKungfu() { } function render() { + renderTokenStats(); renderServerOptions(); renderSessionOptions(); renderSessionControl(); diff --git a/pages/server-management/index.html b/pages/server-management/index.html index 033b7a1..85fac96 100644 --- a/pages/server-management/index.html +++ b/pages/server-management/index.html @@ -17,6 +17,25 @@ +
+
+ 令牌等级 + +
+
+ 已用次数 + +
+
+ 剩余次数 + +
+
+ 令牌状态 + 未获取 +
+
+