解密
+ 查询当前机关解密结果与下轮时间。
+diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7798b4..f3f975c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
## 更新记录
+### version: 2.5:
+
+增加部分新指令,调整功能页面
+
### version: 2.4:
增加大量新指令
diff --git a/README.md b/README.md
index f58a528..65f413c 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,387 @@
-# 剑网三数据查询工具
+# astrbot_plugin_jx3
-## 简介
+基于 [AstrBot](https://docs.astrbot.app/) 框架开发的剑网三游戏数据查询插件。插件主要通过 JX3API、JX3BOX、茶馆等外部接口获取游戏数据,整理为文本消息、富媒体消息,或通过 `HTML + Jinja2` 模板渲染成图片后发送。
-仿照剑网三主流的机器人写了个插件,主要是自用,没有群管理相关的功能,在任何群聊都是使用的一套数据。
+当前插件以“查询型功能”为主,覆盖日常、奇遇、名剑、阵营、交易、职业、排行、副本、活动、名片、百战、推送和本地避雷等场景。部分功能需要配置 JX3API Token,少量职业/角色类接口还需要推栏 ticket。
-插件功能绝大部分都是用AI写的,自己进行了调试。
+## 功能概览
-目前此插件查询功能还没做完,后续会继续完善。
+插件内置 `功能` 指令,会以图片形式展示完整指令列表、参数说明和是否需要令牌。当前帮助页按 16 个功能分组维护,约 87 条指令。
-此插件大部分数据依赖jx3api提供的接口,只有进阶功能需要向其购买token才可使用。其他功能是免token的。
-部分进阶功能除了购买token以外还需要自己去抓包推栏app,获得推栏app的ticket才可正常使用。
+主要功能包括:
-## 功能特点
+- 日常与官方:日常、月历、名望、公告、维护、区服、开服、状态、技改。
+- 职业与角色:角色、阵眼、奇穴、技能、小药、宏、配装、资历排行、试炼排行。
+- 奇遇:角色奇遇、未做奇遇、奇遇统计、近期奇遇、奇遇汇总、奇遇攻略。
+- 名剑:战绩、名剑排行、名剑统计。
+- 阵营:沙盘、阵营拍卖、阵营事件、关隘首领、帮战记录、统战 YY。
+- 交易:金价、外观物价、交易行、贴吧物价。
+- 活动:扶摇、诛恶事件、本日赤兔、本周赤兔、刷马、的卢。
+- 其他:拜师、收徒、名片、百战、精耐、招募、掉落、解密、八卦、骗子查询。
+- 本地功能:避雷添加、查看、查询、修改、删除。
+- 推送功能:开服推送、新闻推送、刷马推送、赤兔推送。
-- **后台循环请求**:通过后台一个任务循环请求接口,记录判断。推送最新消息状态
-- **处理接口数据**:将接口返回的数据处理成,玩家能读懂的消息发送
-- **HTML渲染图片**:利用astrbot里面的文转图服务,处理HTML页面渲染成图片
+## 安装与依赖
+
+将插件目录放入 AstrBot 的插件目录中,例如:
+
+```text
+data/plugins/astrbot_plugin_jx3
+```
+
+安装依赖:
+
+```bash
+pip install -r requirements.txt
+```
+
+当前依赖:
+
+- `aiohttp`:异步 HTTP 请求。
+- `aiofiles`:异步文件处理。
+- `aiosqlite`:本地 SQLite 数据库。
+- `apscheduler`:后台推送任务调度。
+- `matplotlib`:部分图像/数据展示依赖。
+
+插件元信息在 `metadata.yaml` 中维护,要求 AstrBot 版本 `>=4.11.0`。
## 插件配置
-**指令前缀**
+配置结构由 `_conf_schema.json` 定义,主要配置项如下。
-开启后必须要在指令功能前面加上下面配置的前缀才能正常触发。关闭后直接只有指令功能即可。
+### 指令前缀
-**默认服务器**
+```json
+{
+ "prefix": {
+ "enable": true,
+ "text": "剑三"
+ }
+}
+```
-配置后在所有功能指令中需要输入服务器参数的可以忽略不写,将按照配置的内容进行补充,填写官方名称。
+- `enable`:是否启用指令前缀检查。
+- `text`:指令前缀内容,默认 `剑三`。
-**JX3API Token**
+启用后,用户需要发送类似 `剑三 日常`、`剑三 奇遇 角色名` 才能触发。关闭后可直接发送 `日常`、`奇遇 角色名`。
-使用进阶的功能必须配置,购买在https://www.jx3api.com。
+### 默认服务器
-**推栏ticket**
+```json
+{
+ "server": "梦江南"
+}
+```
-在进阶功能中,有部分功能依赖推栏APP的数据,所以需要自己抓包推栏的ticket。
-大致操作下载一个抓包工具,打开推栏APP,做一些查询操作。去抓包工具看看发送的请求,参数里面就有ticket。
+许多指令都带有 `[服务器]` 可选参数。用户未填写服务器时,插件会使用这里配置的默认服务器。
-**推送功能**
+示例:
-配置是否启用,本质就是不停的去接口问数据,数据又更新就推送出来。设定循环请求的间隔时间,不建议太短。下面配置推送的对话列表要要完整的会话ID。例如:QQ:GroupMessage:123456
+```text
+奇遇 飞翔大野猪
+```
+
+等价于:
+
+```text
+奇遇 飞翔大野猪 梦江南
+```
+
+前提是默认服务器配置为 `梦江南`。
+
+### JX3API Token
+
+```json
+{
+ "jx3api_token": ""
+}
+```
+
+需要令牌的功能必须配置该字段。Token 需要在 [JX3API](https://www.jx3api.com/) 注册并购买后获取。
+
+帮助页中标记为“需令牌”的指令,如果未配置 Token,可能无法正常返回数据。
+
+### 推栏 Ticket
+
+```json
+{
+ "jx3api_ticket": ""
+}
+```
+
+部分接口需要推栏 ticket,例如部分职业、角色、学校类接口。该值通常需要通过抓包推栏 App 的请求参数获得。
+
+### 推送配置
+
+插件支持 4 类后台推送:
+
+- `kfts`:开服监控。
+- `xwts`:新闻资讯。
+- `smts`:刷马消息。
+- `ctts`:赤兔消息。
+
+每类推送配置都包含:
+
+- `enable`:是否启用。
+- `time`:轮询间隔,单位秒。
+- `umos`:推送目标会话 ID 列表。
+
+示例:
+
+```json
+{
+ "kfts": {
+ "enable": false,
+ "time": 60,
+ "umos": ["QQ:GroupMessage:123456"]
+ }
+}
+```
+
+`umos` 需要填写完整会话 ID,可通过 AstrBot 的 `/std` 等方式获取。推送本质是后台定时请求接口,检测状态变化后向配置的会话发送消息,不建议将轮询间隔设置得过短。
## 使用方式
-在聊天中输入 **功能** 即可获取插件所有的功能使用说明。
\ No newline at end of file
+发送:
+
+```text
+功能
+```
+
+即可获取完整指令帮助图。
+
+如果开启了指令前缀,则发送:
+
+```text
+剑三 功能
+```
+
+指令参数说明:
+
+- `[]` 表示可选参数。
+- `[服务器]` 未填写时使用插件默认服务器。
+- 数量、天数、模式等参数未填写时使用对应功能的默认值。
+
+示例:
+
+```text
+日常
+奇遇 飞翔大野猪
+未做奇遇 飞翔大野猪 梦江南
+战绩 飞翔大野猪 梦江南 33
+贴吧物价 狐金 5 梦江南
+统战 梦江南
+```
+
+## 目录结构
+
+```text
+astrbot_plugin_jx3/
+├─ main.py # AstrBot 插件入口,初始化配置、资源、服务和指令分发
+├─ metadata.yaml # 插件元信息
+├─ _conf_schema.json # AstrBot 后台配置 schema
+├─ requirements.txt # Python 依赖
+├─ data/
+│ ├─ api_config.json # 外部接口配置,按功能 key 维护 URL、method、默认参数
+│ └─ plugin_data.db # 插件随包数据,主要用于内置数据
+├─ core/
+│ ├─ jx3_data.py # 业务数据层,调用接口、整理数据、选择模板
+│ ├─ message.py # 消息构建层,负责文本、图片、富媒体回复
+│ ├─ request.py # aiohttp 请求封装,统一处理 GET/POST 和接口返回
+│ ├─ async_task.py # APScheduler 后台推送任务
+│ ├─ bilei_data.py # 本地避雷数据增删改查
+│ ├─ sqlite.py # SQLite 异步封装
+│ └─ fun_basic.py # 模板加载、图片 base64、通用格式化工具
+└─ templates/
+ ├─ helps.html # 功能帮助页
+ ├─ *.html # 各图片指令的 HTML/Jinja2 模板
+ ├─ img/ # 通用图片资源
+ ├─ sect/ # 门派/心法图标
+ └─ serendipity/ # 奇遇图标
+```
+
+## 核心实现逻辑
+
+### 1. 插件初始化
+
+`main.py` 中的 `Jx3ApiPlugin` 是插件入口。
+
+初始化时会完成以下工作:
+
+1. 读取 AstrBot 插件配置,包括前缀、默认服务器、Token、Ticket 和推送配置。
+2. 计算本地数据目录、插件数据目录、模板目录和接口配置文件路径。
+3. 加载 `templates/img`、`templates/sect`、`templates/serendipity` 中的图片,并转为 base64,供 HTML 模板直接引用。
+4. 初始化 SQLite、JX3Service、AsyncTask、BiLeidata、MessageBuilder。
+5. 在异步初始化阶段创建本地表,并启动后台推送任务。
+6. 构建 `command_map`,将中文触发词映射到对应的消息处理函数。
+
+### 2. 指令分发
+
+用户消息进入插件后,`main.py` 会:
+
+1. 根据配置判断是否需要指令前缀。
+2. 将消息按空格切分为 `指令 + 参数`。
+3. 在 `command_map` 中查找对应处理函数。
+4. 使用 `inspect.signature` 根据函数参数数量自动传入用户参数。
+5. 找不到指令时不处理,参数错误时返回提示。
+
+### 3. 消息构建
+
+`core/message.py` 的 `MessageBuilder` 负责把业务数据发出去,主要有几类输出:
+
+- `plain_msg()`:发送纯文本。
+- `T2I_image_msg()`:把业务数据传入 HTML 模板,调用 AstrBot 的 HTML 渲染能力生成图片。
+- `image_msg()`:直接发送图片 URL 或图片数据。
+- `plain_chain()`:发送文本 + 图片等富媒体消息链。
+- `handler_plain_image_msg()`:先发文本,再发图片,适用于部分组合型功能。
+
+所有可选服务器参数都会通过 `serverdefault()` 补齐默认服务器。
+
+### 4. 数据请求与业务处理
+
+`core/jx3_data.py` 是主要业务层。每个功能通常对应一个异步方法,例如:
+
+```python
+async def jinqiqiyu(self, server: str) -> Dict[str, Any]:
+ ...
+```
+
+业务方法通常遵循统一结构:
+
+1. 调用 `_init_return_data()` 创建标准返回对象。
+2. 构造接口参数,填入 `server`、`name`、`token`、`ticket` 等。
+3. 通过 `_base_request(config_key, method, params)` 读取 `data/api_config.json` 中的接口配置并发起请求。
+4. 判断接口返回是否为空或结构异常。
+5. 整理时间戳、字段名、分组、列表、统计值等模板需要的数据。
+6. 文本功能直接写入 `return_data["data"]`。
+7. 图片功能额外加载对应 HTML 模板并写入 `return_data["temp"]`。
+8. 成功时设置 `return_data["code"] = 200`。
+
+标准返回结构大致为:
+
+```python
+{
+ "code": 0,
+ "msg": "错误提示",
+ "data": {},
+ "temp": "",
+ "icons": {}
+}
+```
+
+### 5. 接口请求封装
+
+`core/request.py` 中的 `APIClient` 负责实际 HTTP 请求。
+
+实现特点:
+
+- 复用 `aiohttp.ClientSession`。
+- 支持 GET 和 POST。
+- 自动识别 JSON、图片或二进制响应。
+- 对 JX3API 常见返回结构做基础校验。
+- 支持通过 `out_key` 抽取返回对象中的指定字段,默认通常取 `data`。
+
+`JX3Service._base_request()` 会在业务层进一步统一:
+
+- 根据 `data/api_config.json` 查找接口。
+- 合并接口默认参数和运行时参数。
+- 根据配置 method 调用 GET 或 POST。
+- 捕获异常并输出日志。
+
+### 6. 图片渲染
+
+图片输出功能使用 `templates/*.html`。
+
+典型数据流:
+
+```text
+JX3Service 整理数据
+ ↓
+MessageBuilder.T2I_image_msg()
+ ↓
+注入 icons
+ ↓
+html_renderer.render_custom_template()
+ ↓
+发送图片
+```
+
+模板里可以使用:
+
+- `icons.img`:通用图标。
+- `icons.sect`:门派/心法图标。
+- `icons.serendipity`:奇遇图标。
+
+例如门派图标常见写法:
+
+```jinja2
+{% set icon = icons.sect.get(item.forceName) %}
+{% if icon %}
+
+{% endif %}
+```
+
+### 7. 本地数据库
+
+插件使用两个 SQLite 数据库:
+
+- 插件目录下的 `data/plugin_data.db`:随插件提供的内置数据。
+- AstrBot 数据目录中的 `local_data.db`:运行期本地数据。
+
+本地数据主要用于:
+
+- 避雷记录。
+- 推送任务状态缓存。
+
+`core/sqlite.py` 封装了异步增删改查;`core/bilei_data.py` 基于该封装实现避雷数据管理。
+
+### 8. 后台推送
+
+`core/async_task.py` 使用 APScheduler 实现轮询推送。
+
+当前支持:
+
+- 开服监控。
+- 新闻资讯。
+- 刷马消息。
+- 赤兔消息。
+
+推送逻辑是:
+
+1. 初始化时读取配置和本地旧状态。
+2. 对启用且配置了推送目标的任务创建 interval job。
+3. 定时调用对应业务方法。
+4. 比较新旧状态。
+5. 状态变化时向配置会话发送消息,并更新本地状态。
+
+## 新增功能开发流程
+
+新增一个查询功能通常需要改动 4 到 5 个位置:
+
+1. 在 `data/api_config.json` 添加接口配置。
+2. 在 `core/jx3_data.py` 添加业务方法,完成请求、数据整理和错误处理。
+3. 在 `core/message.py` 添加消息包装方法,选择文本、图片或富媒体输出。
+4. 在 `main.py` 的 `command_map` 注册中文触发指令。
+5. 如果是图片输出,新增 `templates/xxx.html`。
+6. 在 `templates/helps.html` 增加帮助卡片并更新统计。
+
+建议保持以下约定:
+
+- 方法名使用拼音或稳定英文名,避免与现有方法冲突。
+- 接口配置 key 使用 `jx3_功能名` 形式。
+- 文本功能失败时返回清晰的 `msg`。
+- 图片功能返回空数据时不要渲染空图,直接返回文本提示。
+- 可选服务器统一通过 `MessageBuilder.serverdefault()` 处理。
+- 时间戳尽量在业务层格式化后传给模板。
+
+## 注意事项
+
+- 本插件依赖多个外部接口,接口变更、网络异常、Token 权限不足都会影响结果。
+- JX3API 数据源可能存在延迟或遗漏,奇遇、掉落、排行榜等数据不保证 100% 完整。
+- Token 与 Ticket 属于敏感配置,不要提交到仓库或公开日志。
+- 后台推送会持续请求接口,轮询间隔不要设置过短。
+- HTML 模板渲染依赖 AstrBot 的文转图能力,运行环境需要支持对应渲染服务。
+
+## License
+
+本项目遵循仓库内 `LICENSE` 文件声明的许可协议。
diff --git a/core/jx3_data.py b/core/jx3_data.py
index b544272..5b19866 100644
--- a/core/jx3_data.py
+++ b/core/jx3_data.py
@@ -971,6 +971,62 @@ class JX3Service:
return return_data
+ async def shilianpaixing(self, name: str, server: str) -> Dict[str, Any]:
+ """试炼排行"""
+ return_data = self._init_return_data()
+
+ params = {
+ "server": server,
+ "name": name,
+ "token": self.token,
+ }
+
+ data: Optional[Dict[str, Any]] = await self._base_request(
+ "jx3_shilianpaixing", "GET", params=params
+ )
+
+ if not data or not isinstance(data, dict):
+ return_data["msg"] = "未查询到试炼排行信息"
+ return return_data
+
+ try:
+ items = data.get("data", [])
+ if not isinstance(items, list) or not items:
+ return_data["msg"] = "未查询到试炼排行信息"
+ return return_data
+
+ update_time = data.get("time")
+ if update_time:
+ try:
+ update_time = datetime.fromtimestamp(int(update_time)).strftime("%Y-%m-%d %H:%M:%S")
+ except (TypeError, ValueError, OSError):
+ update_time = str(update_time)
+ else:
+ update_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+
+ return_data["data"] = {
+ "items": items,
+ "name": data.get("name", name),
+ "server": data.get("server", server),
+ "update_time": update_time
+ }
+ except Exception as e:
+ logger.error(f"处理试炼排行数据失败: {e}")
+ return_data["msg"] = "处理试炼排行数据失败"
+ return return_data
+
+ try:
+ return_data["temp"] = await load_template("shilianpaixing.html")
+ except FileNotFoundError as e:
+ logger.error(f"加载模板失败: {e}")
+ return_data["msg"] = "系统错误:模板文件不存在"
+ return return_data
+
+ return_data["code"] = 200
+
+ return return_data
+
+
async def shaohua(self) -> Dict[str, Any]:
"""骚话"""
return_data = self._init_return_data()
@@ -994,6 +1050,47 @@ class JX3Service:
return return_data
+ async def jiemi(self) -> Dict[str, Any]:
+ """解密"""
+ return_data = self._init_return_data()
+
+ params = {"token": self.token}
+
+ data: Optional[Dict[str, Any]] = await self._base_request(
+ "jx3_jiemi", "GET", params=params
+ )
+
+ if not data or not isinstance(data, dict):
+ return_data["msg"] = "未查询到解密信息"
+ return return_data
+
+ try:
+ curr = data.get("curr", {})
+ next_data = data.get("next", {})
+ if not isinstance(curr, dict):
+ curr = {}
+ if not isinstance(next_data, dict):
+ next_data = {}
+
+ return_data["data"] = "\n".join([
+ "解密",
+ f"当前时间:{data.get('time', '')}",
+ f"当前节点:{curr.get('node', '')}",
+ f"当前结果:{curr.get('data', '')}",
+ f"下轮节点:{next_data.get('node', '')}",
+ f"下轮结果:{next_data.get('data', '')}",
+ f"剩余时间:{data.get('cdtn', '')}",
+ ])
+ except Exception as e:
+ logger.error(f"处理解密数据失败: {e}")
+ return_data["msg"] = "处理解密数据失败"
+ return return_data
+
+ return_data["code"] = 200
+
+ return return_data
+
+
async def shapan(self, server: str ) -> Dict[str, Any]:
"""区服沙盘"""
return_data = self._init_return_data()
@@ -1098,6 +1195,182 @@ class JX3Service:
return return_data
+ async def zhueevent(self) -> Dict[str, Any]:
+ """诛恶事件"""
+ return_data = self._init_return_data()
+
+ params = {"token": self.token}
+
+ data: Optional[List[Dict[str, Any]]] = await self._base_request(
+ "jx3_zhueevent", "GET", params=params
+ )
+
+ if not data or not isinstance(data, list):
+ return_data["msg"] = "未查询到诛恶事件信息"
+ return return_data
+
+ try:
+ items = []
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+
+ event_time = item.get("time")
+ if event_time:
+ try:
+ item["time"] = datetime.fromtimestamp(int(event_time)).strftime("%Y-%m-%d %H:%M:%S")
+ except (TypeError, ValueError, OSError):
+ item["time"] = str(event_time)
+ else:
+ item["time"] = ""
+
+ items.append(item)
+
+ if not items:
+ return_data["msg"] = "未查询到诛恶事件信息"
+ return return_data
+
+ return_data["data"] = {
+ "items": items,
+ "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ }
+ except Exception as e:
+ logger.error(f"处理诛恶事件数据失败: {e}")
+ return_data["msg"] = "处理诛恶事件数据失败"
+ return return_data
+
+ try:
+ return_data["temp"] = await load_template("zhueevent.html")
+ except FileNotFoundError as e:
+ logger.error(f"加载模板失败: {e}")
+ return_data["msg"] = "系统错误:模板文件不存在"
+ return return_data
+
+ return_data["code"] = 200
+
+ return return_data
+
+
+ async def benrichitu(self) -> Dict[str, Any]:
+ """本日赤兔"""
+ return_data = self._init_return_data()
+
+ params = {"token": self.token}
+
+ data: Optional[List[Dict[str, Any]]] = await self._base_request(
+ "jx3_benrichitu", "GET", params=params
+ )
+
+ def no_data() -> Dict[str, Any]:
+ return_data["code"] = 200
+ return_data["data"] = "本日赤兔 暂无数据"
+ return return_data
+
+ if not data or not isinstance(data, list):
+ return no_data()
+
+ try:
+ items = []
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+
+ date_value = item.get("date", "")
+ if date_value:
+ try:
+ date_text = datetime.fromtimestamp(int(date_value)).strftime("%Y-%m-%d %H:%M:%S")
+ except (TypeError, ValueError, OSError):
+ date_text = str(date_value)
+ else:
+ date_text = ""
+
+ items.append({
+ "date": date_text,
+ "server": item.get("server", ""),
+ "map_name": item.get("map_name", ""),
+ })
+
+ if not items:
+ return no_data()
+
+ result_lines = ["本日赤兔"]
+ for item in items:
+ result_lines.extend([
+ f"时间:{item['date']}",
+ f"区服:{item['server']}",
+ f"地图:{item['map_name']}",
+ "",
+ ])
+
+ return_data["code"] = 200
+ return_data["data"] = "\n".join(result_lines).rstrip()
+ except Exception as e:
+ logger.error(f"处理本日赤兔数据失败: {e}")
+ return no_data()
+
+ return return_data
+
+
+ async def benzhouchitu(self) -> Dict[str, Any]:
+ """本周赤兔"""
+ return_data = self._init_return_data()
+
+ params = {"token": self.token}
+
+ data: Optional[List[Dict[str, Any]]] = await self._base_request(
+ "jx3_benzhouchitu", "GET", params=params
+ )
+
+ def no_data() -> Dict[str, Any]:
+ return_data["code"] = 200
+ return_data["data"] = "本周赤兔 暂无数据"
+ return return_data
+
+ if not data or not isinstance(data, list):
+ return no_data()
+
+ try:
+ items = []
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+
+ date_value = item.get("date", "")
+ if date_value:
+ try:
+ date_text = datetime.fromtimestamp(int(date_value)).strftime("%Y-%m-%d %H:%M:%S")
+ except (TypeError, ValueError, OSError):
+ date_text = str(date_value)
+ else:
+ date_text = ""
+
+ items.append({
+ "date": date_text,
+ "server": item.get("server", ""),
+ "map_name": item.get("map_name", ""),
+ })
+
+ if not items:
+ return no_data()
+
+ result_lines = ["本周赤兔"]
+ for item in items:
+ result_lines.extend([
+ f"时间:{item['date']}",
+ f"区服:{item['server']}",
+ f"地图:{item['map_name']}",
+ "",
+ ])
+
+ return_data["code"] = 200
+ return_data["data"] = "\n".join(result_lines).rstrip()
+ except Exception as e:
+ logger.error(f"处理本周赤兔数据失败: {e}")
+ return no_data()
+
+ return return_data
+
+
async def zhengyingpaimai(self, server: str, name: str) -> Dict[str, Any]:
"""阵营拍卖"""
return_data = self._init_return_data()
@@ -1138,6 +1411,299 @@ class JX3Service:
return return_data
+ async def zhenyingevent(self) -> Dict[str, Any]:
+ """阵营事件"""
+ return_data = self._init_return_data()
+
+ params = {"token": self.token}
+
+ data: Optional[List[Dict[str, Any]]] = await self._base_request(
+ "jx3_zhenyingevent", "GET", params=params
+ )
+
+ if not data or not isinstance(data, list):
+ return_data["msg"] = "未查询到阵营事件信息"
+ return return_data
+
+ try:
+ items = []
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+
+ seize_time = item.get("seize_time")
+ if seize_time:
+ try:
+ item["seize_time"] = datetime.fromtimestamp(int(seize_time)).strftime("%Y-%m-%d %H:%M:%S")
+ except (TypeError, ValueError, OSError):
+ item["seize_time"] = str(seize_time)
+ else:
+ item["seize_time"] = ""
+
+ items.append(item)
+
+ if not items:
+ return_data["msg"] = "未查询到阵营事件信息"
+ return return_data
+
+ return_data["data"] = {
+ "items": items,
+ "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ }
+ except Exception as e:
+ logger.error(f"处理阵营事件数据失败: {e}")
+ return_data["msg"] = "处理阵营事件数据失败"
+ return return_data
+
+ try:
+ return_data["temp"] = await load_template("zhenyingevent.html")
+ except FileNotFoundError as e:
+ logger.error(f"加载模板失败: {e}")
+ return_data["msg"] = "系统错误:模板文件不存在"
+ return return_data
+
+ return_data["code"] = 200
+
+ return return_data
+
+
+ async def guanaishouling(self) -> Dict[str, Any]:
+ """关隘首领"""
+ return_data = self._init_return_data()
+
+ params = {"token": self.token}
+
+ data: Optional[List[Dict[str, Any]]] = await self._base_request(
+ "jx3_guanaishouling", "GET", params=params
+ )
+
+ if not data or not isinstance(data, list):
+ return_data["msg"] = "未查询到关隘首领信息"
+ return return_data
+
+ try:
+ now_ts = int(datetime.now().timestamp())
+
+ def format_time(value: Any) -> str:
+ if not value:
+ return ""
+ try:
+ return datetime.fromtimestamp(int(value)).strftime("%Y-%m-%d %H:%M:%S")
+ except (TypeError, ValueError, OSError):
+ return str(value)
+
+ def format_remaining(end_time: Any) -> str:
+ if not end_time:
+ return ""
+ try:
+ seconds = max(0, int(end_time) - now_ts)
+ except (TypeError, ValueError):
+ return str(end_time)
+
+ hours = seconds // 3600
+ minutes = (seconds % 3600) // 60
+ secs = seconds % 60
+ return f"{hours}时{minutes:02d}分{secs:02d}秒"
+
+ groups = []
+ for group in data:
+ if not isinstance(group, dict):
+ continue
+
+ records = group.get("data", [])
+ if not isinstance(records, list) or not records:
+ continue
+
+ parsed_records = []
+ for item in records:
+ if not isinstance(item, dict):
+ continue
+
+ end_time = item.get("end_time")
+ parsed_records.append({
+ "camp_name": item.get("camp_name", ""),
+ "castle": item.get("castle", ""),
+ "str_status": item.get("str_status", ""),
+ "start_time": format_time(item.get("start_time")),
+ "end_time": format_time(end_time),
+ "remaining_time": format_remaining(end_time),
+ })
+
+ if parsed_records:
+ groups.append({
+ "server": group.get("server", ""),
+ "records": parsed_records,
+ })
+
+ if not groups:
+ return_data["msg"] = "未查询到关隘首领信息"
+ return return_data
+
+ return_data["data"] = {
+ "groups": groups,
+ "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ }
+ except Exception as e:
+ logger.error(f"处理关隘首领数据失败: {e}")
+ return_data["msg"] = "处理关隘首领数据失败"
+ return return_data
+
+ try:
+ return_data["temp"] = await load_template("guanaishouling.html")
+ except FileNotFoundError as e:
+ logger.error(f"加载模板失败: {e}")
+ return_data["msg"] = "系统错误:模板文件不存在"
+ return return_data
+
+ return_data["code"] = 200
+
+ return return_data
+
+
+ async def bangzhanjilu(self, server: str) -> Dict[str, Any]:
+ """帮战记录"""
+ return_data = self._init_return_data()
+
+ params = {
+ "server": server,
+ "token": self.token,
+ }
+
+ data: Optional[List[Dict[str, Any]]] = await self._base_request(
+ "jx3_bangzhanjilu", "GET", params=params
+ )
+
+ if not data or not isinstance(data, list):
+ return_data["msg"] = "未查询到帮战记录"
+ return return_data
+
+ try:
+ def format_time(value: Any) -> str:
+ if not value:
+ return ""
+ try:
+ return datetime.fromtimestamp(int(value)).strftime("%Y-%m-%d %H:%M:%S")
+ except (TypeError, ValueError, OSError):
+ return str(value)
+
+ def format_duration(value: Any) -> str:
+ if value in (None, ""):
+ return ""
+ try:
+ seconds = max(0, int(value))
+ except (TypeError, ValueError):
+ return str(value)
+
+ hours = seconds // 3600
+ minutes = (seconds % 3600) // 60
+ secs = seconds % 60
+ return f"{hours}时{minutes:02d}分{secs:02d}秒"
+
+ items = []
+ for item in data:
+ if not isinstance(item, dict):
+ continue
+
+ items.append({
+ "zoneName": item.get("zoneName", ""),
+ "serverName": item.get("serverName", ""),
+ "declaringTongName": item.get("declaringTongName", ""),
+ "acceptingTongName": item.get("acceptingTongName", ""),
+ "startTime": format_time(item.get("startTime")),
+ "matchDuration": format_duration(item.get("matchDuration")),
+ "endTime": format_time(item.get("endTime")),
+ })
+
+ if not items:
+ return_data["msg"] = "未查询到帮战记录"
+ return return_data
+
+ return_data["data"] = {
+ "items": items,
+ "server": server,
+ "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+ }
+ except Exception as e:
+ logger.error(f"处理帮战记录数据失败: {e}")
+ return_data["msg"] = "处理帮战记录数据失败"
+ return return_data
+
+ try:
+ return_data["temp"] = await load_template("bangzhanjilu.html")
+ except FileNotFoundError as e:
+ logger.error(f"加载模板失败: {e}")
+ return_data["msg"] = "系统错误:模板文件不存在"
+ return return_data
+
+ return_data["code"] = 200
+
+ return return_data
+
+
+ async def tongzhanyy(self, server: str) -> Dict[str, Any]:
+ """统战歪歪"""
+ return_data = self._init_return_data()
+
+ params = {
+ "server": server,
+ "token": self.token,
+ }
+
+ data: Optional[List[Dict[str, Any]]] = await self._base_request(
+ "jx3_tongzhanyy", "GET", params=params
+ )
+
+ if not data or not isinstance(data, list):
+ return_data["msg"] = "未查询到统战歪歪信息"
+ return return_data
+
+ try:
+ lines = ["统战歪歪"]
+ has_channel = False
+
+ for group in data:
+ if not isinstance(group, dict):
+ continue
+
+ group_server = group.get("server", "")
+ channels = group.get("data", [])
+ if not isinstance(channels, list) or not channels:
+ continue
+
+ if len(lines) > 1:
+ lines.append("")
+ lines.append(f"服务器:{group_server}")
+
+ for item in channels:
+ if not isinstance(item, dict):
+ continue
+
+ has_channel = True
+ short_id = item.get("esid") or item.get("asid", "")
+ lines.extend([
+ f"阵营:{item.get('campName', '')}",
+ f"频道ID:{item.get('sid', '')}",
+ f"短位ID:{short_id}",
+ f"在线人数:{item.get('users', '')}",
+ f"频道名:{item.get('snick', '')}",
+ "",
+ ])
+
+ if not has_channel:
+ return_data["msg"] = "未查询到统战歪歪信息"
+ return return_data
+
+ return_data["data"] = "\n".join(lines).rstrip()
+ except Exception as e:
+ logger.error(f"处理统战歪歪数据失败: {e}")
+ return_data["msg"] = "处理统战歪歪数据失败"
+ return return_data
+
+ return_data["code"] = 200
+
+ return return_data
+
+
async def dilujilu(self, server: str) -> Dict[str, Any]:
"""的卢记录"""
return_data = self._init_return_data()
@@ -2234,6 +2800,79 @@ class JX3Service:
return return_data
+ async def tiebawujia(self, name: str, limit: int = 5, server: str = "") -> Dict[str, Any]:
+ """贴吧物价"""
+ return_data = self._init_return_data()
+
+ try:
+ limit = int(limit)
+ except (TypeError, ValueError):
+ return_data["msg"] = "记录数量必须是数字"
+ return return_data
+
+ if limit < 1 or limit > 50:
+ return_data["msg"] = "贴吧物价记录数量必须在 1-50 之间"
+ return return_data
+
+ params = {
+ "server": server,
+ "name": name,
+ "limit": limit,
+ "token": self.token,
+ }
+
+ data: Optional[List[Dict[str, Any]]] = await self._base_request(
+ "jx3_tiebawujia", "GET", params=params
+ )
+
+ if not data or not isinstance(data, list):
+ return_data["msg"] = "未查询到贴吧物价记录"
+ return return_data
+
+ try:
+ lines = [
+ f"贴吧物价:{name}",
+ f"服务器:{server}",
+ f"记录数:{len(data)}",
+ "",
+ ]
+
+ for index, item in enumerate(data, start=1):
+ if not isinstance(item, dict):
+ continue
+
+ item_time = item.get("time", "")
+ if item_time:
+ try:
+ item_time = datetime.fromtimestamp(int(item_time)).strftime("%Y-%m-%d %H:%M:%S")
+ except (TypeError, ValueError, OSError):
+ item_time = str(item_time)
+
+ lines.extend([
+ f"{index}. {item.get('name', '')}",
+ f"区服:{item.get('zone', '')} 服务器:{item.get('server', '')}",
+ f"内容:{item.get('context', '')}",
+ f"回复:{item.get('reply', '')} 楼层:{item.get('floor', '')}",
+ f"时间:{item_time}",
+ f"链接:https://tieba.baidu.com/p/{item.get('url', '')}",
+ "",
+ ])
+
+ if len(lines) <= 4:
+ return_data["msg"] = "未查询到贴吧物价记录"
+ return return_data
+
+ return_data["data"] = "\n".join(lines).rstrip()
+ except Exception as e:
+ logger.error(f"处理贴吧物价数据失败: {e}")
+ return_data["msg"] = "处理贴吧物价数据失败"
+ return return_data
+
+ return_data["code"] = 200
+
+ return return_data
+
+
async def diaoluo(self, name: str, limit: int = 20, server: str = "") -> Dict[str, Any]:
"""物品掉落记录"""
return_data = self._init_return_data()
diff --git a/core/message.py b/core/message.py
index da3b6f0..27fa47e 100644
--- a/core/message.py
+++ b/core/message.py
@@ -282,11 +282,21 @@ class MessageBuilder:
return await self.T2I_image_msg(event, lambda: self.jx3api.zilipaixing(school, self.serverdefault(server)))
+ async def jx3_shilianpaixing(self, event: AstrMessageEvent, name: str, server: str = ""):
+ """剑三 试炼排行 心法 服务器"""
+ return await self.T2I_image_msg(event, lambda: self.jx3api.shilianpaixing(name, self.serverdefault(server)))
+
+
async def jx3_shaohua(self, event: AstrMessageEvent,):
"""剑三 骚话"""
return await self.plain_msg(event, self.jx3api.shaohua)
+ async def jx3_jiemi(self, event: AstrMessageEvent):
+ """剑三 解密"""
+ return await self.plain_msg(event, self.jx3api.jiemi)
+
+
async def jx3_shapan(self, event: AstrMessageEvent,server: str = ""):
"""剑三 沙盘 服务器"""
return await self.image_msg(event, lambda: self.jx3api.shapan(self.serverdefault(server)))
@@ -302,11 +312,46 @@ class MessageBuilder:
return await self.plain_msg(event, lambda: self.jx3api.fuyaojjiutian( self.serverdefault(server)))
+ async def jx3_zhueevent(self, event: AstrMessageEvent):
+ """剑三 诛恶事件"""
+ return await self.T2I_image_msg(event, self.jx3api.zhueevent)
+
+
+ async def jx3_benrichitu(self, event: AstrMessageEvent):
+ """剑三 本日赤兔"""
+ return await self.plain_msg(event, self.jx3api.benrichitu)
+
+
+ async def jx3_benzhouchitu(self, event: AstrMessageEvent):
+ """剑三 本周赤兔"""
+ return await self.plain_msg(event, self.jx3api.benzhouchitu)
+
+
async def jx3_zhengyingpaimai(self, event: AstrMessageEvent,name: str = "玄晶", server: str = ""):
"""剑三 阵营拍卖 物品名称 服务器"""
return await self.T2I_image_msg(event, lambda: self.jx3api.zhengyingpaimai( self.serverdefault(server), name))
+ async def jx3_zhenyingevent(self, event: AstrMessageEvent):
+ """剑三 阵营事件"""
+ return await self.T2I_image_msg(event, self.jx3api.zhenyingevent)
+
+
+ async def jx3_guanaishouling(self, event: AstrMessageEvent):
+ """剑三 关隘首领"""
+ return await self.T2I_image_msg(event, self.jx3api.guanaishouling)
+
+
+ async def jx3_bangzhanjilu(self, event: AstrMessageEvent, server: str = ""):
+ """剑三 帮战记录 服务器"""
+ return await self.T2I_image_msg(event, lambda: self.jx3api.bangzhanjilu(self.serverdefault(server)))
+
+
+ async def jx3_tongzhanyy(self, event: AstrMessageEvent, server: str = ""):
+ """剑三 统战歪歪 服务器"""
+ return await self.plain_msg(event, lambda: self.jx3api.tongzhanyy(self.serverdefault(server)))
+
+
async def jx3_dilujilu(self, event: AstrMessageEvent,server: str = ""):
"""剑三 的卢 服务器"""
return await self.T2I_image_msg(event, lambda: self.jx3api.dilujilu( self.serverdefault(server)))
@@ -520,6 +565,19 @@ class MessageBuilder:
return await self.T2I_image_msg(event, lambda: self.jx3api.jiaoyihang(Name, self.serverdefault(server)))
+ async def jx3_tiebawujia(self, event: AstrMessageEvent, name: str = "狐金", limit_or_server: str = "5", server: str = ""):
+ """剑三 贴吧物价 物品名称 数量 服务器"""
+ limit = 5
+ target_server = server
+
+ if str(limit_or_server).isdigit():
+ limit = int(limit_or_server)
+ else:
+ target_server = limit_or_server
+
+ return await self.plain_msg(event, lambda: self.jx3api.tiebawujia(name, limit, self.serverdefault(target_server)))
+
+
async def jx3_diaoluo(self, event: AstrMessageEvent, name: str = "玄晶", limit_or_server: str = "20", server: str = ""):
"""剑三 掉落 物品名称 数量 服务器"""
limit = 20
diff --git a/data/api_config.json b/data/api_config.json
index e74d83c..655d768 100644
--- a/data/api_config.json
+++ b/data/api_config.json
@@ -147,6 +147,16 @@
"token":""
}
},
+ "jx3_shilianpaixing":{
+ "url":"https://www.jx3api.com/data/rank/trials",
+ "method":"GET",
+ "description":"查询指定心法的木桩试炼排行榜",
+ "params":{
+ "server": "梦江南",
+ "name": "花间游",
+ "token":""
+ }
+ },
"jx3_shaohua":{
"url":"https://www.jx3api.com/data/saohua/random",
"method":"GET",
@@ -155,6 +165,14 @@
"name": "万花"
}
},
+ "jx3_jiemi":{
+ "url":"https://www.jx3api.com/data/mech/calculator",
+ "method":"GET",
+ "description":"查询机关解密结果",
+ "params":{
+ "token": ""
+ }
+ },
"aijx3_shapan":{
"url":"https://www.jianxiachaguan.cn/api2/aijx3-jxcg/game/get-sand-table-img",
"method":"POST",
@@ -180,6 +198,30 @@
"token": ""
}
},
+ "jx3_zhueevent":{
+ "url":"https://www.jx3api.com/data/smite/records",
+ "method":"GET",
+ "description":"查询诛恶事件记录",
+ "params":{
+ "token": ""
+ }
+ },
+ "jx3_benrichitu":{
+ "url":"https://www.jx3api.com/data/chitu/records",
+ "method":"GET",
+ "description":"查询本日赤兔记录",
+ "params":{
+ "token": ""
+ }
+ },
+ "jx3_benzhouchitu":{
+ "url":"https://www.jx3api.com/data/chitu/week/records",
+ "method":"GET",
+ "description":"查询本周赤兔记录",
+ "params":{
+ "token": ""
+ }
+ },
"jx3_zhengyingpaimai":{
"url":"https://www.jx3api.com/data/auction/records",
"method":"GET",
@@ -191,6 +233,40 @@
"token": ""
}
},
+ "jx3_zhenyingevent":{
+ "url":"https://www.jx3api.com/data/fenxian/records",
+ "method":"GET",
+ "description":"查询跨服阵营事件记录",
+ "params":{
+ "token": ""
+ }
+ },
+ "jx3_guanaishouling":{
+ "url":"https://www.jx3api.com/data/mine/cart",
+ "method":"GET",
+ "description":"查询各服务器关隘首领刷新记录",
+ "params":{
+ "token": ""
+ }
+ },
+ "jx3_bangzhanjilu":{
+ "url":"https://www.jx3api.com/data/battle/records",
+ "method":"GET",
+ "description":"查询指定服务器帮战记录",
+ "params":{
+ "server": "梦江南",
+ "token": ""
+ }
+ },
+ "jx3_tongzhanyy":{
+ "url":"https://www.jx3api.com/data/duowan/statistics",
+ "method":"GET",
+ "description":"查询指定服务器统战歪歪信息",
+ "params":{
+ "server": "梦江南",
+ "token": ""
+ }
+ },
"jx3_dilujilu":{
"url":"https://www.jx3api.com/data/steed/records",
"method":"GET",
@@ -424,6 +500,17 @@
"token": ""
}
},
+ "jx3_tiebawujia":{
+ "url":"https://www.jx3api.com/data/tieba/item/records",
+ "method":"GET",
+ "description":"查询指定物品的贴吧价格记录",
+ "params":{
+ "server": "梦江南",
+ "name": "狐金",
+ "limit": 5,
+ "token": ""
+ }
+ },
"jx3_diaoluo":{
"url":"https://www.jx3api.com/data/reward/statistics",
"method":"GET",
diff --git a/main.py b/main.py
index 93c5d72..dd1f268 100644
--- a/main.py
+++ b/main.py
@@ -17,7 +17,7 @@ from .core.fun_basic import load_as_base64
@register("astrbot_plugin_jx3",
"fxdyz",
"通过调用剑网三API接口获取游戏数据,处理发送。",
- "2.3",
+ "2.5",
"https://github.com/qsc20001102/astrbot_plugin_jx3api"
)
class Jx3ApiPlugin(Star):
@@ -204,14 +204,23 @@ class Jx3ApiPlugin(Star):
"奇穴": self.jx3cmd.jx3_qixue,
"技能": self.jx3cmd.jx3_jineng,
"资历排行": self.jx3cmd.jx3_zilipaixing,
+ "试炼排行": self.jx3cmd.jx3_shilianpaixing,
"骚话": self.jx3cmd.jx3_shaohua,
+ "解密": self.jx3cmd.jx3_jiemi,
"沙盘": self.jx3cmd.jx3_shapan,
"攻略": self.jx3cmd.jx3_qiyugonglue,
"宏": self.jx3cmd.jx3_hong,
"配装": self.jx3cmd.jx3_peizhuang,
"百战": self.jx3cmd.jx3_baizhan,
"扶摇": self.jx3cmd.jx3_fuyaojjiutian,
+ "诛恶事件": self.jx3cmd.jx3_zhueevent,
+ "本日赤兔": self.jx3cmd.jx3_benrichitu,
+ "本周赤兔": self.jx3cmd.jx3_benzhouchitu,
"拍卖": self.jx3cmd.jx3_zhengyingpaimai,
+ "阵营事件": self.jx3cmd.jx3_zhenyingevent,
+ "关隘首领": self.jx3cmd.jx3_guanaishouling,
+ "帮战记录": self.jx3cmd.jx3_bangzhanjilu,
+ "统战": self.jx3cmd.jx3_tongzhanyy,
"的卢": self.jx3cmd.jx3_dilujilu,
"烟花": self.jx3cmd.jx3_yanhuachaxun,
"骗子": self.jx3cmd.jx3_pianzhi,
@@ -253,6 +262,7 @@ class Jx3ApiPlugin(Star):
"物价": self.jx3cmd.jx3_wujia,
"八卦": self.jx3cmd.jx3_bagua,
"交易行": self.jx3cmd.jx3_jiaoyihang,
+ "贴吧物价": self.jx3cmd.jx3_tiebawujia,
"掉落": self.jx3cmd.jx3_diaoluo,
"开服推送": self.jx3cmd.jx3_kaifhujiank,
"新闻推送": self.jx3cmd.jx3_xinwenzhixun,
diff --git a/metadata.yaml b/metadata.yaml
index 8b59e5d..7bed29e 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -1,7 +1,7 @@
name: astrbot_plugin_jx3
display_name: 剑网三游戏数据查询工具
desc: 通过接口调用剑网三API接口获取游戏数据,处理发送。
-version: v2.4
+version: v2.5
author: 飞翔大野猪
repo: https://github.com/qsc20001102/astrbot_plugin_jx3
astrbot_version: ">=4.11.0"
diff --git a/templates/bangzhanjilu.html b/templates/bangzhanjilu.html
new file mode 100644
index 0000000..2f3b08d
--- /dev/null
+++ b/templates/bangzhanjilu.html
@@ -0,0 +1,134 @@
+
+
+
| 区服 | +服务器 | +宣战帮会 | +应战帮会 | +开始时间 | +持续时间 | +结束时间 | +
|---|---|---|---|---|---|---|
| {{ item.zoneName }} | +{{ item.serverName }} | +{{ item.declaringTongName }} | +{{ item.acceptingTongName }} | +{{ item.startTime }} | +{{ item.matchDuration }} | +{{ item.endTime }} | +
| 归属 | +关隘名称 | +状态 | +开始时间 | +结束时间 | +剩余时间 | +
|---|---|---|---|---|---|
| {{ item.camp_name }} | +{{ item.castle }} | +{{ item.str_status }} | +{{ item.start_time }} | +{{ item.end_time }} | +{{ item.remaining_time }} | +
按使用场景重新分组,保留每条指令的名称、使用示例与说明。卡片底色表示指令是否需要令牌,免令牌与需令牌功能一眼区分。
八卦 类型
查询 818 / 616 / 避雷等八卦记录。
+ +解密
+ 查询当前机关解密结果与下轮时间。
+交易行 物品名称 [服务器]
查询指定物品交易行价格与挂单信息。
+ +贴吧物价 物品名称 [记录数量] [服务器]
+ 查询指定物品的贴吧交易记录。
+查询阵营拍卖物品及服务器记录。
+阵营事件
+ 查询跨服阵营事件记录。
+关隘首领
+ 查询各分线关隘首领状态与剩余时间。
+帮战记录 [服务器]
+ 查询指定服务器帮战记录。
+统战 [服务器]
+ 查询指定服务器阵营统战 YY 信息。
+查询指定服务器扶摇九天活动记录。
+诛恶事件
+ 查询诛恶事件服务器、地图和时间记录。
+本日赤兔
+ 查询本日赤兔时间、区服和地图记录。
+本周赤兔
+ 查询本周赤兔时间、区服和地图记录。
+资历排行 职业 [服务器]
查询指定职业资历排行榜。
+ +试炼排行 心法 [服务器]
+ 查询指定心法木桩试炼排行榜。
+