From a42d3146c5ec0d9462c999be5abfcbacd4cf7757 Mon Sep 17 00:00:00 2001 From: qsc Date: Sat, 5 Sep 2026 22:41:53 +0800 Subject: [PATCH] fxdyz --- CHANGELOG.md | 12 +++ README.md | 7 +- _conf_schema.json | 2 +- core/jx3api_data.py | 156 +++++++++++++++++++++++++++ core/message.py | 41 +++++++ main.py | 7 +- metadata.yaml | 2 +- templates/pages/bounty_rank.html | 46 ++++++++ templates/pages/dilujilu.html | 10 +- templates/pages/guanaishouling.html | 5 +- templates/pages/helps.html | 10 +- templates/pages/juedoutiaozhan.html | 41 +++++++ templates/pages/kuafumingjian.html | 34 ++++++ templates/pages/rank_role.html | 5 +- templates/pages/shitu.html | 5 +- templates/pages/wulinzhengba.html | 43 ++++++++ templates/pages/zhengyingpaimai.html | 5 +- templates/pages/zhenyingevent.html | 9 +- templates/sect/万灵.png | Bin 0 -> 6211 bytes templates/styles/components.css | 8 ++ 20 files changed, 432 insertions(+), 16 deletions(-) create mode 100644 templates/pages/bounty_rank.html create mode 100644 templates/pages/juedoutiaozhan.html create mode 100644 templates/pages/kuafumingjian.html create mode 100644 templates/pages/wulinzhengba.html create mode 100644 templates/sect/万灵.png diff --git a/CHANGELOG.md b/CHANGELOG.md index d8c94d6..220d610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ ## 更新记录 +### version: 3.4.5(2026-09-05): + +新增 `跨服名剑 服务器 [模式]` 指令,接入 JX3API 跨服名剑榜;竞技模式支持 `0=2v2`、`1=3v3`、`2=5v5`,默认为 `1`。榜单按接口数组顺序生成排名,使用统一数据表格模板展示队伍名称、积分、胜场、败场和总场数,并同步更新功能帮助与 README。 + +新增 `武林争霸 服务器 [阵营]` 指令,接入 JX3API 武林争霸赛帮会榜;阵营支持 `1=浩气`、`2=恶人`,默认为 `1`。榜单按接口数组顺序生成排名,使用统一数据表格模板展示帮会、帮主、所属服务器、阵营、胜负场、积分和最后比赛时间。 + +新增 `捕快荣誉 服务器` 和 `江湖浪客 服务器` 指令,分别接入 JX3API `/rank/constable` 与 `/rank/outlaw`。两个榜单复用同一数据处理流程和统一表格模板,按接口数组顺序生成排名,展示角色、帮会、门派、阵营和抓捕数;江湖浪客榜额外展示敌对数。 + +新增 `决斗挑战 服务器 [模式]` 指令,接入 JX3API `/rank/wanted`;模式支持 `1=公开`、`2=私密`,默认为 `1`。榜单按接口数组顺序生成排名,展示被悬赏角色、门派、赏金、剩余时间和发起人,并将 `timeOut` 结束时间戳转换为动态剩余时长。 + +统一图片列表中的阵营列:浩气盟、恶人谷和中立改为显示 `templates/img` 内对应图标,不再显示阵营文本;空值或未识别阵营使用“未知”图标兜底。 + ### version: 3.4.4(2026-09-05): 烟花查询在角色名称后新增可选的“条数”参数,默认展示前 50 条,支持指定正整数条数;沿用已绑定会话省略服务器的用法,并同步更新功能帮助和 README。上游接口未提供条数参数,查询结果在本地截取后生成图片。 diff --git a/README.md b/README.md index 1e22ae4..f79380e 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,11 @@ JX3API Token 可从 [JX3API](https://www.jx3api.com/) 获取。推栏 Ticket 通 | `战绩 服务器 角色 [模式]` | 角色名剑战绩,模式可选 `22`、`33`、`55`,默认 `33`;图片 | Token + Ticket | | `名剑排行 [模式] [数量]` | 名剑大会排行,默认 `33`、50 条;图片 | Token + Ticket | | `名剑统计 [模式]` | 名剑门派统计,模式默认 `33`;图片 | Token + Ticket | +| `跨服名剑 服务器 [模式]` | 跨服名剑队伍榜单,模式可选 `0`(2v2)、`1`(3v3)、`2`(5v5),默认 `1`;图片 | Token(LV.2) | +| `武林争霸 服务器 [阵营]` | 武林争霸赛帮会榜,阵营可选 `1`(浩气)、`2`(恶人),默认 `1`;图片 | Token(LV.2) | +| `捕快荣誉 服务器` | 捕快荣誉榜,展示角色、帮会、门派、阵营和抓捕数;图片 | Token(LV.2) | +| `江湖浪客 服务器` | 江湖浪客榜,展示角色、帮会、门派、阵营、抓捕数和敌对数;图片 | Token(LV.2) | +| `决斗挑战 服务器 [模式]` | 决斗挑战悬赏榜,模式可选 `1`(公开)、`2`(私密),默认 `1`;图片 | Token(LV.2) | | `帮会排行 服务器` | 回复序号选择神兵宝甲或爱心帮会榜单,最多保留前 50 条;图片 | Token | | `阵营排行 服务器` | 回复序号选择赛季、上周或本周阵营榜单,最多保留前 50 条;图片 | Token | | `其他排行 服务器` | 回复序号选择名士、老江湖、名师等其他榜单,最多保留前 50 条;图片 | Token | @@ -522,7 +527,7 @@ git diff --check ## 当前版本状态 -以下内容是对 v3.4.4 当前源码的静态核对结果,部署和二次开发前应注意: +以下内容是对 v3.4.5 当前源码的静态核对结果,部署和二次开发前应注意: 1. 查询图片使用浅色高对比主题和放大的内容区域;渲染清晰度、JPEG/PNG 格式及 JPEG 质量由 `image_render_quality` 配置组控制,提高清晰度或使用 PNG 会增加图片体积与渲染耗时。 2. `帮会排行`、`阵营排行`、`其他排行` 使用固定的“数字序号 → 榜单名称”首轮菜单,次轮查询统一截取接口返回的前 50 条;取得有效选择后会先停止超时计时器,避免渲染期间重复输出。 diff --git a/_conf_schema.json b/_conf_schema.json index 74f22e8..b8a852c 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -53,7 +53,7 @@ "1.3", "1.8" ], - "default": "1.3", + "default": "1.0", "hint": "数值越高越清晰,同时图片更大、渲染耗时更多。" }, "format": { diff --git a/core/jx3api_data.py b/core/jx3api_data.py index 4bf0cb6..9152eec 100644 --- a/core/jx3api_data.py +++ b/core/jx3api_data.py @@ -560,6 +560,162 @@ class JX3APIService: template="mingjiantongji.html" ) + async def kuafumingjian(self, server: str, mode: int = 1) -> Dict[str, Any]: + """跨服名剑榜。""" + mode_names = {0: "2v2", 1: "3v3", 2: "5v5"} + + async def processor(data: Any, return_data: Dict[str, Any]) -> None: + items = data.get("data") or [] + if not isinstance(items, list): + items = [] + + return_data["data"] = { + "items": [item for item in items if isinstance(item, dict)], + "name": data.get("name") or "跨服名剑榜", + "server": data.get("server") or server or "全区", + "mode_name": mode_names[mode], + "update_time": format_time(data.get("time")), + } + + return await self._request_api( + path="/rank/arena", + params={"server": server, "mode": mode, "token": self.token}, + processor=processor, + template="kuafumingjian.html", + ) + + async def wulinzhengba(self, server: str, camp: int = 1) -> Dict[str, Any]: + """武林争霸赛帮会榜。""" + camp_names = {1: "浩气盟", 2: "恶人谷"} + + def format_match_time(value: Any) -> str: + try: + total_seconds = int(value) + except (TypeError, ValueError): + return "" + if total_seconds < 0: + return "" + + hours, remainder = divmod(total_seconds, 3600) + minutes, seconds = divmod(remainder, 60) + if hours: + return f"{hours}时{minutes:02d}分{seconds:02d}秒" + return f"{minutes}分{seconds:02d}秒" + + async def processor(data: Any, return_data: Dict[str, Any]) -> None: + items = data.get("data") or [] + if not isinstance(items, list): + items = [] + + rank_items = [] + for item in items: + if not isinstance(item, dict): + continue + rank_item = item.copy() + rank_item["lastMatchTimeText"] = format_match_time( + item.get("lastMatchTime") + ) + rank_items.append(rank_item) + + return_data["data"] = { + "items": rank_items, + "name": data.get("name") or "武林争霸赛", + "server": data.get("server") or server or "全区", + "camp_name": camp_names[camp], + "update_time": format_time(data.get("time")), + } + + return await self._request_api( + path="/rank/championship", + params={"server": server, "camp": camp, "token": self.token}, + processor=processor, + template="wulinzhengba.html", + ) + + async def _bounty_rank(self,server: str,path: str,fallback_name: str,show_hostile_count: bool,) -> Dict[str, Any]: + """处理捕快荣誉和江湖浪客共用的榜单结构。""" + + async def processor(data: Any, return_data: Dict[str, Any]) -> None: + items = data.get("data") or [] + if not isinstance(items, list): + items = [] + + return_data["data"] = { + "items": [item for item in items if isinstance(item, dict)], + "name": data.get("name") or fallback_name, + "server": data.get("server") or server or "全区", + "show_hostile_count": show_hostile_count, + "update_time": format_time(data.get("time")), + } + + return await self._request_api( + path=path, + params={"server": server, "token": self.token}, + processor=processor, + template="bounty_rank.html", + ) + + async def bukairongyu(self, server: str) -> Dict[str, Any]: + """捕快荣誉榜。""" + return await self._bounty_rank( + server=server, + path="/rank/constable", + fallback_name="捕快荣誉榜", + show_hostile_count=False, + ) + + async def jianghulangke(self, server: str) -> Dict[str, Any]: + """江湖浪客榜。""" + return await self._bounty_rank( + server=server, + path="/rank/outlaw", + fallback_name="江湖浪客榜", + show_hostile_count=True, + ) + + async def juedoutiaozhan(self,server: str,mode: int = 1,) -> Dict[str, Any]: + """决斗挑战榜。""" + mode_names = {1: "公开", 2: "私密"} + + async def processor(data: Any, return_data: Dict[str, Any]) -> None: + items = data.get("data") or [] + if not isinstance(items, list): + items = [] + + now_timestamp = int(datetime.now().timestamp()) + rank_items = [] + for item in items: + if not isinstance(item, dict): + continue + rank_item = item.copy() + try: + timeout = int(item.get("timeOut")) + except (TypeError, ValueError): + remaining_time = "" + else: + remaining_time = ( + format_remaining(timeout) + if timeout > now_timestamp + else "已结束" + ) + rank_item["remainingTime"] = remaining_time + rank_items.append(rank_item) + + return_data["data"] = { + "items": rank_items, + "name": data.get("name") or "决斗挑战榜", + "server": data.get("server") or server or "全区", + "mode_name": mode_names[mode], + "update_time": format_time(data.get("time")), + } + + return await self._request_api( + path="/rank/wanted", + params={"server": server, "mode": mode, "token": self.token}, + processor=processor, + template="juedoutiaozhan.html", + ) + async def banghui_rank_menu(self) -> Dict[str, str]: """帮会排行榜选择内容。""" diff --git a/core/message.py b/core/message.py index de0cc6f..5cce9ef 100644 --- a/core/message.py +++ b/core/message.py @@ -349,6 +349,47 @@ class MessageBuilder: """ 名剑统计 模式""" return await self.T2I_image_msg(event, lambda: self.jx3api.mingjiantongji(mode)) + async def kuafumingjian(self, event: AstrMessageEvent, server: str, mode: int = 1,): + """跨服名剑 服务器 [模式]。""" + if mode not in {0, 1, 2}: + return event.plain_result("竞技模式仅支持 0=2v2、1=3v3、2=5v5") + return await self.T2I_image_msg( + event, + lambda: self.jx3api.kuafumingjian(server, mode), + ) + + async def wulinzhengba(self,event: AstrMessageEvent,server: str,camp: int = 1,): + """武林争霸 服务器 [阵营]。""" + if camp not in {1, 2}: + return event.plain_result("阵营仅支持 1=浩气、2=恶人") + return await self.T2I_image_msg( + event, + lambda: self.jx3api.wulinzhengba(server, camp), + ) + + async def bukairongyu(self, event: AstrMessageEvent, server: str): + """捕快荣誉 服务器。""" + return await self.T2I_image_msg( + event, + lambda: self.jx3api.bukairongyu(server), + ) + + async def jianghulangke(self, event: AstrMessageEvent, server: str): + """江湖浪客 服务器。""" + return await self.T2I_image_msg( + event, + lambda: self.jx3api.jianghulangke(server), + ) + + async def juedoutiaozhan(self,event: AstrMessageEvent,server: str,mode: int = 1,): + """决斗挑战 服务器 [模式]。""" + if mode not in {1, 2}: + return event.plain_result("模式仅支持 1=公开、2=私密") + return await self.T2I_image_msg( + event, + lambda: self.jx3api.juedoutiaozhan(server, mode), + ) + async def banghuipaihang(self, event: AstrMessageEvent, server: str): """帮会排行 服务器。""" return await self.handler_plain_image_msg( diff --git a/main.py b/main.py index 4073b23..e0a8e4b 100644 --- a/main.py +++ b/main.py @@ -25,7 +25,7 @@ PLUGIN_NAME = "astrbot_plugin_jx3" @register("astrbot_plugin_jx3", "fxdyz", "聚合剑网三游戏数据,提供查询、图片渲染、本地避雷和实时事件推送。", - "3.4.4", + "3.4.5", "https://github.com/qsc20001102/astrbot_plugin_jx3" ) class Jx3ApiPlugin(Star): @@ -254,6 +254,11 @@ class Jx3ApiPlugin(Star): "战绩": self. jx3cmd.zhanji, "名剑排行": self. jx3cmd.mingjianpaihang, "名剑统计": self. jx3cmd.mingjiantongji, + "跨服名剑": self.jx3cmd.kuafumingjian, + "武林争霸": self.jx3cmd.wulinzhengba, + "捕快荣誉": self.jx3cmd.bukairongyu, + "江湖浪客": self.jx3cmd.jianghulangke, + "决斗挑战": self.jx3cmd.juedoutiaozhan, "帮会排行": self. jx3cmd.banghuipaihang, "阵营排行": self. jx3cmd.zhenyingpaihang, "其他排行": self. jx3cmd.qitapaihang, diff --git a/metadata.yaml b/metadata.yaml index 760b859..fbf9326 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -1,7 +1,7 @@ name: astrbot_plugin_jx3 display_name: 剑网三游戏数据查询工具 desc: 聚合剑网三游戏数据,提供查询、图片渲染、本地避雷和实时事件推送。 -version: 3.4.4 +version: 3.4.5 author: 飞翔大野猪 repo: https://github.com/qsc20001102/astrbot_plugin_jx3 astrbot_version: ">=4.24.1" diff --git a/templates/pages/bounty_rank.html b/templates/pages/bounty_rank.html new file mode 100644 index 0000000..9361d1c --- /dev/null +++ b/templates/pages/bounty_rank.html @@ -0,0 +1,46 @@ +{# template-title: 悬赏排行榜 #} +{# template-components: data-table #} + + +
+

