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 %}
+
+ | {{ loop.index }} |
+ {{ item.name }} |
+ {{ item.tongName }} |
+
+
+ {% if icons.sect.get(item.forceName) %}
+ 
+ {% endif %}
+ {{ item.forceName }}
+
+ |
+ {% set camp_name = item.campName or '未知' %}
+
+
+ |
+ {{ item.score }} |
+ {% if show_hostile_count %}{{ item.customValue0 }} | {% endif %}
+
+ {% endfor %}
+
+
+
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 '未知' %}
+
+
+ |
{{ m.auctionTime }} |
{{ m.auctionAmount }} |
{{ m.auctionRoleName }} |
- {{ m.auctionCampName }} |
+ {% set auction_camp_name = m.auctionCampName or '未知' %}
+
+
+ |
{% 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 '未知' %}
+
+
+ |
{{ 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 @@