Compare commits

...

2 Commits

Author SHA1 Message Date
qsc20001102 a42d3146c5 fxdyz 2026-09-05 22:41:53 +08:00
qsc20001102 21033c8184 fxdyz 2026-09-05 18:29:50 +08:00
25 changed files with 575 additions and 71 deletions
+26
View File
@@ -1,5 +1,31 @@
## 更新记录
### version: 3.4.52026-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.42026-09-05):
烟花查询在角色名称后新增可选的“条数”参数,默认展示前 50 条,支持指定正整数条数;沿用已绑定会话省略服务器的用法,并同步更新功能帮助和 README。上游接口未提供条数参数,查询结果在本地截取后生成图片。
修复绑定区服后,省略服务器并填写后续可选参数时被误判为显式服务器的问题;例如 `战绩 晏卿卿 22` 现在会正确补齐绑定区服,并按角色“晏卿卿”、模式“22”查询。
修复名剑战绩模板固定读取 `performance['3v3']`,导致 `22``55` 模式渲染失败的问题;现在会按查询模式分别使用 `2v2``3v3``5v5` 表现数据。
名剑战绩的对战记录按胜负统一整行文本颜色,胜利显示绿色,失败显示红色。
名剑排行榜的门派列复用名剑统计的图标样式,在门派名称前显示对应门派图标。
心法别名接入全局指令参数解析:试炼排行、阵眼、配装、技能、奇穴、小药和宏会在调用业务接口前将标准名称或别名统一转换为标准心法名;WebUI 保存或恢复别名后会立即刷新解析缓存,门派筛选参数保持原义。
### version: 3.4.32026-09-05):
接入 JX3API `POST /token/stats` 令牌统计接口,使用插件配置的 `jx3api_token` 查询等级、已用次数、剩余次数和有效状态;WebUI 顶部恢复为四项令牌统计,接口不可用或未配置令牌时显示未获取状态且不影响其他管理数据。
+13 -6
View File
@@ -123,6 +123,7 @@ JX3API Token 可从 [JX3API](https://www.jx3api.com/) 获取。推栏 Ticket 通
- 缺少必填参数、数字转换失败或执行异常时,入口层统一回复 `参数错误或执行失败`
- 未绑定区服时按指令表提供标准参数。绑定后可以省略 `server` 参数;显式填写完整参数时仍优先使用本次输入的区服。在区服参数位置填写 `全区` 时,会忽略会话绑定并向接口传入空区服以查询全区数据。
- 标准区服名和 WebUI 中配置的别名都可用于查询。对于区服后的可选参数,分发器通过当前有效区服目录和别名判断首个参数是显式区服还是后续参数。
- 心法类指令会在入口层把标准心法名或 WebUI 配置的心法别名统一解析为标准名称;当前适用于试炼排行、阵眼、配装、技能、奇穴、小药和宏。门派筛选参数不参与心法别名解析。
### 会话区服绑定
@@ -154,7 +155,7 @@ JX3API Token 可从 [JX3API](https://www.jx3api.com/) 获取。推栏 Ticket 通
| `关隘` | 查询关隘首领状态;图片 | Token |
| `赤兔``本周赤兔` | 查询当日或本周赤兔记录;文本 | Token |
| `阵营奉献 [阵营]` | 查询阵营奉献事件,固定最多 50 条;图片 | Token |
| `烟花 服务器 [角色]` | 查询指定服务器的烟花记录;已绑定会话可省略服务器;图片 | Token |
| `烟花 服务器 角色 [条数]` | 查询指定服务器的烟花记录;条数默认为 50,须为正整数,填写时放在角色名称后;已绑定会话可省略服务器;图片 | Token |
| `刷马 服务器` | 查询刷马聊天情报;文本 | Token |
| `马场 服务器` | 查询未过期马场记录;文本 | Token |
@@ -162,9 +163,14 @@ JX3API Token 可从 [JX3API](https://www.jx3api.com/) 获取。推栏 Ticket 通
| 指令 | 说明与输出 | 凭据 |
| --- | --- | --- |
| `战绩 服务器 角色 [模式]` | 角色名剑战绩,模式默认 `33`;图片 | Token + Ticket |
| `战绩 服务器 角色 [模式]` | 角色名剑战绩,模式可选 `22``33``55`默认 `33`;图片 | Token + Ticket |
| `名剑排行 [模式] [数量]` | 名剑大会排行,默认 `33`、50 条;图片 | Token + Ticket |
| `名剑统计 [模式]` | 名剑门派统计,模式默认 `33`;图片 | Token + Ticket |
| `跨服名剑 服务器 [模式]` | 跨服名剑队伍榜单,模式可选 `0`2v2)、`1`3v3)、`2`5v5),默认 `1`;图片 | TokenLV.2 |
| `武林争霸 服务器 [阵营]` | 武林争霸赛帮会榜,阵营可选 `1`(浩气)、`2`(恶人),默认 `1`;图片 | TokenLV.2 |
| `捕快荣誉 服务器` | 捕快荣誉榜,展示角色、帮会、门派、阵营和抓捕数;图片 | Token(LV.2 |
| `江湖浪客 服务器` | 江湖浪客榜,展示角色、帮会、门派、阵营、抓捕数和敌对数;图片 | Token(LV.2 |
| `决斗挑战 服务器 [模式]` | 决斗挑战悬赏榜,模式可选 `1`(公开)、`2`(私密),默认 `1`;图片 | TokenLV.2 |
| `帮会排行 服务器` | 回复序号选择神兵宝甲或爱心帮会榜单,最多保留前 50 条;图片 | Token |
| `阵营排行 服务器` | 回复序号选择赛季、上周或本周阵营榜单,最多保留前 50 条;图片 | Token |
| `其他排行 服务器` | 回复序号选择名士、老江湖、名师等其他榜单,最多保留前 50 条;图片 | Token |
@@ -348,8 +354,9 @@ flowchart LR
1. `parse_message()` 检查前缀并使用 `str.split()` 切分消息。
2. 第一段作为指令,其余段作为位置参数。
3. `command_map` 将中文触发词映射到 `MessageBuilder` 方法。
4. `_call_with_auto_args()` 通过 `inspect.signature()` 读取参数签名,按注解转换 `int``float`
5. 找不到指令时忽略消息;找到指令后停止事件继续传播并调用处理器
4. `_prepare_server_args()` `_prepare_kungfu_args()` 根据处理器参数名统一补齐区服绑定并解析区服、心法别名
5. `_call_with_auto_args()` 通过 `inspect.signature()` 读取参数签名,按注解转换 `int``float`
6. 找不到指令时忽略消息;找到指令后停止事件继续传播并调用处理器。
分发器不会检查多余参数,多出的参数会被忽略。缺少必填参数会抛出异常并返回统一错误提示。
@@ -474,7 +481,7 @@ astrbot_plugin_jx3/
│ ├── session_control.py # 全部会话、白名单与黑名单访问策略
│ ├── webui.py # 插件管理页路由、数据聚合与请求处理
│ ├── server_binding.py # 会话区服绑定、别名与区服目录
│ ├── kungfu_alias.py # 本地心法目录初始化、校验与 WebUI 写入
│ ├── kungfu_alias.py # 本地心法目录初始化、全局别名解析、校验与 WebUI 写入
│ ├── bilei_data.py # 避雷数据增删改查
│ ├── sqlite.py # aiosqlite 通用封装
│ ├── fun_basic.py # 图标、时间和货币格式化工具
@@ -520,7 +527,7 @@ git diff --check
## 当前版本状态
以下内容是对 v3.4.3 当前源码的静态核对结果,部署和二次开发前应注意:
以下内容是对 v3.4.5 当前源码的静态核对结果,部署和二次开发前应注意:
1. 查询图片使用浅色高对比主题和放大的内容区域;渲染清晰度、JPEG/PNG 格式及 JPEG 质量由 `image_render_quality` 配置组控制,提高清晰度或使用 PNG 会增加图片体积与渲染耗时。
2. `帮会排行``阵营排行``其他排行` 使用固定的“数字序号 → 榜单名称”首轮菜单,次轮查询统一截取接口返回的前 50 条;取得有效选择后会先停止超时计时器,避免渲染期间重复输出。
+1 -1
View File
@@ -53,7 +53,7 @@
"1.3",
"1.8"
],
"default": "1.3",
"default": "1.0",
"hint": "数值越高越清晰,同时图片更大、渲染耗时更多。"
},
"format": {
+165 -2
View File
@@ -451,10 +451,11 @@ class JX3APIService:
)
async def yanhuachaxun(self, server: str, name:str ) -> Dict[str, Any]:
async def yanhuachaxun(self, server: str, name: str, limit: int) -> Dict[str, Any]:
"""烟花记录"""
# 数据处理
async def processor(data: Any, return_data: Dict[str, Any]) -> None:
data = data[:limit]
for item in data:
item["time"] = format_time(item.get("time"))
@@ -511,7 +512,13 @@ class JX3APIService:
async def zhanji(self, name: str, server:str, mode:str) -> Dict[str, Any]:
"""战绩"""
# 数据处理
async def processor(data: Any, return_data: Dict[str, Any]) -> None:
async def processor(data: Any, return_data: Dict[str, Any]) -> None:
performance_key = {
"22": "2v2",
"33": "3v3",
"55": "5v5",
}.get(str(mode), "3v3")
data["currentPerformance"] = data["performance"][performance_key]
return_data["data"] = data
return await self._request_api(
@@ -553,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]:
"""帮会排行榜选择内容。"""
+33
View File
@@ -13,6 +13,7 @@ class KungfuAliasService:
def __init__(self, sqlite: AsyncSQLiteDB, seed_path: Path):
self.sql = sqlite
self.seed_path = seed_path
self._kungfu_lookup: dict[str, str] = {}
async def initialize(self):
await self.sql.execute(
@@ -29,6 +30,7 @@ class KungfuAliasService:
"""
)
await self._seed_defaults()
await self._reload_cache()
async def _seed_defaults(self):
records = self._load_seed_defaults()
@@ -84,6 +86,7 @@ class KungfuAliasService:
for values in records
)
await self.sql.execute_transaction(statements)
await self._reload_cache()
return len(records)
async def list_kungfu(self) -> list[dict[str, Any]]:
@@ -141,6 +144,36 @@ class KungfuAliasService:
""",
(*values, normalized_pzid),
)
await self._reload_cache()
async def _reload_cache(self):
"""Refresh the canonical kungfu name and alias lookup cache.
Returns:
None.
"""
rows = await self.list_kungfu()
lookup = {
self._key(row["name"]): row["name"]
for row in rows
if row["name"]
}
for row in rows:
for alias in row["aliases"]:
lookup.setdefault(self._key(alias), row["name"])
self._kungfu_lookup = lookup
def resolve_kungfu(self, value: Any) -> str:
"""Resolve a canonical kungfu name or alias to its canonical name.
Args:
value: Canonical kungfu name or configured alias.
Returns:
Canonical kungfu name when matched; otherwise the cleaned input.
"""
kungfu = self._clean(value)
return self._kungfu_lookup.get(self._key(kungfu), kungfu)
def _normalize_aliases(self, name: str, aliases: Iterable[Any]) -> list[str]:
result: list[str] = []
+63 -18
View File
@@ -321,9 +321,13 @@ class MessageBuilder:
""" 阵营事件 阵营"""
return await self.T2I_image_msg(event, lambda: self.jx3api.zhenyingevent(name,50))
async def yanhuachaxun(self, event: AstrMessageEvent,server: str,name: str = "" ):
""" 烟花 服务器 角色"""
return await self.T2I_image_msg(event, lambda: self.jx3api.yanhuachaxun(server,name))
async def yanhuachaxun(self, event: AstrMessageEvent, server: str, name: str = "", limit: int = 50):
""" 烟花 服务器 角色 条数"""
if limit <= 0:
return event.plain_result("条数必须为正整数")
return await self.T2I_image_msg(
event, lambda: self.jx3api.yanhuachaxun(server, name, limit)
)
async def shuma(self, event: AstrMessageEvent,server: str ):
""" 刷马 服务器"""
@@ -345,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(
@@ -369,9 +414,9 @@ class MessageBuilder:
lambda selected: self.jx3api.rank_statistical_select(server,selected,),
)
async def shilianpaixing(self, event: AstrMessageEvent, server: str , name: str):
async def shilianpaixing(self, event: AstrMessageEvent, server: str , kungfu: str):
""" 试炼排行 服务器 心法 """
return await self.T2I_image_msg(event, lambda: self.jx3api.shilianpaixing(name, server))
return await self.T2I_image_msg(event, lambda: self.jx3api.shilianpaixing(kungfu, server))
async def zhengyingpaimai(self, event: AstrMessageEvent,server: str , name: str = "", limit: int = 50 ):
""" 阵营拍卖 物品名称 服务器"""
@@ -465,25 +510,25 @@ class MessageBuilder:
""" 角色 服务器 名称 """
return await self.plain_msg(event, lambda: self.jx3api.jueshe(server, name, 1))
async def zhenyan(self, event: AstrMessageEvent, name: str):
async def zhenyan(self, event: AstrMessageEvent, kungfu: str):
""" 阵眼 心法"""
return await self.plain_msg(event, lambda: self.jx3api.zhenyan(name))
return await self.plain_msg(event, lambda: self.jx3api.zhenyan(kungfu))
async def peizhuang(self, event: AstrMessageEvent,name: str, tags: str = ""):
async def peizhuang(self, event: AstrMessageEvent, kungfu: str, tags: str = ""):
""" 配装 心法 类型"""
return await self.plain_msg(event, lambda: self.jx3box.peizhuang(name,tags))
return await self.plain_msg(event, lambda: self.jx3box.peizhuang(kungfu,tags))
async def zilipaixing(self, event: AstrMessageEvent, server: str = "", school: str = ""):
""" 资历排行 服务器 门派 """
return await self.T2I_image_msg(event, lambda: self.jx3api.zilipaixing(server, school))
async def jineng(self, event: AstrMessageEvent, name: str):
async def jineng(self, event: AstrMessageEvent, kungfu: str):
""" 技能 心法"""
return await self.T2I_image_msg(event, lambda: self.jx3api.jineng(name,0))
return await self.T2I_image_msg(event, lambda: self.jx3api.jineng(kungfu,0))
async def qixue(self, event: AstrMessageEvent, name: str):
async def qixue(self, event: AstrMessageEvent, kungfu: str):
""" 奇穴 心法"""
return await self.T2I_image_msg(event, lambda: self.jx3api.qixue(name,0))
return await self.T2I_image_msg(event, lambda: self.jx3api.qixue(kungfu,0))
async def liaotian(self, event: AstrMessageEvent, server:str, name: str, limit:int = 20, page:int = 1):
""" 发言 服务器 角色 条数 页数"""
@@ -493,9 +538,9 @@ class MessageBuilder:
""" 统战 服务器"""
return await self.plain_msg(event, lambda: self.jx3api.tongzhanyy(server))
async def xiaoyao(self, event: AstrMessageEvent, name:str = ""):
async def xiaoyao(self, event: AstrMessageEvent, kungfu:str = ""):
""" 小药 心法"""
return await self.T2I_image_msg(event, lambda: self.jx3api.xiaoyao(name))
return await self.T2I_image_msg(event, lambda: self.jx3api.xiaoyao(kungfu))
async def pianzhi(self, event: AstrMessageEvent, uid: str, server:str = ""):
""" 骗子 uid 服务器"""
@@ -618,12 +663,12 @@ class MessageBuilder:
return await self.T2I_image_msg(event, lambda: self.jx3api.diaoluo(name, server, limit))
async def hong(self, event: AstrMessageEvent,name: str):
async def hong(self, event: AstrMessageEvent, kungfu: str):
""" 宏 心法"""
return await self.handler_plain_image_msg(
event,
lambda: self.jx3box.hong1(name),
lambda selected: self.jx3box.hong_select(name, selected),
lambda: self.jx3box.hong1(kungfu),
lambda selected: self.jx3box.hong_select(kungfu, selected),
result_handler=self.plain_image_msg,
)
+35 -14
View File
@@ -25,7 +25,7 @@ PLUGIN_NAME = "astrbot_plugin_jx3"
@register("astrbot_plugin_jx3",
"fxdyz",
"聚合剑网三游戏数据,提供查询、图片渲染、本地避雷和实时事件推送。",
"3.4.3",
"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,
@@ -407,14 +412,6 @@ class Jx3ApiPlugin(Star):
has_server_arg = server_index < len(prepared)
if bound_server:
required_count = sum(
parameter.default is inspect.Parameter.empty
for parameter in params
)
has_required_parameter_after_server = any(
parameter.default is inspect.Parameter.empty
for parameter in params[server_index + 1:]
)
explicit_server = (
has_server_arg
and (
@@ -422,11 +419,6 @@ class Jx3ApiPlugin(Star):
prepared[server_index]
)
or self.server_binding.is_known_server(prepared[server_index])
or len(prepared) >= len(params)
or (
has_required_parameter_after_server
and len(prepared) >= required_count
)
)
)
if explicit_server:
@@ -442,6 +434,34 @@ class Jx3ApiPlugin(Star):
return prepared
def _prepare_kungfu_args(self, handler, args: list[str]) -> list[str]:
"""Resolve the kungfu parameter through the global alias catalog.
Args:
handler: Command handler whose signature defines argument positions.
args: Positional command arguments after server preparation.
Returns:
Arguments with a recognized kungfu alias replaced by its canonical name.
"""
params = [
parameter
for parameter in inspect.signature(handler).parameters.values()
if parameter.name not in {"self", "event"}
]
kungfu_index = next(
(index for index, parameter in enumerate(params) if parameter.name == "kungfu"),
None,
)
if kungfu_index is None or kungfu_index >= len(args):
return args
prepared = list(args)
prepared[kungfu_index] = self.kungfu_alias.resolve_kungfu(
prepared[kungfu_index]
)
return prepared
async def _call_with_auto_args(self, handler, event: AstrMessageEvent, args: list[str]):
"""指令执行函数"""
@@ -511,6 +531,7 @@ class Jx3ApiPlugin(Star):
try:
args = await self._prepare_server_args(handler, event, args)
args = self._prepare_kungfu_args(handler, args)
ret = await self._call_with_auto_args(handler, event, args)
if ret is not None:
yield ret
+1 -1
View File
@@ -1,7 +1,7 @@
name: astrbot_plugin_jx3
display_name: 剑网三游戏数据查询工具
desc: 聚合剑网三游戏数据,提供查询、图片渲染、本地避雷和实时事件推送。
version: 3.4.3
version: 3.4.5
author: 飞翔大野猪
repo: https://github.com/qsc20001102/astrbot_plugin_jx3
astrbot_version: ">=4.24.1"
+46
View File
@@ -0,0 +1,46 @@
{# template-title: 悬赏排行榜 #}
{# template-components: data-table #}
<div class="container data-page">
<h1>{{ name }}</h1>
<div class="data-page__meta">{{ server }}{% if update_time %} · 更新时间:{{ update_time }}{% endif %}</div>
<table class="data-table">
<thead>
<tr>
<th>排名</th>
<th>名称</th>
<th>帮会</th>
<th>门派</th>
<th>阵营</th>
<th>抓捕数</th>
{% if show_hostile_count %}<th>敌对数</th>{% endif %}
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td class="data-cell--rank">{{ loop.index }}</td>
<td class="data-cell--name data-cell--nowrap">{{ item.name }}</td>
<td class="data-cell--wrap">{{ item.tongName }}</td>
<td>
<div class="icon-label">
{% if icons.sect.get(item.forceName) %}
<img class="icon-label__icon" src="{{ icons.sect[item.forceName] }}" alt="{{ item.forceName }}">
{% endif %}
<span>{{ item.forceName }}</span>
</div>
</td>
{% set camp_name = item.campName or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(camp_name) or icons.img.get('未知') }}" alt="{{ camp_name }}" title="{{ camp_name }}">
</td>
<td class="data-cell--score">{{ item.score }}</td>
{% if show_hostile_count %}<td class="data-cell--nowrap data-cell--numeric">{{ item.customValue0 }}</td>{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
+8 -2
View File
@@ -33,11 +33,17 @@
<td>{{ m.refreshTime }}</td>
<td>{{ m.captureTime }}</td>
<td>{{ m.captureRoleName }}</td>
<td>{{ m.captureCampName }}</td>
{% set capture_camp_name = m.captureCampName or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(capture_camp_name) or icons.img.get('未知') }}" alt="{{ capture_camp_name }}" title="{{ capture_camp_name }}">
</td>
<td>{{ m.auctionTime }}</td>
<td>{{ m.auctionAmount }}</td>
<td>{{ m.auctionRoleName }}</td>
<td>{{ m.auctionCampName }}</td>
{% set auction_camp_name = m.auctionCampName or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(auction_camp_name) or icons.img.get('未知') }}" alt="{{ auction_camp_name }}" title="{{ auction_camp_name }}">
</td>
</tr>
{% endfor %}
</tbody>
+4 -1
View File
@@ -30,7 +30,10 @@
class="camp-haoqi"
{% endif %}
>
<td class="data-cell--camp data-cell--nowrap data-cell--strong">{{ item.camp_name }}</td>
{% set camp_name = item.camp_name or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(camp_name) or icons.img.get('未知') }}" alt="{{ camp_name }}" title="{{ camp_name }}">
</td>
<td class="data-cell--camp data-cell--name data-cell--nowrap">{{ item.castle }}</td>
<td class="data-cell--nowrap data-cell--strong">{{ item.str_status }}</td>
<td class="data-cell--time">{{ item.start_time }}</td>
+8 -4
View File
@@ -4,7 +4,7 @@
<header class="top-info help-header">
<div>
<div class="top-name">剑网三综合查询插件</div>
<div class="top-summary">18 个功能分区 · 92 个触发指令</div>
<div class="top-summary">18 个功能分区 · 97 个触发指令</div>
</div>
<div class="help-rules">
<span>参数之间使用空格分隔</span>
@@ -24,23 +24,27 @@
<div class="command-row"><div class="feature-name">关隘首领</div><div class="commands"><code class="command">关隘</code></div></div>
<div class="command-row"><div class="feature-name">赤兔记录</div><div class="commands"><code class="command">赤兔</code><code class="command">本周赤兔</code></div></div>
<div class="command-row"><div class="feature-name">阵营事件</div><div class="commands"><code class="command">阵营奉献 [阵营]</code></div></div>
<div class="command-row"><div class="feature-name">烟花记录</div><div class="commands"><code class="command">烟花 服务器 角色</code></div></div>
<div class="command-row" data-description="条数默认为 50,须为正整数"><div class="feature-name">烟花记录</div><div class="commands"><code class="command">烟花 服务器 [角色] [条数]</code></div></div>
<div class="command-row"><div class="feature-name">马场查询</div><div class="commands"><code class="command">刷马 服务器</code><code class="command">马场 服务器</code></div></div>
</div>
</section>
<section class="command-section">
<header class="command-section__header"><span class="section-index">02</span><h2>名剑相关</h2><span class="section-count">3 个触发词</span></header>
<header class="command-section__header"><span class="section-index">02</span><h2>名剑相关</h2><span class="section-count">4 个触发词</span></header>
<div class="command-list">
<div class="command-row" data-description="模式可选22、33、55"><div class="feature-name">名剑战绩</div><div class="commands"><code class="command">战绩 服务器 角色 [模式]</code></div></div>
<div class="command-row"><div class="feature-name">名剑排行</div><div class="commands"><code class="command">名剑排行 [模式] [数量]</code></div></div>
<div class="command-row"><div class="feature-name">名剑统计</div><div class="commands"><code class="command">名剑统计 [模式]</code></div></div>
<div class="command-row" data-description="模式:0=2v2、1=3v3、2=5v5,默认 1"><div class="feature-name">跨服名剑</div><div class="commands"><code class="command">跨服名剑 服务器 [模式]</code></div></div>
</div>
</section>
<section class="command-section">
<header class="command-section__header"><span class="section-index">03</span><h2>排行榜单</h2><span class="section-count">4 个触发词</span></header>
<header class="command-section__header"><span class="section-index">03</span><h2>排行榜单</h2><span class="section-count">8 个触发词</span></header>
<div class="command-list">
<div class="command-row" data-description="阵营:1=浩气、2=恶人,默认 1"><div class="feature-name">武林争霸</div><div class="commands"><code class="command">武林争霸 服务器 [阵营]</code></div></div>
<div class="command-row"><div class="feature-name">悬赏排行</div><div class="commands"><code class="command">捕快荣誉 服务器</code><code class="command">江湖浪客 服务器</code></div></div>
<div class="command-row" data-description="模式:1=公开、2=私密,默认 1"><div class="feature-name">决斗挑战</div><div class="commands"><code class="command">决斗挑战 服务器 [模式]</code></div></div>
<div class="command-row"><div class="feature-name">分类排行</div><div class="commands"><code class="command">帮会排行 服务器</code><code class="command">阵营排行 服务器</code><code class="command">其他排行 服务器</code></div></div>
<div class="command-row"><div class="feature-name">试炼排行</div><div class="commands"><code class="command">试炼排行 服务器 心法</code></div></div>
</div>
+41
View File
@@ -0,0 +1,41 @@
{# template-title: 决斗挑战榜 #}
{# template-components: data-table #}
<div class="container data-page">
<h1>{{ name }}</h1>
<div class="data-page__meta">{{ server }} · {{ mode_name }}{% if update_time %} · 更新时间:{{ update_time }}{% endif %}</div>
<table class="data-table">
<thead>
<tr>
<th>排名</th>
<th>被悬赏</th>
<th>门派</th>
<th>赏金</th>
<th>剩余时间</th>
<th>发起人</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td class="data-cell--rank">{{ loop.index }}</td>
<td class="data-cell--name data-cell--nowrap">{{ item.name }}</td>
<td>
<div class="icon-label">
{% if icons.sect.get(item.forceName) %}
<img class="icon-label__icon" src="{{ icons.sect[item.forceName] }}" alt="{{ item.forceName }}">
{% endif %}
<span>{{ item.forceName }}</span>
</div>
</td>
<td class="data-cell--score">{{ item.money }}</td>
<td class="data-cell--time">{{ item.remainingTime }}</td>
<td class="data-cell--name data-cell--wrap">{{ item.payerName }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
+34
View File
@@ -0,0 +1,34 @@
{# template-title: 跨服名剑榜 #}
{# template-components: data-table #}
<div class="container data-page">
<h1>{{ name }}</h1>
<div class="data-page__meta">{{ server }} · {{ mode_name }}{% if update_time %} · 更新时间:{{ update_time }}{% endif %}</div>
<table class="data-table">
<thead>
<tr>
<th>排名</th>
<th>队伍名称</th>
<th>积分</th>
<th>胜场</th>
<th>败场</th>
<th>总场数</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td class="data-cell--rank">{{ loop.index }}</td>
<td class="data-cell--name data-cell--wrap">{{ item.corpsName }}</td>
<td class="data-cell--score">{{ item.corpsLevel }}</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.seasonWinCount }}</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.seasonLoseCount }}</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.seasonTotalCount }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
+9 -5
View File
@@ -11,7 +11,6 @@
<th>排名</th>
<th>头像</th>
<th>角色</th>
<th>区服</th>
<th>门派</th>
<th>分数</th>
<th>胜率</th>
@@ -35,11 +34,16 @@
<td>{{ m.roleName }}</td>
<td>{{ m.zoneName }} · {{ m.serverName }}</td>
<td>
<div class="icon-label">
{% if icons.sect.get(m.forceName) %}
<img class="icon-label__icon" src="{{ icons.sect[m.forceName] }}" alt="{{ m.forceName }}">
{% endif %}
<span>{{ m.forceName }}</span>
</div>
</td>
<td>{{ m.forceName }}</td>
<td>{{ m.score }}</td>
<td>{{ m.matchScore }}</td>
<td class="winrate data-cell--numeric">{{ m.winRate }}</td>
</tr>
+4 -1
View File
@@ -33,7 +33,10 @@
</td>
<td class="data-cell--name data-cell--nowrap">{{ item.name }}</td>
<td class="data-cell--nowrap">{{ item.level }}</td>
<td class="data-cell--nowrap">{{ item.campName }}</td>
{% set camp_name = item.campName or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(camp_name) or icons.img.get('未知') }}" alt="{{ camp_name }}" title="{{ camp_name }}">
</td>
<td class="data-cell--wrap">{{ item.tongName }}</td>
<td class="data-cell--score">{{ item.totalScore }}</td>
</tr>
+4 -4
View File
@@ -21,10 +21,10 @@
{% for item in items %}
<tr>
<td class="data-cell--rank">{{ loop.index }}</td>
<td class="data-cell--name">{{ item.role_name }}</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.equip_score }}</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.max_level }}</td>
<td class="data-cell--score">{{ item.total_score }}</td>
<td class="data-cell--name">{{ item.roleName }}</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.equipScore }}</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.maxLevel }}</td>
<td class="data-cell--score">{{ item.totalScore }}</td>
</tr>
{% endfor %}
</tbody>
+4 -1
View File
@@ -38,7 +38,10 @@
<td class="data-cell--nowrap">{{ item.roleLevel }}</td>
<td class="data-cell--nowrap">{{ item.bodyName }}</td>
<td>{{ item.tongName }}</td>
<td class="data-cell--nowrap">{{ item.campName }}</td>
{% set camp_name = item.campName or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(camp_name) or icons.img.get('未知') }}" alt="{{ camp_name }}" title="{{ camp_name }}">
</td>
<td class="data-cell--copy">{{ item.comment }}</td>
</tr>
{% endfor %}
+43
View File
@@ -0,0 +1,43 @@
{# template-title: 武林争霸赛 #}
{# template-components: data-table #}
<div class="container data-page">
<h1>{{ name }}</h1>
<div class="data-page__meta">{{ server }} · {{ camp_name }}{% if update_time %} · 更新时间:{{ update_time }}{% endif %}</div>
<table class="data-table">
<thead>
<tr>
<th>排名</th>
<th>帮会名称</th>
<th>帮主</th>
<th>所属服务器</th>
<th>阵营</th>
<th>胜场</th>
<th>败场</th>
<th>积分</th>
<th>最后比赛时间</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td class="data-cell--rank">{{ loop.index }}</td>
<td class="data-cell--name data-cell--wrap">{{ item.tongName }}</td>
<td class="data-cell--name data-cell--wrap">{{ item.masterName }}</td>
<td class="data-cell--nowrap">{{ item.serverName }}</td>
{% set camp_name = item.campName or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(camp_name) or icons.img.get('未知') }}" alt="{{ camp_name }}" title="{{ camp_name }}">
</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.winCount }}</td>
<td class="data-cell--nowrap data-cell--numeric">{{ item.loseCount }}</td>
<td class="data-cell--score">{{ item.totalScore }}</td>
<td class="data-cell--time">{{ item.lastMatchTimeText }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
+7 -7
View File
@@ -16,34 +16,34 @@
</div>
</div>
<!-- 头像 + 当前 3v3 表现 -->
<!-- 当前查询模式表现 -->
<div class="stats-wrap">
<!-- 右侧数据 -->
<div class="stats">
<div class="stat-box">
<div class="stat-title">名剑积分</div>
<div class="stat-value mmr">{{ performance['3v3'].mmr }}</div>
<div class="stat-value mmr">{{ currentPerformance.mmr }}</div>
</div>
<div class="stat-box">
<div class="stat-title">名剑段位</div>
<div class="stat-value">{{ performance['3v3'].grade }}</div>
<div class="stat-value">{{ currentPerformance.grade }}</div>
</div>
<div class="stat-box">
<div class="stat-title">胜率</div>
<div class="stat-value winrate">{{ performance['3v3'].winRate }}%</div>
<div class="stat-value winrate">{{ currentPerformance.winRate }}%</div>
</div>
<div class="stat-box">
<div class="stat-title">总场数</div>
<div class="stat-value">{{ performance['3v3'].totalCount }}</div>
<div class="stat-value">{{ currentPerformance.totalCount }}</div>
</div>
<div class="stat-box">
<div class="stat-title">MVP次数</div>
<div class="stat-value">{{ performance['3v3'].mvpCount }}</div>
<div class="stat-value">{{ currentPerformance.mvpCount }}</div>
</div>
</div>
@@ -66,7 +66,7 @@
<tbody>
{% for m in history %}
<tr>
<tr class="{{ 'match-win' if m.won else 'match-lose' }}">
<td class="time-col start-time" data-ts="{{ m.startTime }}">{{ m.startTime }}</td>
<td class="duration-col">{{ m.endTime - m.startTime }}</td>
<td class="kungfu-col">{{ m.kungfu }}</td>
+4 -1
View File
@@ -29,7 +29,10 @@
<td>{{ m.server }}</td>
<td>{{ m.itemName }}</td>
<td>{{ m.roleName }}</td>
<td>{{ m.campName }}</td>
{% set camp_name = m.campName or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(camp_name) or icons.img.get('未知') }}" alt="{{ camp_name }}" title="{{ camp_name }}">
</td>
<td>{{ m.itemAmount }}</td>
<td>{{ m.time }}</td>
</tr>
+6 -3
View File
@@ -20,13 +20,16 @@
<tbody>
{% for item in items %}
<tr
{% if item.camp_name == '恶人谷' %}
{% if item.campName == '恶人谷' %}
class="camp-eren"
{% elif item.camp_name == '浩气盟' %}
{% elif item.campName == '浩气盟' %}
class="camp-haoqi"
{% endif %}
>
<td class="data-cell--camp data-cell--nowrap data-cell--strong">{{ item.campName }}</td>
{% set camp_name = item.campName or '未知' %}
<td class="data-cell--camp data-cell--nowrap">
<img class="data-camp-icon" src="{{ icons.img.get(camp_name) or icons.img.get('未知') }}" alt="{{ camp_name }}" title="{{ camp_name }}">
</td>
<td class="data-cell--nowrap data-cell--strong">{{ item.fenxianName }}</td>
<td class="data-cell--nowrap data-cell--strong">{{ item.friendName }}</td>
<td class="data-cell--name data-cell--copy">{{ item.roleName }}</td>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

+8
View File
@@ -353,6 +353,14 @@
background: var(--jx3-surface-muted);
}
.jx3-template .data-camp-icon {
display: block;
width: 46px;
height: 40px;
margin: 0 auto;
object-fit: contain;
}
.jx3-template .data-table tr.camp-haoqi {
color: var(--jx3-blue);
}
+8
View File
@@ -83,6 +83,14 @@
font-weight: bold;
}
.data-table tr.match-win > td {
color: var(--jx3-green);
}
.data-table tr.match-lose > td {
color: var(--jx3-red);
}
.mvp {
color: #f5a623;
font-weight: bold;