{{ name }}

+
{{ server }}{% if update_time %} · 更新时间:{{ update_time }}{% endif %}
+ + + + + + + + + + + {% if show_hostile_count %}{% endif %} + + + + + {% for item in items %} + + + + + + {% set camp_name = item.campName or '未知' %} + + + {% if show_hostile_count %}{% endif %} + + {% endfor %} + +
排名名称帮会门派阵营抓捕数敌对数
{{ loop.index }}{{ item.name }}{{ item.tongName }} +
+ {% if icons.sect.get(item.forceName) %} + {{ item.forceName }} + {% endif %} + {{ item.forceName }} +
+
+ {{ camp_name }} + {{ item.score }}{{ item.customValue0 }}
+
diff --git a/templates/pages/dilujilu.html b/templates/pages/dilujilu.html index 681f15b..ea1e188 100644 --- a/templates/pages/dilujilu.html +++ b/templates/pages/dilujilu.html @@ -33,11 +33,17 @@ {{ m.refreshTime }} {{ m.captureTime }} {{ m.captureRoleName }} - {{ m.captureCampName }} + {% set capture_camp_name = m.captureCampName or '未知' %} + + {{ capture_camp_name }} + {{ m.auctionTime }} {{ m.auctionAmount }} {{ m.auctionRoleName }} - {{ m.auctionCampName }} + {% set auction_camp_name = m.auctionCampName or '未知' %} + + {{ auction_camp_name }} + {% endfor %} diff --git a/templates/pages/guanaishouling.html b/templates/pages/guanaishouling.html index 0f9145f..a944e94 100644 --- a/templates/pages/guanaishouling.html +++ b/templates/pages/guanaishouling.html @@ -30,7 +30,10 @@ class="camp-haoqi" {% endif %} > - {{ item.camp_name }} + {% set camp_name = item.camp_name or '未知' %} + + {{ camp_name }} + {{ item.castle }} {{ item.str_status }} {{ item.start_time }} diff --git a/templates/pages/helps.html b/templates/pages/helps.html index 167444d..dc7aec1 100644 --- a/templates/pages/helps.html +++ b/templates/pages/helps.html @@ -4,7 +4,7 @@
剑网三综合查询插件
-
18 个功能分区 · 92 个触发指令
+
18 个功能分区 · 97 个触发指令
参数之间使用空格分隔 @@ -30,17 +30,21 @@
-
02

