Compare commits

..

6 Commits

Author SHA1 Message Date
qsc20001102 328b09c24b 增加部分图标 2026-09-02 16:24:23 +08:00
qsc20001102 2051ae7387 区服接口调整 2026-08-15 15:39:55 +08:00
qsc20001102 299c535310 功能描述调整 2026-08-09 14:30:46 +08:00
qsc20001102 9dca029d13 ok 2026-08-09 14:29:18 +08:00
qsc20001102 2758008e7b OK 2026-08-07 11:58:56 +08:00
qsc20001102 abedad8877 11 2026-08-07 10:51:15 +08:00
112 changed files with 241 additions and 344 deletions
+35
View File
@@ -0,0 +1,35 @@
# Python 缓存与编译产物
__pycache__/
*.py[cod]
*$py.class
# 测试与覆盖率产物
tests/
.pytest_cache/
.coverage
.coverage.*
htmlcov/
# 类型检查与代码质量工具缓存
.mypy_cache/
.pyright/
.ruff_cache/
# Python 虚拟环境
.venv/
venv/
env/
# 编辑器与操作系统文件
.idea/
.vscode/
.DS_Store
Thumbs.db
# 本地日志和临时文件
*.log
*.tmp
*.temp
*.bak
*.swp
*~
+22
View File
@@ -1,5 +1,27 @@
## 更新记录
### version: 3.2.3
API区服接口功能调整,做对应调整
### version: 3.2.2
调整部分功能指令的描述
### version: 3.2.1
统一封装 JX3BOX Node、Next2 和 CMS 三类接口请求,集中处理基础地址、GET/POST、请求参数、返回字段提取与异常日志。
修复资历菜单和点数缓存的刷新链路,缓存过期后分别请求 `/api/node/achievement/menus``/api/node/achievement/points`,上游失败时继续使用旧缓存。
刷马与赤兔后台推送改用 JX3BOX Next2 马场消息接口,并向后台任务管理器注入 `JX3BOXService`
配装查询开始实际传递类型筛选参数。
新增项目级 `.gitignore`,忽略 Python 缓存、虚拟环境、日志、临时文件及整个 `tests/` 目录。
同步更新 README、插件注册信息和元数据版本号。
### version: 3.1
增加副本记录查询功能。
+29 -28
View File
@@ -10,9 +10,6 @@
`astrbot_plugin_jx3` 通过 JX3API、剑侠茶馆、JX3BOX 等数据源查询《剑网3》游戏数据,并根据功能将结果发送为纯文本、图片、图文消息或两轮交互消息。插件同时提供本地避雷记录和开服、新闻、刷马、赤兔后台推送能力。
当前仓库版本为 **v3.1**,要求 **AstrBot >= 4.11.0**,元数据声明支持 `aiocqhttp``weixin_oc` 平台。
> 本 README 以 v3.1 的 `main.py` 指令映射、`core/message.py` 参数签名和三个数据服务的实际实现为准。当前代码与内置帮助图之间存在少量差异,详见[当前版本状态](#当前版本状态)。
## 功能特点
@@ -22,6 +19,7 @@
- 支持本地 SQLite 避雷记录的增删改查。
- 支持开服、新闻、刷马、赤兔四类定时轮询与会话推送。
- 复用 `aiohttp.ClientSession`,统一处理 GET、POST、JSON、图片和分页请求。
- JX3BOX 的 Node、Next2、CMS 请求统一封装,资历与交易行基础数据支持本地快照缓存和过期兜底。
- 内置 46 个页面片段,通过公共布局与样式在本地组装为完整 HTML,并附带通用、门派/心法和奇遇图标资源。
## 数据来源
@@ -30,7 +28,7 @@
| --- | --- | --- |
| [JX3API](https://www.jx3api.com/) | `core/jx3api_data.py` | 绝大多数游戏查询、官方资讯、排行、角色和推送数据 |
| [剑侠茶馆](https://www.jianxiachaguan.cn/) | `core/aijx3_data.py` | 阵营沙盘图片 |
| [JX3BOX](https://www.jx3box.com/) | `core/jx3box_data.py` | 奇遇攻略、配装、宏、资历交易行 |
| [JX3BOX](https://www.jx3box.com/) | `core/jx3box_data.py` | 奇遇攻略、配装、宏、资历交易行及刷马/赤兔推送消息 |
| 本地 SQLite | `core/sqlite.py``core/bilei_data.py` | 心法别名、避雷记录、推送状态和基础数据缓存 |
外部数据源的可用性、数据时效和字段结构均不由本插件控制。接口变更、网络异常、凭据权限不足或上游限流都可能导致查询失败。
@@ -74,7 +72,7 @@ pip install -r data/plugins/astrbot_plugin_jx3/requirements.txt
| `aiofiles` | 异步读取 HTML 模板 |
| `aiosqlite` | 异步访问本地 SQLite 数据库 |
| `apscheduler` | 后台轮询与消息推送调度 |
| `matplotlib` | 当前依赖清单保留的绘图依赖;v3.1 业务代码未直接导入 |
| `matplotlib` | 当前依赖清单保留的绘图依赖;v3.2.1 业务代码未直接导入 |
## 插件配置
@@ -371,7 +369,7 @@ flowchart LR
- `JX3APIService`:以 `https://www.jx3api.com` 为根地址,通过 GET 请求实现主体功能。
- `AIJX3Service`:向剑侠茶馆发送 POST 请求,目前只负责沙盘图片。
- `JX3BOXService`直接访问 JX3BOX 的 Node、CMSNext2 和图标地址,负责攻略、配装、宏、资历交易行。
- `JX3BOXService`通过统一请求入口访问 JX3BOX 的 Node、CMSNext2 服务,负责攻略、配装、宏、资历交易行及刷马/赤兔推送消息
服务方法通常返回统一结构:
@@ -399,6 +397,8 @@ flowchart LR
- 可通过 `out_key` 提取响应中的指定字段。
- 网络、HTTP、JSON 和业务码异常统一记录日志并返回 `None`
业务服务在 `APIClient` 之上维护各自的基础请求方法。`JX3APIService``https://www.jx3api.com` 为固定根地址;`JX3BOXService._base_request()` 根据 `node``next2``cms` 数据源选择基础地址,并统一转发 GET/POST 参数和 `out` 返回字段。JX3BOX 业务代码只传接口路径,不再重复拼接完整域名。
### 5. 消息与图片渲染
`core/message.py` 根据业务结果选择输出方式:
@@ -449,7 +449,7 @@ AstrBot 的渲染接口接收完整 HTML 字符串,因此插件不会依赖渲
| `data/plugin_data.db` | 随插件分发,只读基础数据为主 | `kungfu` 心法名称、别名和 JX3BOX 配装 ID |
| AstrBot 插件数据目录下的 `local_data.db` | 运行时创建和维护 | 避雷记录、推送状态、资历与交易行基础数据缓存 |
`achievement_cache` 同时被资历基础数据和交易行物品分组复用。缓存有效期为 30 天;缓存过期后优先刷新,上游请求失败时会尝试使用旧缓存兜底
`achievement_cache` 同时被资历基础数据和交易行物品分组复用。每个接口快照以一条 JSON 记录保存,当前使用 `achievement_menus``achievement_points``trade_item_groups` 三个键。缓存有效期为 30 天;缓存过期后优先全量刷新,上游请求失败时继续使用可解析的旧缓存兜底。资历菜单与点数的刷新接口分别为 JX3BOX Node 的 `/api/node/achievement/menus``/api/node/achievement/points`
### 7. 后台推送
@@ -463,10 +463,13 @@ AstrBot 的渲染接口接收完整 HTML 字符串,因此插件不会依赖渲
调度任务取得业务数据后读取其中的 `status`。状态发生变化时,向所有 `umos` 发送 `data` 文本,并将新状态写回 `tuishong` 表。插件卸载时会移除全部任务并以非等待方式关闭调度器。
开服与新闻任务使用 `JX3APIService`;刷马与赤兔任务使用 `JX3BOXService.machangxiaoxi()` 请求 Next2 马场消息接口,分别传入 `horse/foreshow``chitu-horse/share_msg`,并把最新消息 ID 作为状态值避免重复推送。
## 目录结构
```text
astrbot_plugin_jx3/
├── .gitignore # Python、工具缓存、临时文件和 tests 忽略规则
├── main.py # 插件入口、生命周期、数据库建表和指令分发
├── metadata.yaml # 插件名称、版本、平台和 AstrBot 版本要求
├── _conf_schema.json # AstrBot 管理面板配置结构
@@ -486,21 +489,19 @@ astrbot_plugin_jx3/
│ ├── sqlite.py # aiosqlite 通用封装
│ ├── fun_basic.py # 图标、时间和货币格式化工具
│ └── template.py # 模板组合、异步读取与内存缓存
── templates/
├── layouts/
│ └── base.html # 唯一的完整 HTML 文档骨架
├── pages/
│ └── *.html # 46 个页面内容与 Jinja2 数据绑定
├── styles/
│ ├── tokens.css # 设计变量与页面宽度
│ ├── base.css # 全局背景、外框和排版
│ ├── components.css # 表格、网格、卡片、状态等公共组件
│ └── pages/*.css # 可选的复杂页面独有布局(当前 13 个)
├── img/ # 通用图片资源
├── sect/ # 门派与心法图标
└── serendipity/ # 奇遇图标
└── tests/
└── test_template_system.py # 46 个模板的组合、渲染与安全检查
── templates/
├── layouts/
│ └── base.html # 唯一的完整 HTML 文档骨架
├── pages/
│ └── *.html # 46 个页面内容与 Jinja2 数据绑定
├── styles/
│ ├── tokens.css # 设计变量与页面宽度
│ ├── base.css # 全局背景、外框和排版
│ ├── components.css # 表格、网格、卡片、状态等公共组件
│ └── pages/*.css # 可选的复杂页面独有布局(当前 13 个)
├── img/ # 通用图片资源
├── sect/ # 门派与心法图标
└── serendipity/ # 奇遇图标
```
## 新增功能开发
@@ -520,19 +521,19 @@ astrbot_plugin_jx3/
```bash
python -m compileall -q .
python -m unittest tests.test_template_system -v
git diff --check
```
模板测试会检查 46 个页面片段、13 个可选页面样式、公共组件按声明选择性注入、完整文档组合、Jinja2 基础渲染,以及模板路径能否阻止目录穿越。它和 `compileall` 仍不能替代带真实数据的 AstrBot 消息、HTML 渲染和外部接口联调。
项目级 `.gitignore` 会忽略整个 `tests/`,本地自行维护的测试不会随插件分发。语法检查和差异检查仍不能替代带真实数据的 AstrBot 消息、HTML 渲染、后台推送和外部接口联调;发布前应在具备有效凭据的实际环境中覆盖成功、空数据、超时及上游异常路径
## 当前版本状态
以下内容是对 v3.1 当前源码的静态核对结果,部署和二次开发前应注意:
以下内容是对 v3.2.1 当前源码的静态核对结果,部署和二次开发前应注意:
1. `command_map` 实际注册 108 个触发词;`templates/pages/helps.html` 标注 105 条,并遗漏 `功能``小药``骗子``开服推送`。帮助图中的 `开服监控` 不是当前有效触发词。
2. 默认服务器配置用于四类后台任务,并只在 `烟花` 查询中通过 `serverdefault()` 显式补齐其他可选服务器参数会原样传为空字符串。
3. `JX3BOXService._get_achievement_base_data()` 当前调用了该类中未定义的 `_base_request()`,因此 `资历` 在用户选择分类后无法完成基础数据加载
4. 刷马和赤兔后台任务当前调用了 `JX3APIService` 中不存在的 `shuamamsg()`,启用 `smts``ctts` 后,定时回调会记录执行异常
2. 默认服务器配置用于刷马和赤兔后台任务;开服监控仍固定查询 `梦江南`,新闻任务不使用服务器参数。普通查询中只有 `烟花` 通过 `serverdefault()` 显式补齐默认服务器,其他可选服务器参数会原样传为空字符串。
3. `JX3BOXService` 通过统一的 `_base_request()` 分发 Node、Next2 和 CMS 请求;资历菜单与点数缓存过期后分别从 `/api/node/achievement/menus``/api/node/achievement/points` 刷新
4. 刷马和赤兔后台任务已改用 JX3BOX Next2 马场消息接口;`machangxiaoxi()` 当前直接读取结果列表第一项,上游返回空列表时会由后台任务记录数据结构异常而不发送消息
5. `main.py` 仍计算 `data/jx3api_config.json` 路径,但仓库没有该文件,当前三个服务也不从该路径读取接口配置;接口地址直接维护在服务代码中。
6. `APIClient` 当前默认 `ssl_verify=False`,即外部 HTTPS 请求不校验证书。对传输安全有要求的部署应先评估并调整该设置。
7. JX3API 服务初始化时会把 Token 和 Ticket 写入 debug 日志。不要公开调试日志,建议二次开发时移除敏感值输出。
+8 -6
View File
@@ -11,6 +11,7 @@ from astrbot.api import AstrBotConfig
from .jx3api_data import JX3APIService
from .jx3box_data import JX3BOXService
from .sqlite import AsyncSQLiteDB
class AsyncTask:
@@ -18,10 +19,11 @@ class AsyncTask:
基于 APScheduler 的后台异步监控任务管理类
"""
def __init__(self, context: Context, config: AstrBotConfig, jx3api: JX3APIService, sqlite: AsyncSQLiteDB):
def __init__(self, context: Context, config: AstrBotConfig, jx3api: JX3APIService, jx3box: JX3BOXService, sqlite: AsyncSQLiteDB):
self.context = context
self.conf = config
self.jx3fun = jx3api
self.jx3api = jx3api
self.jx3box = jx3box
self.sql = sqlite
self.server = self.conf.get("server", "梦江南")
@@ -107,10 +109,10 @@ class AsyncTask:
async def init_tasks(self):
settings = [
("kfts", "开服监控", lambda: self.jx3fun.kaifu("梦江南")),
("xwts", "新闻资讯", lambda: self.jx3fun.xinwen(1)),
("smts", "刷马消息", lambda: self.jx3fun.shuamamsg(self.server,type="horse",subtype="foreshow")),
("ctts", "赤兔消息", lambda: self.jx3fun.shuamamsg(self.server,type="chitu-horse",subtype="share_msg")),
("kfts", "开服监控", lambda: self.jx3api.kaifu("梦江南")),
("xwts", "新闻资讯", lambda: self.jx3api.xinwen(1)),
("smts", "刷马消息", lambda: self.jx3box.machangxiaoxi(self.server,"horse","foreshow")),
("ctts", "赤兔消息", lambda: self.jx3box.machangxiaoxi(self.server,"chitu-horse","share_msg")),
]
for key, name, fetch in settings:
+4 -6
View File
@@ -1611,7 +1611,7 @@ class JX3APIService:
return await self._request_api(
path="/server/status/check",
params= {"server": server},
params= {"server": server, "type": "其他"},
processor=processor,
template="qufuzhuangtai.html"
)
@@ -1620,10 +1620,8 @@ class JX3APIService:
async def kaifu(self, server: str) -> Dict[str, Any]:
"""开服状态查询"""
async def processor(data: Any, return_data: Dict[str, Any]) -> None:
if not data.get("status") == "维护" :
status = 1
else:
status = 0
list_data = data[0]
status = list_data.get("status")
if status == 1:
status_str = f"{server}服务器已开服,快冲,快冲!"
@@ -1637,7 +1635,7 @@ class JX3APIService:
return await self._request_api(
path="/server/status/check",
params= {"server": server},
params= {"server": server, "type": 1},
processor=processor,
template="qufuzhuangtai.html"
)
+129 -29
View File
@@ -38,6 +38,13 @@ ACHIEVEMENT_CHOICES = [
ACHIEVEMENT_CHOICE_MAP = {index: (menu_id, title) for index, menu_id, title in ACHIEVEMENT_CHOICES}
JX3BOX_API_BASE_URLS = {
"node": "https://node.jx3box.com",
"next2": "https://next2.jx3box.com",
"cms": "https://cms.jx3box.com",
}
class JX3BOXService:
def __init__(self, config: AstrBotConfig, sqlite: AsyncSQLiteDB, cache_sqlite: Optional[AsyncSQLiteDB] = None):
# 实例化 API Client
@@ -67,28 +74,110 @@ class JX3BOXService:
}
async def _base_request(
self,
source: str,
api_path: str,
method: str = "GET",
params: Optional[Dict[str, Any]] = None,
out: Optional[str] = "data",
) -> Optional[Any]:
"""统一封装 JX3BOX Node、Next2 和 CMS 接口请求。"""
try:
if not self._api:
logger.error("API client is not initialized")
return None
base_url = JX3BOX_API_BASE_URLS.get(source)
if not base_url:
logger.error(f"不支持的 JX3BOX 数据源: {source}")
return None
normalized_path = api_path if api_path.startswith("/") else f"/{api_path}"
api_url = f"{base_url}{normalized_path}"
request_method = method.upper()
if request_method == "GET":
data = await self._api.get(api_url, params=params, out_key=out)
elif request_method == "POST":
data = await self._api.post(api_url, data=params, out_key=out)
else:
logger.error(f"不支持的 JX3BOX 请求方法: {request_method}")
return None
if not data:
logger.warning(f"获取接口信息失败或返回空数据: {api_url}")
return data
except Exception as e:
logger.error(f"JX3BOX 基础请求调用出错 ({source}:{api_path}): {e}")
return None
async def machangxiaoxi(self, srever: str, type: str, subtype: str) -> Dict[str, Any]:
"""马场消息 """
return_data = self._init_return_data()
data = await self._base_request(
"next2",
"/api/game/reporter/horse",
"GET",
params={
"pageIndex":1,
"pageSize":1,
"server":srever,
"type":type,
"subtype":subtype,
},
)
if data == None:
return None
else:
data_list = data["list"][0]
return_data["status"] = data_list["id"]
return_data["data"] = (
f"区服:{srever}\n"
f"{data_list.get('content')}\n"
f"时间:{data_list.get('created_at')}\n"
)
return_data["code"] = 200
return return_data
async def qiyugonglue(self, name: str) -> Dict[str, Any]:
"""奇遇攻略"""
return_data = self._init_return_data()
# 1. 调用基础请求
api_url = "https://node.jx3box.com/serendipities"
data = await self._api.get(api_url, params={"name": name}, out_key="list")
data = await self._base_request(
"node",
"/serendipities",
params={"name": name},
out="list",
)
if not data:
return_data["msg"] = "未找到该奇遇"
return return_data
# 提取dwID
dwID = data[0]["dwID"]
url = f"https://node.jx3box.com/serendipity/{dwID}/achievement"
logger.debug(f"获取ID接口地址:{url}")
data1 = await self._api.get(url)
url1 = f"https://cms.jx3box.com/api/cms/wiki/post/type/achievement/source/{data1['achievement_id']}"
logger.debug(f"获取攻略接口地址:{url1}")
data1 = await self._base_request(
"node",
f"/serendipity/{dwID}/achievement",
out=None,
)
# 获取奇遇攻略
data2 = await self._api.get(url1, out_key="data")
data2 = await self._base_request(
"cms",
f"/api/cms/wiki/post/type/achievement/source/{data1['achievement_id']}",
)
if not data2:
return_data["msg"] = "获取攻略数据异常"
return return_data
@@ -134,7 +223,7 @@ class JX3BOXService:
params = {
"per": "10",
"page": "1",
"tags": "",
"tags": tags,
"client": "std",
"global_level": "130",
"mount": mount,
@@ -142,8 +231,11 @@ class JX3BOXService:
}
# 2. 调用基础请求
api_url = "https://cms.jx3box.com/api/cms/app/pz"
data: Optional[Dict[str, Any]] = await self._api.get(api_url, params=params, out_key="data")
data: Optional[Dict[str, Any]] = await self._base_request(
"cms",
"/api/cms/app/pz",
params=params,
)
# 验证数据
if not data:
@@ -195,8 +287,11 @@ class JX3BOXService:
params = {"subtype": kungfu}
# 2. 调用基础请求
api_url = "https://cms.jx3box.com/api/cms/posts"
data: Optional[Dict[str, Any]] = await self._api.get(api_url, params=params, out_key="data")
data: Optional[Dict[str, Any]] = await self._base_request(
"cms",
"/api/cms/posts",
params=params,
)
if not data:
return_data["msg"] = "未找到该心法一键宏"
@@ -230,10 +325,7 @@ class JX3BOXService:
"""宏 心法"""
return_data = self._init_return_data()
url = f"https://cms.jx3box.com/api/cms/post/{pid}"
logger.debug(f"获取宏接口地址:{url}")
data = await self._api.get(url, out_key="data")
data = await self._base_request("cms", f"/api/cms/post/{pid}")
if not isinstance(data, dict):
return_data["msg"] = "获取宏数据异常"
@@ -313,13 +405,13 @@ class JX3BOXService:
logger.error(f"写入资历缓存失败: {e}")
async def _get_achievement_base_data(self, cache_key: str, config_key: str) -> Optional[Dict[str, Any]]:
async def _get_achievement_base_data(self, cache_key: str, api_path: str) -> Optional[Dict[str, Any]]:
"""获取资历菜单或点数数据,优先使用未过期缓存"""
cached, expired = await self._load_achievement_cache(cache_key)
if cached and not expired:
return cached
data: Optional[Dict[str, Any]] = await self._base_request(config_key, "GET")
data: Optional[Dict[str, Any]] = await self._base_request("node", api_path)
if data and isinstance(data, dict):
await self._save_achievement_cache(cache_key, data)
return data
@@ -339,8 +431,11 @@ class JX3BOXService:
return cached
api_url = "https://cms.jx3box.com/api/cms/pvx/item/group"
data = await self._api.get(api_url, params={"client":"std"}, out_key="data")
data = await self._base_request(
"cms",
"/api/cms/pvx/item/group",
params={"client": "std"},
)
if isinstance(data, list) and data:
await self._save_achievement_cache(cache_key, data)
return data
@@ -485,10 +580,10 @@ class JX3BOXService:
return_data["msg"] = "无法获取角色全区 ID"
return return_data
achievement_data = await self._api.get(
"https://next2.jx3box.com/api/next2/user-achievements",
achievement_data = await self._base_request(
"next2",
"/api/next2/user-achievements",
params={"jx3id": global_id},
out_key="data",
)
if not achievement_data or not isinstance(achievement_data, dict):
return_data["msg"] = "未查询到资历数据"
@@ -507,11 +602,11 @@ class JX3BOXService:
menu_payload = await self._get_achievement_base_data(
"achievement_menus",
"jx3box_achievement_menus",
"/api/node/achievement/menus",
)
point_payload = await self._get_achievement_base_data(
"achievement_points",
"jx3box_achievement_points",
"/api/node/achievement/points",
)
if not menu_payload or not point_payload:
return_data["msg"] = "基础资历数据获取失败"
@@ -600,8 +695,13 @@ class JX3BOXService:
"server": server,
"aggregate_type": "hourly",
}
api_url = "https://next2.jx3box.com/api/auction/"
price_data: Optional[List[Dict[str, Any]]] = await self._api.post(api_url, data=params, out_key="")
price_data: Optional[List[Dict[str, Any]]] = await self._base_request(
"next2",
"/api/auction/",
method="POST",
params=params,
out="",
)
if not price_data or not isinstance(price_data, list):
+9 -3
View File
@@ -18,8 +18,8 @@ from .core.fun_basic import load_as_base64
@register("astrbot_plugin_jx3",
"fxdyz",
"通过调用剑网三API接口获取游戏数据,处理发送。",
"3.2.0",
"聚合剑网三游戏数据,提供查询、图片渲染、本地避雷和后台推送。",
"3.2.3",
"https://github.com/qsc20001102/astrbot_plugin_jx3"
)
class Jx3ApiPlugin(Star):
@@ -154,7 +154,13 @@ class Jx3ApiPlugin(Star):
self.jx3api = JX3APIService(self.conf, self.plugin_sql_db, self.local_sql_db)
self.aijx3 = AIJX3Service(self.conf, self.plugin_sql_db, self.local_sql_db)
self.jx3box = JX3BOXService(self.conf, self.plugin_sql_db, self.local_sql_db)
self.jx3at = AsyncTask(cast(Context, self.context), self.conf, self.jx3api, self.local_sql_db)
self.jx3at = AsyncTask(
cast(Context, self.context),
self.conf,
self.jx3api,
self.jx3box,
self.local_sql_db,
)
self.jx3cmd = MessageBuilder(self.server, self.jx3api, self.aijx3, self.jx3box, self.bilei, self.jx3at, self.icons)
+2 -2
View File
@@ -1,7 +1,7 @@
name: astrbot_plugin_jx3
display_name: 剑网三游戏数据查询工具
desc: 通过接口调用剑网三API接口获取游戏数据,处理发送。
version: 3.2.0
desc: 聚合剑网三游戏数据,提供查询、图片渲染、本地避雷和后台推送。
version: 3.2.3
author: 飞翔大野猪
repo: https://github.com/qsc20001102/astrbot_plugin_jx3
astrbot_version: ">=4.11.0"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Some files were not shown because too many files have changed in this diff Show More