名剑相关

3 个触发词
+
02

名剑相关

4 个触发词
名剑战绩
战绩 服务器 角色 [模式]
名剑排行
名剑排行 [模式] [数量]
名剑统计
名剑统计 [模式]
+
跨服名剑
跨服名剑 服务器 [模式]
-
03

排行榜单

4 个触发词
+
03

排行榜单

8 个触发词
+
武林争霸
武林争霸 服务器 [阵营]
+
悬赏排行
捕快荣誉 服务器江湖浪客 服务器
+
决斗挑战
决斗挑战 服务器 [模式]
分类排行
帮会排行 服务器阵营排行 服务器其他排行 服务器
试炼排行
试炼排行 服务器 心法
diff --git a/templates/pages/juedoutiaozhan.html b/templates/pages/juedoutiaozhan.html new file mode 100644 index 0000000..fde36d1 --- /dev/null +++ b/templates/pages/juedoutiaozhan.html @@ -0,0 +1,41 @@ +{# template-title: 决斗挑战榜 #} +{# template-components: data-table #} + + +
+

{{ name }}

+
{{ server }} · {{ mode_name }}{% if update_time %} · 更新时间:{{ update_time }}{% endif %}
+ + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + {% endfor %} + +
排名被悬赏门派赏金剩余时间发起人
{{ loop.index }}{{ item.name }} +
+ {% if icons.sect.get(item.forceName) %} + {{ item.forceName }} + {% endif %} + {{ item.forceName }} +
+
{{ item.money }}{{ item.remainingTime }}{{ item.payerName }}
+
diff --git a/templates/pages/kuafumingjian.html b/templates/pages/kuafumingjian.html new file mode 100644 index 0000000..c4f21e3 --- /dev/null +++ b/templates/pages/kuafumingjian.html @@ -0,0 +1,34 @@ +{# template-title: 跨服名剑榜 #} +{# template-components: data-table #} + + +
+

{{ name }}

+
{{ server }} · {{ mode_name }}{% if update_time %} · 更新时间:{{ update_time }}{% endif %}
+ + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + {% endfor %} + +
排名队伍名称积分胜场败场总场数
{{ loop.index }}{{ item.corpsName }}{{ item.corpsLevel }}{{ item.seasonWinCount }}{{ item.seasonLoseCount }}{{ item.seasonTotalCount }}
+
diff --git a/templates/pages/rank_role.html b/templates/pages/rank_role.html index 058a556..b915b42 100644 --- a/templates/pages/rank_role.html +++ b/templates/pages/rank_role.html @@ -33,7 +33,10 @@ {{ item.name }} {{ item.level }} - {{ item.campName }} + {% set camp_name = item.campName or '未知' %} + + {{ camp_name }} + {{ item.tongName }} {{ item.totalScore }} diff --git a/templates/pages/shitu.html b/templates/pages/shitu.html index 9dd9c2c..06d7cbf 100644 --- a/templates/pages/shitu.html +++ b/templates/pages/shitu.html @@ -38,7 +38,10 @@ {{ item.roleLevel }} {{ item.bodyName }} {{ item.tongName }} - {{ item.campName }} + {% set camp_name = item.campName or '未知' %} + + {{ camp_name }} + {{ item.comment }} {% endfor %} diff --git a/templates/pages/wulinzhengba.html b/templates/pages/wulinzhengba.html new file mode 100644 index 0000000..5cbef90 --- /dev/null +++ b/templates/pages/wulinzhengba.html @@ -0,0 +1,43 @@ +{# template-title: 武林争霸赛 #} +{# template-components: data-table #} + + +
+

{{ name }}

+
{{ server }} · {{ camp_name }}{% if update_time %} · 更新时间:{{ update_time }}{% endif %}
+ + + + + + + + + + + + + + + + + + {% for item in items %} + + + + + + {% set camp_name = item.campName or '未知' %} + + + + + + + {% endfor %} + +
排名帮会名称帮主所属服务器阵营胜场败场积分最后比赛时间
{{ loop.index }}{{ item.tongName }}{{ item.masterName }}{{ item.serverName }} + {{ camp_name }} + {{ item.winCount }}{{ item.loseCount }}{{ item.totalScore }}{{ item.lastMatchTimeText }}
+
diff --git a/templates/pages/zhengyingpaimai.html b/templates/pages/zhengyingpaimai.html index c638c22..d8959f3 100644 --- a/templates/pages/zhengyingpaimai.html +++ b/templates/pages/zhengyingpaimai.html @@ -29,7 +29,10 @@ {{ m.server }} {{ m.itemName }} {{ m.roleName }} - {{ m.campName }} + {% set camp_name = m.campName or '未知' %} + + {{ camp_name }} + {{ m.itemAmount }} {{ m.time }} diff --git a/templates/pages/zhenyingevent.html b/templates/pages/zhenyingevent.html index 08b5b63..abda0dc 100644 --- a/templates/pages/zhenyingevent.html +++ b/templates/pages/zhenyingevent.html @@ -20,13 +20,16 @@ {% for item in items %} - {{ item.campName }} + {% set camp_name = item.campName or '未知' %} + + {{ camp_name }} + {{ item.fenxianName }} {{ item.friendName }} {{ item.roleName }} diff --git a/templates/sect/万灵.png b/templates/sect/万灵.png new file mode 100644 index 0000000000000000000000000000000000000000..683458eca640e4862d740e41c169730146bc2a95 GIT binary patch literal 6211 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