import json import html import re from datetime import datetime, timedelta from typing import Dict, Any, Optional, List, Union from inspect import isawaitable from typing import Any, Awaitable, Callable, Dict, Optional from astrbot.api import logger from astrbot.api import AstrBotConfig import astrbot.api.message_components as Comp from .request import APIClient from .sqlite import AsyncSQLiteDB from .fun_basic import load_template,gold_to_parts,week_to_num,compare_date_str,format_time,format_remaining ACHIEVEMENT_CHOICES = [ (0, None, "资历总览"), (1, "1", "杂闻总览"), (2, "2", "武学总览"), (3, "3", "修为总览"), (4, "4", "装备总览"), (5, "5", "技艺总览"), (6, "6", "阅读总览"), (7, "7", "任务总览"), (8, "8", "足迹总览"), (9, "9", "战斗总览"), (10, "10", "声望总览"), (11, "11", "秘境总览"), (12, "12", "帮会总览"), (13, "13", "阵营总览"), (14, "15", "节日总览"), (15, "16", "活动总览"), (16, "17", "风雨江湖路总览"), (17, "40", "家园总览"), (18, "41", "剑侠录总览"), ] ACHIEVEMENT_CHOICE_MAP = {index: (menu_id, title) for index, menu_id, title in ACHIEVEMENT_CHOICES} class JX3APIService: def __init__(self, config: AstrBotConfig, sqlite: AsyncSQLiteDB, cache_sqlite: Optional[AsyncSQLiteDB] = None): # 实例化 API Client self._api: APIClient = APIClient() # 引用插件配置文件 self._config = config # 引用sqlite self._sql_db = sqlite self._cache_db = cache_sqlite or sqlite # 获取配置中的 Token self.token = self._config.get("jx3api_token", "") if self.token == "": logger.warning("获取配置token失败,请正确填写token,否则部分功能无法正常使用") else: logger.debug(f"获取配置token成功。{self.token}") # 获取配置中的 ticket self.ticket = self._config.get("jx3api_ticket", "") if self.ticket == "": logger.warning("获取配置ticket失败,请正确填写ticket,否则部分功能无法正常使用") else: logger.debug(f"获取配置ticket成功。{self.ticket}") async def close(self): """释放底层 APIClient 资源""" if self._api: await self._api.close() def _init_return_data(self) -> Dict[str, Any]: """初始化标准的返回数据结构""" return { "code": 0, "msg": "功能函数未执行", "data": {}, "temp": "", "icons": {} } async def _base_request( self, api_path: str, params: Optional[Dict[str, Any]] = None, out: Optional[str] = "data" ) -> Optional[Any]: """ 基础请求封装,处理配置获取和API调用。 """ try: if not self._api: logger.error("API client is not initialized") return None base_url = "https://www.jx3api.com" api_url = base_url + api_path data = await self._api.get(api_url, params=params, out_key=out) if not data: logger.warning(f"获取接口信息失败或返回空数据: {api_url}") return data except Exception as e: logger.error(f"基础请求调用出错 ({api_path}): {e}") return None async def _request_api( self, path: str, params: Dict[str, Any], processor: Optional[ Callable[[Any, Dict[str, Any]], Any | Awaitable[Any]] ] = None, template: Optional[str] = None, ) -> Dict[str, Any]: """通用接口请求与模板处理。""" return_data = self._init_return_data() data = await self._base_request(path, params) if data is None: return_data["msg"] = "获取接口信息失败" return return_data try: await processor(data, return_data) except Exception as e: logger.exception(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data # template 为空时不加载模板 if template: try: return_data["temp"] = await load_template(template) except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data # --- 业务功能函数 --- async def helps(self) -> Dict[str, Any]: """帮助""" return_data = self._init_return_data() # 加载模板 try: return_data["temp"] = await load_template("helps.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def richang(self, mode: str, num: int) -> Dict[str, Any]: """活动日历""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: if mode == "list": today = data.get("today") or {} total = data.get("total") or [] items = [ { "en": False, "compare": "", "date": "", "war": "", "battle": "", } for _ in range(week_to_num(today.get("week", "")) or 0) ] items.extend( { "en": True, "compare": compare_date_str(item.get("date", "")), "date": item.get("date", ""), "war": item.get("war", ""), "battle": item.get("battle", ""), } for item in total if isinstance(item, dict) ) return_data["data"]["items"] = items return_data["data"]["today"] = today return weekly = data.get("weekly") or {} return_data["data"] = ( f"{data.get('date', '')} 星期{data.get('week', '')}\n" f"大战:{data.get('war', '')}\n" f"战场:{data.get('battle', '')}\n" f"阵营:{data.get('orecar', '')}\n" f"宗门:{data.get('school', '')}\n" f"驰援:{data.get('rescue', '')}\n" f"【宠物福缘】\n{', '.join(data.get('lucky') or [])}\n" f"【家园声望·加倍道具】\n{', '.join(data.get('card') or [])}\n" f"【武林通鉴·公共任务】\n{', '.join(weekly.get('conn') or [])}\n" f"【武林通鉴·团队秘境】\n{', '.join(weekly.get('raid') or [])}\n" ) return await self._request_api( path="/active/calendar", params={"mode": mode, "num": num}, processor=processor, template="richangyuche.html" if mode == "list" else None, ) async def xingxiashijian(self,name: str) -> Dict[str, Any]: """地图活动""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"]["items"] = data return_data["data"]["name"] = name return await self._request_api( path="/active/celebs", params={ "name": name}, processor=processor, template="xingxiashijian.html" ) async def guanaishouling(self) -> Dict[str, Any]: """关隘首领""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: groups = [ { "server": group.get("server", ""), "records": [ { "camp_name": item.get("campName", ""), "castle": item.get("castle", ""), "str_status": item.get("statusText", ""), "start_time": format_time(item.get("startTime")), "end_time": format_time(item.get("endTime")), "remaining_time": format_remaining(item.get("endTime")), } for item in group.get("data", []) if isinstance(item, dict) ], } for group in data if isinstance(group, dict) and group.get("data") ] groups = [group for group in groups if group["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") } return await self._request_api( path="/castle/status", params={"token": self.token}, processor=processor, template="guanaishouling.html" ) async def benrichitu(self) -> Dict[str, Any]: """本日赤兔""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: if not data or not isinstance(data, list): return_data["data"] = "今日赤兔 暂无数据" return result_lines = ["今日赤兔"] for item in data: result_lines.extend([ f"时间:{item['date']}", f"区服:{item['server']}", f"地图:{item['mapName']}", ]) return_data["data"] = "\n".join(result_lines).rstrip() return await self._request_api( path="/chitu/records", params={"token": self.token}, processor=processor, template="" ) async def benzhouchitu(self) -> Dict[str, Any]: """本周赤兔""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: if not data or not isinstance(data, list): return_data["data"] = "本周赤兔 暂无数据" return result_lines = ["本周赤兔"] for item in data: result_lines.extend([ f"时间:{item['date']}", f"区服:{item['server']}", f"地图:{item['mapName']}", ]) return_data["data"] = "\n".join(result_lines).rstrip() return await self._request_api( path="/chitu/week/records", params={"token": self.token}, processor=processor, template="" ) async def zhenyingevent(self,name: str,limit: str) -> Dict[str, Any]: """阵营事件""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: item["seizeTime"] = format_time(item.get("seizeTime")) return_data["data"] = { "items": data, "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } return await self._request_api( path="/fenxian/records", params={"token": self.token,"name": name, "limit": limit}, processor=processor, template="zhenyingevent.html" ) async def yanhuachaxun(self, server: str, name:str ) -> Dict[str, Any]: """烟花记录""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: item["time"] = format_time(item.get("time")) return_data["data"]["list"] = data return await self._request_api( path="/firework/records", params={"token": self.token,"name": name, "server": server}, processor=processor, template="yanhuan.html" ) async def shuma(self,server:str) -> Dict[str, Any]: """刷马""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"] = ( f"【阴山大草原】\n{', '.join(data.get('阴山大草原') or [])}\n" f"【鲲鹏岛】\n{', '.join(data.get('鲲鹏岛') or [])}\n" f"【黑戈壁】\n{', '.join(data.get('黑戈壁') or [])}\n" ) return await self._request_api( path="/ranch/chat", params={"token": self.token, "server": server}, processor=processor, template="" ) async def machang(self, server: str, expired: int) -> Dict[str, Any]: """马场""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: data0 = data.get("data") return_data["data"] = ( f"【区服】:{data.get('server')}\n" f"【阴山大草原】\n{', '.join(data0.get('阴山大草原') or [])}\n" f"【鲲鹏岛】\n{', '.join(data0.get('鲲鹏岛') or [])}\n" f"【黑戈壁】\n{', '.join(data0.get('黑戈壁') or [])}\n" f"【龙泉府 / 进图(21:10)】\n{', '.join(data0.get('龙泉府 / 进图(21:10)') or [])}\n" f"\n{data.get('note')}\n" ) return await self._request_api( path="/ranch/records", params={"token": self.token, "server": server, "expired": expired}, processor=processor, template="" ) async def zhanji(self, name: str, server:str, mode:str) -> Dict[str, Any]: """战绩""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"] = data return await self._request_api( path="/arena/recent", params={"server": server, "name":name, "mode":mode, "token": self.token, "ticket": self.ticket}, processor=processor, template="zhanji.html" ) async def mingjianpaihang(self, limit: str, mode:str) -> Dict[str, Any]: """名剑排行""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"]["lists"] = data return await self._request_api( path="/arena/awesome", params={"limit": limit, "mode":mode, "token": self.token, "ticket": self.ticket}, processor=processor, template="mingjianpaihang.html" ) async def mingjiantongji(self, mode: str) -> Dict[str, Any]: """名剑统计""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"] = { "items": data, "mode": mode, "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } return await self._request_api( path="/arena/schools", params={"mode": mode, "token": self.token, "ticket": self.ticket}, processor=processor, template="mingjiantongji.html" ) async def rank_statistical(self, name: str, server: str) -> Dict[str, Any]: """排行榜单""" ROLE_RANK_NAMES = { "名士五十强", "老江湖五十强", "兵甲藏家五十强", "名师五十强", "阵营英雄五十强", "薪火相传五十强", "庐园广记一百强", } TONG_RANK_NAMES0 = { "赛季恶人五十强", "赛季浩气五十强", "本周恶人五十强", "本周浩气五十强", } TONG_RANK_NAMES1 = { "浩气神兵宝甲五十强", "恶人神兵宝甲五十强", "浩气爱心帮会五十强", "恶人爱心帮会五十强", } TONG_RANK_NAMES2 = { "上周恶人五十强", "上周浩气五十强", } if name in ROLE_RANK_NAMES: template_name = "rank_role.html" elif name in TONG_RANK_NAMES0: template_name = "rank_tong0.html" elif name in TONG_RANK_NAMES1: template_name = "rank_tong1.html" elif name in TONG_RANK_NAMES2: template_name = "rank_tong2.html" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: items = data.get("data", []) return_data["data"] = { "items": items, "server": data.get("server", server), "rank_name": data.get("name", name), "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } return await self._request_api( path="/rank/statistics", params={"server": server, "name": name, "token": self.token}, processor=processor, template=template_name ) async def shilianpaixing(self, name: str, server: str) -> Dict[str, Any]: """试炼排行""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: items = data.get("data", []) return_data["data"] = { "items": items, "name": data.get("name", name), "server": data.get("server", server), "update_time": format_time(data.get("time")) } return await self._request_api( path="/rank/trials", params={"server": server,"name": name,"token": self.token,}, processor=processor, template="shilianpaixing.html" ) async def zhengyingpaimai(self, server: str, name: str, limit: int) -> Dict[str, Any]: """阵营拍卖""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: item["time"] = format_time(item["time"]) return_data["data"]["list"] = data return await self._request_api( path="/auction/records", params={"server": server, "name": name, "limit": limit, "token": self.token}, processor=processor, template="zhengyingpaimai.html" ) async def dilujilu(self, server: str) -> Dict[str, Any]: """的卢拍卖""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: item["refreshTime"] = format_time(item["refreshTime"]) item["captureTime"] = format_time(item["captureTime"]) item["auctionTime"] = format_time(item["auctionTime"]) return_data["data"]["list"] = data return await self._request_api( path="/steed/records", params={"server": server, "token": self.token}, processor=processor, template="dilujilu.html" ) async def jinjia(self, server: str, limit:str) -> Dict[str, Any]: """金价行情""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"]["items"] = data return await self._request_api( path="/trade/demon", params={"server": server, "limit": limit, "token": self.token}, processor=processor, template="jinjia.html" ) async def wujia(self, Name: str, server:str) -> Dict[str, Any]: """物价查询""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"] = data return await self._request_api( path="/trade/records", params={"name": Name,"token": self.token, "server": server}, processor=processor, template="wujia.html" ) async def chengbeng(self, Name: str, server:str, source: int) -> Dict[str, Any]: """成本计算""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"] = data return await self._request_api( path="/trade/manufacture", params={"name": Name,"token": self.token, "server": server, "source": source}, processor=processor, template="chengbeng.html" ) async def bianhao(self, id: str) -> Dict[str, Any]: """编号搜索""" # 数据处理 async def processor(data: Any, return_data: Dict[str, Any]) -> None: if not isinstance(data, dict): return_data["data"] = "账号角色数据格式错误" return # 同时兼容完整接口数据和直接传入 data 字段 data = data.get("data", data) if not isinstance(data, dict): return_data["data"] = "账号角色数据为空" return # 将 replyContent 中的 HTML 转换为纯文本 detail = str(data.get("replyContent") or "") detail = re.sub(r"", "\n", detail, flags=re.IGNORECASE) detail = re.sub(r"<[^>]+>", "", detail) detail = html.unescape(detail).strip() or "暂无账号详细信息" # 交易状态 trade_status = { 1: "公示中", 2: "出售中", 3: "出售中", 4: "已售出", 5: "已下架", }.get(data.get("tradeStatus"), f"状态码 {data.get('tradeStatus', '未知')}") # 调价记录,接口中的时间为毫秒时间戳 update_prices = data.get("updatePrices") or [] update_price_text = "\n".join( f"{index}. {format_time(int(item.get('updateTime') or 0) // 1000)}:" f"{item.get('updatePrice', 0)} 元" for index, item in enumerate(update_prices, start=1) if isinstance(item, dict) ) or "暂无调价记录" return_data["data"] = ( f"【万宝楼账号】\n" f"{data.get('replyTitle') or '暂无标题'}\n\n" f"【角色信息】\n" f"区服:{data.get('serverName') or '未知'}\n" f"角色:{data.get('roleName') or '未知'}\n" f"等级:{data.get('roleLevel') or 0}\n" f"门派:{data.get('forceName') or '未知'}\n" f"体型:{data.get('bodyName') or '未知'}\n" f"阵营:{data.get('campName') or '未知'}\n\n" f"【账号数据】\n" f"装备分数:{data.get('equipScore') or 0}\n" f"江湖资历:{data.get('seniorityNum') or 0}\n" f"约见次数:{data.get('meetingNum') or 0}\n" f"关注人数:{data.get('followNum') or 0}\n\n" f"【交易信息】\n" f"挂牌价格:{data.get('priceNum') or 0} 元\n" f"交易状态:{trade_status}\n" f"商品编号:{data.get('id') or '未知'}\n" f"发布时间:{format_time(data.get('replyTime') or 0)}\n\n" f"【调价记录】\n" f"{update_price_text}\n\n" f"【账号详情】\n" f"{detail}" ) return await self._request_api( path="/trade/wanbaolou", params={"id": id,"token": self.token}, processor=processor, template="" ) async def bangzhanjilu(self, server: str) -> Dict[str, Any]: """帮战记录""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: item["startTime"] = format_time(item["startTime"]) item["durationSeconds"] = format_remaining(item["durationSeconds"]) item["endTime"] = format_time(item["endTime"]) return_data["data"] = { "items": data, "server": server, "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } return await self._request_api( path="/battle/records", params={"server": server}, processor=processor, template="bangzhanjilu.html" ) async def zhueevent(self,server: str,limit: str) -> Dict[str, Any]: """诛恶事件""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: item["time"] = format_time(item["time"]) return_data["data"] = { "items": data, "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } return await self._request_api( path="/wicked/records", params={"token": self.token, "server": server, "limit": limit}, processor=processor, template="zhueevent.html" ) async def jueshemingpian(self, server: str, name: str) -> Dict[str, Any]: """名片缓存""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: url = data.get("showAvatar") if not url: return_data["msg"] = "未获取到名片图片" return return_data server_name = data.get("serverName", server) role_name = data.get("roleName", name) show_like = data.get("showLike", 0) msg0 = f"{server_name}-{role_name}" msg1 = f"点赞:{show_like}" return_data["data"] = [ Comp.Plain(msg0), Comp.Image.fromURL(url), Comp.Plain(msg1) ] return await self._request_api( path="/card/cached", params={"server": server, "name": name, "token": self.token}, processor=processor, template="" ) async def shuijimingpian(self, server:str, force: str, body:str,) -> Dict[str, Any]: """随机名片""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: url = data.get("showAvatar") if not url: return_data["msg"] = "未获取到名片图片" return return_data server_name = data.get("serverName") role_name = data.get("roleName") msg = f"{server_name}-{role_name}" return_data["data"] = [ Comp.Plain(msg), Comp.Image.fromURL(url), ] return await self._request_api( path="/card/random", params={"server": server, "body": body, "force":force, "token": self.token}, processor=processor, template="" ) async def shuoyoumingpian(self, server: str, name: str) -> Dict[str, Any]: """名片历史""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: chain = [] for m in data: status = "当前展示" if m.get("showActive") else "未展示" msg = f"第{m.get('showIndex')}张 {status}" url = m.get("showAvatar") if not url: logger.warning(f"第{m.get('showIndex')}张名片缺少图片URL,已跳过") continue chain.extend([ Comp.Plain(msg), Comp.Image.fromURL(url), ]) if not chain: return_data["msg"] = "未获取到有效的名片数据" return return_data return_data["data"] = chain return await self._request_api( path="/card/records", params={"server": server, "name": name, "token": self.token}, processor=processor, template="" ) async def qiyuhuizong(self, server: str, num: int) -> Dict[str, Any]: """奇遇汇总""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: latest = item.get("last") item["latest_name"] = latest.get("name") item["latest_time"] = format_time(latest.get("time")) return_data["data"] = { "items": data, "server": server, "num": num, "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } return await self._request_api( path="/event/collect", params={"server": server, "num": num, "token": self.token}, processor=processor, template="qiyuhuizong.html" ) async def weizuoqiyu(self, server: str, name: str, ) -> Dict[str, Any]: """未出奇遇""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"]["ptqy"] = [] return_data["data"]["jsqy"] = [] return_data["data"]["name"] = name return_data["data"]["server"] = server for item in data: try: level = int(item.get("level", 0)) except (TypeError, ValueError): continue event_item = { "event": item.get("name"), "time": "未触发" } if level == 1: return_data["data"]["ptqy"].append(event_item) elif level == 2: return_data["data"]["jsqy"].append(event_item) if not return_data["data"]["ptqy"] and not return_data["data"]["jsqy"]: return_data["msg"] = "未查询到未做普通或绝世奇遇" return return_data return await self._request_api( path="/event/missing", params={"server": server, "name": name, "token": self.token}, processor=processor, template="weizuoqiyu.html" ) async def jinqiqiyu(self, server: str, limit: int) -> Dict[str, Any]: """近期奇遇""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: item["time"] = format_time(item.get("time")) return_data["data"] = { "items": data, "server": server, "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } return await self._request_api( path="/event/recent", params={"server": server, "limit": limit, "token": self.token}, processor=processor, template="jinqiqiyu.html" ) async def juesheqiyu(self, server: str, name: str, full: int) -> Dict[str, Any]: """角色奇遇""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: return_data["data"]["ptqy"] = [] return_data["data"]["jsqy"] = [] return_data["data"]["cwqy"] = [] for item in data: item["time"] = datetime.fromtimestamp(item["time"]).strftime("%Y-%m-%d %H:%M:%S") if item["level"] == 1: return_data["data"]["ptqy"].append(item) if item["level"] == 2: return_data["data"]["jsqy"].append(item) if item["level"] == 3: return_data["data"]["cwqy"].append(item) return await self._request_api( path="/event/records", params= {"server": server, "name": name, "full": full, "token": self.token}, processor=processor, template="juesheqiyu.html" ) async def qiyutongji(self, name: str, server: str, limit: int) -> Dict[str, Any]: """奇遇统计""" async def processor(data: Any, return_data: Dict[str, Any]) -> None: for item in data: item["time"] = format_time(item.get("time")) return_data["data"] = { "items": data, "server": server, "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "qiyuname": name } return await self._request_api( path="/event/statistics", params={"name": name, "server": server, "limit": limit, "token": self.token}, processor=processor, template="qiyuliebiao.html" ) async def keju(self,subject: str, limit: int) -> Dict[str, Any]: """科举""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"subject": subject, "limit": limit} # 2. 调用基础请求 data: Optional[List[Dict[str, Any]]] = await self._base_request( "jx3_keju", "GET", params=params ) if not data: return_data["msg"] = "未查询到相关题目" return return_data # 3. 处理返回数据 try: # 格式化字符串,利用字典的 get 方法提供默认值 result_msg = "" for m in data: result_msg += f"{m['id']}.{m['question']}\n" result_msg += f"答案:{m['answer']}\n\n" return_data["data"] = result_msg except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data return_data["code"] = 200 return return_data async def huajia(self,server: str, name: str, map: str) -> Dict[str, Any]: """花价""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"server": server, "name": name, "map": map} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_huajia", "GET", params=params ) if not data: return_data["msg"] = "未查询到相关内容" return return_data # 3. 处理返回数据 try: return_data["data"]["data"] = data return_data["data"]["server"] = server except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data # 加载模板 try: return_data["temp"] = await load_template("huajia.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def zhuangshi(self,name: str) -> Dict[str, Any]: """装饰""" return_data = self._init_return_data() # 1. 构造请求参数 params = { "name": name} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_zhuangshi", "GET", params=params ) if not data: return_data["msg"] = "未查询到相关内容" return return_data # 3. 处理返回数据 try: return_data["data"]["data"] = data except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data # 加载模板 try: return_data["temp"] = await load_template("zhuangshi.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def qiwu(self,name: str) -> Dict[str, Any]: """器物""" return_data = self._init_return_data() # 1. 构造请求参数 params = { "name": name} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_qiwu", "GET", params=params ) if not data: return_data["msg"] = "未查询到相关内容" return return_data # 3. 处理返回数据 try: return_data["data"]["data"] = data return_data["data"]["name"] = name except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data # 加载模板 try: return_data["temp"] = await load_template("qiwu.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def xinwen(self, num:int) -> Dict[str, Any]: """新闻资讯""" return_data = self._init_return_data() params = {"limit": num} # 提取字段可能返回列表 data: Optional[List[Dict[str, Any]]] = await self._base_request( "jx3_xinweng", "GET", params=params) if not data or not isinstance(data, list): return_data["msg"] = "获取接口信息失败或数据格式错误" return return_data try: # result = data[0] return_data["status"] = result.get('id') result_msg = "新闻资讯推送\n" # 仅展示前1条,避免消息过长 for i, item in enumerate(data[:num], 1): result_msg += f"{i}. 【{item.get('type', '无类型')}】\n" result_msg += f"标题:{item.get('title', '未知时间')}\n" result_msg += f"时间:{item.get('date', '未知时间')}\n" result_msg += f"链接:{item.get('url', '无链接')}\n" return_data["data"] = result_msg except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return_data["code"] = 200 return return_data async def weihu(self, num:int) -> Dict[str, Any]: """维护""" return_data = self._init_return_data() params = {"limit": num} # 提取字段可能返回列表 data: Optional[List[Dict[str, Any]]] = await self._base_request( "jx3_weihu", "GET", params=params) if not data or not isinstance(data, list): return_data["msg"] = "获取接口信息失败或数据格式错误" return return_data try: # result = data[0] return_data["status"] = result.get('id') result_msg = "维护推送\n" # 仅展示前1条,避免消息过长 for i, item in enumerate(data[:num], 1): result_msg += f"{i}. 【{item.get('type', '无类型')}】\n" result_msg += f"标题:{item.get('title', '未知时间')}\n" result_msg += f"时间:{item.get('date', '未知时间')}\n" result_msg += f"链接:{item.get('url', '无链接')}\n" return_data["data"] = result_msg except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return_data["code"] = 200 return return_data async def qufu(self,name: str) -> Dict[str, Any]: """区服信息""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"name": name} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_qufu", "GET", params=params ) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 3. 处理返回数据 try: # 格式化字符串,利用字典的 get 方法提供默认值 result_msg = f"主服:{data.get('zone', '无')}-{data.get('name', '无')}\n" slave = data.get('slave', []) slave_msg = f"区服:{', '.join(slave)}\n" alias = data.get('alias', []) alias_msg = f"别名:{', '.join(alias)}\n" return_data["data"] = result_msg + slave_msg + alias_msg return_data["code"] = 200 except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data return_data["code"] = 200 return return_data async def kaifu(self, server: str) -> Dict[str, Any]: """开服状态查询""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"server": server,"type": "1"} # 2. 调用基础请求 data: Optional[Dict[str, Union[int, str]]] = await self._base_request( "jx3_zhuangtai", "GET", params=params ) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 3. 处理返回数据 try: status = data.get("status", 0) lasttime = data.get("lasttime", 0) shuttime = data.get("shuttime", 0) lasttime_t = datetime.fromtimestamp(float(lasttime)).strftime("%Y-%m-%d %H:%M:%S") shuttime_t = datetime.fromtimestamp(float(shuttime)).strftime("%Y-%m-%d %H:%M:%S") if status == 1: status_str = f"{server}服务器已开服,快冲,快冲!\n开服时间:{lasttime_t}" status_bool = True else: status_str = f"{server}服务器当前维护中,等会再来吧!\n维护时间:{shuttime_t}" status_bool = False return_data["status"] = status_bool return_data["data"] = status_str except Exception as e: logger.error(f"kaifu 数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data return_data["code"] = 200 return return_data async def zhuangtai(self) -> Dict[str, Any]: """区服状态""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"server": "","type": "2"} data: Optional[List[Dict[str, Any]]] = await self._base_request("jx3_zhuangtai", "GET", params=params) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 处理返回数据 try: server_wj = [] server_dx = [] server_sx = [] for itme in data: if itme['zone'] == "无界区": server_wj.append(itme) elif itme['zone'] == "电信区": server_dx.append(itme) elif itme['zone'] == "双线区": server_sx.append(itme) return_data["data"]["server_wj"] = server_wj return_data["data"]["server_dx"] = server_dx return_data["data"]["server_sx"] = server_sx except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" # 加载模板 try: return_data["temp"] = await load_template("qufuzhuangtai.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def jigai(self) -> Dict[str, Any]: """技改记录""" return_data = self._init_return_data() # 提取字段可能返回列表 data: Optional[List[Dict[str, Any]]] = await self._base_request("jx3_jigai", "GET") if not data or not isinstance(data, list): return_data["msg"] = "获取接口信息失败或数据格式错误" return return_data try: result_msg = "最近技改\n" for i, item in enumerate(data[:3], 1): result_msg += f"{i}. {item.get('title', '无标题')}\n" result_msg += f"时间:{item.get('time', '未知时间')}\n" result_msg += f"链接:{item.get('url', '无链接')}\n\n" return_data["data"] = result_msg except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data return_data["code"] = 200 return return_data async def xiaoyao(self) -> Dict[str, Any]: """小药""" return_data = self._init_return_data() data: Optional[List[Dict[str, Any]]] = await self._base_request("jx3_xiaoyao", "GET") logger.debug(data) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 处理返回数据 try: result = {} for item in data: k = item["kungfu"] color = item["color"] cls = item["class"] name = item["name"] if k not in result: result[k] = { "kungfu": k, "purple": {}, "blue": {} } if color == "紫": result[k]["purple"][cls] = name else: result[k]["blue"][cls] = name return_data["data"]["items"] = list(result.values()) logger.debug(return_data["data"]) except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" # 加载模板 try: return_data["temp"] = await load_template("xiaoyao.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def zhenyan(self, name: str) -> Dict[str, Any]: """阵眼""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"name": name, "ticket": self.ticket, "token": self.token} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_zhenyan", "GET", params=params ) if not data or not isinstance(data, dict): return_data["msg"] = "未查询到该心法阵眼信息" return return_data # 3. 处理返回数据 try: items = data.get("data", []) if not isinstance(items, list) or not items: return_data["msg"] = "未查询到该心法阵眼信息" return return_data result_msg = f"{data.get('name', name)}-{data.get('skillName', '')}\n" for item in items: if not isinstance(item, dict): continue result_msg += f"{item.get('name', '')}:{item.get('desc', '')}\n" return_data["data"] = result_msg.rstrip() except Exception as e: logger.error(f"处理阵眼数据失败: {e}") return_data["msg"] = "处理阵眼数据失败" return return_data return_data["code"] = 200 return return_data async def qixue(self, name: str) -> Dict[str, Any]: """奇穴""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"name": name, "ticket": self.ticket, "token": self.token} # 2. 调用基础请求 data: Optional[List[Dict[str, Any]]] = await self._base_request( "jx3_qixue", "GET", params=params ) if not data or not isinstance(data, list): return_data["msg"] = "未查询到该心法奇穴信息" return return_data # 3. 处理返回数据 try: level_titles = { 1: "主奇穴", 2: "第一重", 3: "第二重", 4: "第三重", 5: "第四重", 6: "第五重", 7: "第六重", 8: "混池", } groups = [] for group in data: if not isinstance(group, dict): continue level = group.get("level") try: level_key = int(level) except (TypeError, ValueError): level_key = 0 items = group.get("data", []) if not isinstance(items, list) or not items: continue parsed_items = [] for item in items: if not isinstance(item, dict): continue class_value = item.get("class") is_active = class_value == 1 or str(class_value) == "1" parsed_items.append({ "name": item.get("name", ""), "icon": item.get("icon", ""), "desc": item.get("desc", ""), "class_text": "主动" if is_active else "被动", "interval": item.get("interval", "") if is_active else "", }) if parsed_items: groups.append({ "level": level_key, "title": level_titles.get(level_key, f"第{level_key}组"), "talents": parsed_items, }) if not groups: return_data["msg"] = "未查询到该心法奇穴信息" return return_data groups.sort(key=lambda item: item["level"]) return_data["data"] = { "name": name, "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 # 4. 加载模板 try: return_data["temp"] = await load_template("qixue.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def jineng(self, name: str) -> Dict[str, Any]: """技能""" return_data = self._init_return_data() params = {"name": name, "ticket": self.ticket, "token": self.token} data: Optional[List[Dict[str, Any]]] = await self._base_request( "jx3_jineng", "GET", params=params ) if not data or not isinstance(data, list): return_data["msg"] = "未查询到该心法技能信息" return return_data try: groups = [] for group in data: if not isinstance(group, dict): continue skills = group.get("data", []) if not isinstance(skills, list) or not skills: continue parsed_skills = [] for skill in skills: if not isinstance(skill, dict): continue parsed_skills.append({ "name": skill.get("name", ""), "icon": skill.get("icon", ""), "desc": skill.get("desc", ""), "interval": skill.get("interval", ""), "distance": skill.get("distance", ""), "release_type": skill.get("releaseType", ""), "weapon": skill.get("weapon", ""), }) if parsed_skills: groups.append({ "title": group.get("class", "其他技能"), "skills": parsed_skills, }) if not groups: return_data["msg"] = "未查询到该心法技能信息" return return_data return_data["data"] = { "name": name, "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("jineng.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def zilipaixing(self, school: str, server: str) -> Dict[str, Any]: """资历排行""" return_data = self._init_return_data() params = { "server": server, "school": school, "ticket": self.ticket, "token": self.token, } data: Optional[List[Dict[str, Any]]] = await self._base_request( "jx3_zilipaixing", "GET", params=params ) if not data or not isinstance(data, list): return_data["msg"] = "未查询到资历排行信息" return return_data try: return_data["data"] = { "items": data, "school": school, "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("zilipaixing.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() # 因为没有参数,所以 params=None data: Optional[Dict[str, Any]] = await self._base_request("jx3_shaohua", "GET") if not data: return_data["msg"] = "获取接口信息失败" return return_data text = data.get("text") if text: return_data["data"] = text else: return_data["msg"] = "接口未返回文本" return return_data return_data["code"] = 200 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 baizhan(self) -> Dict[str, Any]: """百战首领""" return_data = self._init_return_data() # 1. 构造请求参数 params = { "token": self.token} data: Optional[Dict[str, Any]] = await self._base_request("jx3_baizhan", "GET", params=params) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 处理返回数据 try: return_data["data"] = data return_data["data"]["start"] = datetime.fromtimestamp(float(data["start"])).strftime("%Y-%m-%d %H:%M:%S") return_data["data"]["end"] = datetime.fromtimestamp(float(data["end"])).strftime("%Y-%m-%d %H:%M:%S") except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" # 加载模板 try: return_data["temp"] = await load_template("baizhan.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def fuyaojjiutian(self, server: str) -> Dict[str, Any]: """扶摇九天""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"server": server, "token": self.token} # 2. 调用基础请求 data: Optional[list[Dict[str, Any]]] = await self._base_request( "jx3_fuyaojiutian", "GET", params=params ) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 3. 处理返回数据 try: data_new = data[0] data_time = datetime.fromtimestamp(data_new['time']).strftime('%Y-%m-%d %H:%M:%S') result_msg = f"{server}\n" if data_new["status"] == 0: result_msg += f"本轮扶摇九天尚未开启\n" if data_new["status"] == 1: result_msg += f"本轮扶摇九天正在进行\n" if data_new["status"] == 2: result_msg += f"本轮扶摇九天已经结束\n" result_msg += f"开启时间:{data_time}\n" return_data["data"] = result_msg except Exception 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 pianzhi(self, uid: str) -> Dict[str, Any]: """骗子查询""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"uid": uid, "token": self.token} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_pianzhi", "GET", params=params ) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 3. 处理返回数据 try: records = data["records"] if not records: result_msg = "未找到该用户行骗记录,很棒!继续保持!" else: result_msg = "" for record in records: result_msg += f"区服:{record['server']} 标签:{record['tieba']}\n\n" for item in record["data"]: result_msg += f"标题:{item['title']}\n" result_msg += f"地址:{item['url']}\n" result_msg += f"ID:{item['tid']}\n" result_msg += f"内容:{item['text']}\n" result_msg += ( f"时间:{datetime.fromtimestamp(item['time']).strftime('%Y-%m-%d %H:%M:%S')}\n\n" ) result_msg += "\n\n" return_data["data"] = result_msg except Exception as e: logger.error(f"处理返回数据失败: {e}") return_data["msg"] = "处理返回数据失败" return return_data return_data["code"] = 200 return return_data async def tuanduizhaomu(self, server: str, keyword: str) -> Dict[str, Any]: """团队招募""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"server": server, "keyword": keyword, "token": self.token} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_tuanduizhaomu", "GET", params=params ) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 3. 处理返回数据 try: for item in data["data"]: item["createTime"] = datetime.fromtimestamp(item["createTime"]).strftime("%Y-%m-%d %H:%M:%S") item["number"] = f"{item['number']}/{item['maxNumber']}" return_data["data"]["list"] = data["data"] except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data # 4. 加载模板 try: return_data["temp"] = await load_template("tuanduizhaomu.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def shitu(self, type_value: int, keyword: str, server: str) -> Dict[str, Any]: """师徒招募""" return_data = self._init_return_data() if type_value not in {1, 2}: return_data["msg"] = "师徒查询类型错误" return return_data # 1. 构造请求参数 params = {"type": type_value, "server": server, "keyword": keyword, "token": self.token} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_shitu", "GET", params=params ) if not data or not isinstance(data, dict): return_data["msg"] = "未查询到师徒招募信息" return return_data # 3. 处理返回数据 try: items = data.get("data", []) if not items: return_data["msg"] = "未查询到师徒招募信息" return return_data title = "收徒信息" if type_value == 1 else "拜师信息" return_data["data"] = { "items": items, "server": data.get("server", server), "keyword": keyword, "type_value": type_value, "title": title, "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 # 4. 加载模板 try: return_data["temp"] = await load_template("shitu.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def _load_achievement_cache(self, key: str) -> tuple[Optional[Any], bool]: """读取资历基础数据缓存,返回数据和是否已过期""" try: row = await self._cache_db.select_one("achievement_cache", "key=?", (key,)) except Exception as e: logger.error(f"读取资历缓存失败: {e}") return None, True if not row: return None, True try: payload = json.loads(row.get("content", "{}")) updated_at = datetime.strptime(row.get("updated_at", ""), "%Y-%m-%d %H:%M:%S") expired = datetime.now() - updated_at > timedelta(days=30) return payload, expired except Exception as e: logger.error(f"解析资历缓存失败: {e}") return None, True async def _save_achievement_cache(self, key: str, payload: Any): """写入资历基础数据缓存""" try: await self._cache_db.execute( """ INSERT INTO achievement_cache (key, content, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET content=excluded.content, updated_at=excluded.updated_at """, ( key, json.dumps(payload, ensure_ascii=False), datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ), ) except Exception as e: logger.error(f"写入资历缓存失败: {e}") async def _get_achievement_base_data(self, cache_key: str, config_key: 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") if data and isinstance(data, dict): await self._save_achievement_cache(cache_key, data) return data if cached: logger.warning(f"资历基础数据接口失败,使用旧缓存: {cache_key}") return cached return None async def _get_trade_item_groups(self) -> Optional[List[Dict[str, Any]]]: """获取交易行物品库,优先使用未过期缓存""" cache_key = "trade_item_groups" cached, expired = await self._load_achievement_cache(cache_key) if isinstance(cached, list) and not expired: return cached data = await self._base_request("jx3box_trade_items", "GET") if isinstance(data, list) and data: await self._save_achievement_cache(cache_key, data) return data if isinstance(cached, list) and cached: logger.warning("交易行物品库接口失败,使用旧缓存") return cached return None def _flatten_trade_items(self, groups: List[Dict[str, Any]]) -> list[Dict[str, Any]]: """从交易行物品分组中提取可查询物品""" items = [] for group in groups: if not isinstance(group, dict): continue for item in group.get("items", []) or []: if not isinstance(item, dict): continue item_id = item.get("item_id") label = item.get("label") if not item_id or not label: continue items.append( { "item_id": str(item_id), "label": str(label), "icon": str(item.get("icon") or ""), } ) return items def _match_trade_items(self, items: list[Dict[str, Any]], keyword: str, limit: int = 50) -> list[Dict[str, Any]]: """按物品名模糊匹配交易行物品""" keyword = (keyword or "").strip() if not keyword: return [] matched = [] seen = set() for item in items: label = item.get("label", "") item_id = item.get("item_id", "") if keyword not in label or item_id in seen: continue seen.add(item_id) if label == keyword: rank = 0 elif label.startswith(keyword): rank = 1 else: rank = 2 matched.append((rank, len(label), label, item)) matched.sort(key=lambda row: (row[0], row[1], row[2])) return [row[3] for row in matched[:limit]] def _flatten_achievement_ids(self, values: Any) -> list[int]: """展开菜单中的单个资历 ID 和数组资历 ID""" result = [] if isinstance(values, list): for item in values: result.extend(self._flatten_achievement_ids(item)) return result try: result.append(int(values)) except (TypeError, ValueError): pass return result def _category_achievement_ids(self, category: Dict[str, Any]) -> list[int]: """提取分类及其子分类包含的资历 ID""" ids = self._flatten_achievement_ids(category.get("achievements", [])) for child in category.get("children", []) or []: if isinstance(child, dict): ids.extend(self._flatten_achievement_ids(child.get("achievements", []))) return ids def _build_achievement_progress( self, name: str, achievement_ids: list[int], point_map: Dict[str, Any], completed_ids: set[int], ) -> Dict[str, Any]: """按资历点数计算完成进度""" unique_ids = set(achievement_ids) total_points = 0 completed_points = 0 completed_count = 0 for achievement_id in unique_ids: try: point = int(point_map.get(str(achievement_id), 0) or 0) except (TypeError, ValueError): point = 0 total_points += point if achievement_id in completed_ids: completed_count += 1 completed_points += point percent = round(completed_points / total_points * 100, 2) if total_points else 0 return { "name": name, "total_points": total_points, "completed_points": completed_points, "percent": percent, "percent_text": f"{percent:.2f}%", "achievement_count": len(unique_ids), "completed_count": completed_count, } async def zili(self, name: str, server: str, choice: int) -> Dict[str, Any]: """角色资历""" return_data = self._init_return_data() if choice not in ACHIEVEMENT_CHOICE_MAP: return_data["msg"] = "无效序号,结束会话" return return_data role_params = {"server": server, "name": name, "token": self.token} role_data: Optional[Dict[str, Any]] = await self._base_request( "jx3_jueshexinxi", "GET", params=role_params ) if not role_data or not isinstance(role_data, dict): return_data["msg"] = "未查询到角色" return return_data global_id = role_data.get("globalId") if not global_id: return_data["msg"] = "无法获取角色全区 ID" return return_data achievement_data = await self._api.get( "https://next2.jx3box.com/api/next2/user-achievements", params={"jx3id": global_id}, out_key="data", ) if not achievement_data or not isinstance(achievement_data, dict): return_data["msg"] = "未查询到资历数据" return return_data completed_ids = set() achievements = achievement_data.get("achievements") or "" if isinstance(achievements, str): for item in achievements.split(","): try: completed_ids.add(int(item.strip())) except ValueError: continue elif isinstance(achievements, list): completed_ids = set(self._flatten_achievement_ids(achievements)) menu_payload = await self._get_achievement_base_data( "achievement_menus", "jx3box_achievement_menus", ) point_payload = await self._get_achievement_base_data( "achievement_points", "jx3box_achievement_points", ) if not menu_payload or not point_payload: return_data["msg"] = "基础资历数据获取失败" return return_data menus = menu_payload.get("menus", {}) points = point_payload.get("points", {}) if not isinstance(menus, dict) or not isinstance(points, dict): return_data["msg"] = "基础资历数据格式异常" return return_data selected_menu_id, selected_title = ACHIEVEMENT_CHOICE_MAP[choice] items = [] if choice == 0: total_ids = [] for _, menu_id, title in ACHIEVEMENT_CHOICES[1:]: category = menus.get(str(menu_id), {}) if not isinstance(category, dict): continue category_ids = self._category_achievement_ids(category) total_ids.extend(category_ids) items.append( self._build_achievement_progress(title, category_ids, points, completed_ids) ) summary = self._build_achievement_progress(selected_title, total_ids, points, completed_ids) else: category = menus.get(str(selected_menu_id), {}) if not isinstance(category, dict) or not category: return_data["msg"] = "未找到该资历分类" return return_data category_ids = self._category_achievement_ids(category) summary = self._build_achievement_progress(selected_title, category_ids, points, completed_ids) for child in category.get("children", []) or []: if not isinstance(child, dict): continue child_ids = self._flatten_achievement_ids(child.get("achievements", [])) items.append( self._build_achievement_progress(child.get("name", "未命名"), child_ids, points, completed_ids) ) try: return_data["temp"] = await load_template("zili.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["data"] = { "title": selected_title, "summary": summary, "items": items, "role_name": role_data.get("roleName", name), "server": role_data.get("serverName", server), "zone": role_data.get("zoneName", ""), "force_name": role_data.get("forceName", "无"), "camp_name": role_data.get("campName", "无"), "tong_name": role_data.get("tongName", "无"), "updated_at": achievement_data.get("updated_at", ""), "update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), } return_data["code"] = 200 return return_data async def jueshe(self,name: str, server: str) -> Dict[str, Any]: """角色""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"server": server, "name": name, "token": self.token} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_jueshexinxi", "GET", params=params ) if not data: return_data["msg"] = "获取接口信息失败" return return_data # 3. 处理返回数据 try: # 格式化字符串,利用字典的 get 方法提供默认值 result_msg = ( f"服务器:{data.get('zoneName', '无')}·{data.get('serverName', '无')}\n" f"名称:{data.get('roleName', '无')}\n" f"角色ID:{data.get('roleId', '无')}\n" f"全区ID:{data.get('globalId', '无')}\n" f"职业:{data.get('forceName', '无')}·{data.get('bodyName', '无')}\n" f"帮会:{data.get('tongName', '无')}\n" f"阵营:{data.get('campName', '无')}\n" ) return_data["data"] = result_msg return_data["code"] = 200 except Exception as e: logger.error(f"数据处理时出错: {e}") return_data["msg"] = "处理接口返回信息时出错" return return_data return_data["code"] = 200 return return_data async def jingnai(self, name: str, server: str) -> Dict[str, Any]: """百战精耐""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"server": server, "name": name, "token": self.token} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3_jingnai", "GET", params=params ) if not data or not isinstance(data, dict): return_data["msg"] = "未查询到角色精耐信息" return return_data # 3. 处理返回数据 try: items = data.get("skill_list", []) if not isinstance(items, list) or not items: return_data["msg"] = "未查询到技能信息" return return_data color_map = { 7: "黑色", 6: "紫色", 5: "红色", 4: "绿色", 3: "蓝色", 2: "黄色", 0: "无", } for item in items: if not isinstance(item, dict): continue color = item.get("skill_color") try: color_key = int(color) except (TypeError, ValueError): color_key = None item["skill_color_text"] = color_map.get(color_key, str(color) if color is not None else "") update_time = data.get("update_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 = "" return_data["data"] = { "server": data.get("server", server), "role_name": data.get("role_name", name), "skill_energy": data.get("skill_energy", ""), "skill_stamina": data.get("skill_stamina", ""), "skill_count": data.get("skill_count", len(items)), "items": items, "update_time": update_time } except Exception as e: logger.error(f"处理精耐数据失败: {e}") return_data["msg"] = "处理精耐数据失败" return return_data # 4. 加载模板 try: return_data["temp"] = await load_template("jingnai.html") except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return_data["code"] = 200 return return_data async def jiaoyihang(self, name: str , server: str) -> Dict[str, Any]: """区服交易行""" return_data = self._init_return_data() item_groups = await self._get_trade_item_groups() if not item_groups: return_data["msg"] = "交易行基础物品数据获取失败" return return_data trade_items = self._flatten_trade_items(item_groups) matched_items = self._match_trade_items(trade_items, name, 50) if not matched_items: return_data["msg"] = "未找到匹配的交易行物品" return return_data item_map = {item["item_id"]: item for item in matched_items} params = { "item_ids": list(item_map.keys()), "server": server, "aggregate_type": "hourly", } price_data: Optional[List[Dict[str, Any]]] = await self._base_request( "jx3_jiaoyihang", "POST", params=params, out_key="" ) if not price_data or not isinstance(price_data, list): return_data["msg"] = "未查询到交易行价格数据" return return_data try: result = [] for price_item in price_data: if not isinstance(price_item, dict): continue item_id = str(price_item.get("item_id") or "") base_item = item_map.get(item_id) if not base_item: continue timestamp = price_item.get("timestamp") try: created = datetime.fromtimestamp(int(timestamp)).strftime("%Y-%m-%d %H:%M:%S") except (TypeError, ValueError, OSError): created = "" result.append( { "item_id": item_id, "name": base_item.get("label", ""), "icon": f"https://icon.jx3box.com/icon/{base_item.get('icon', '')}.png", "server": price_item.get("server", server), "price": price_item.get("price", 0), "price_parts": gold_to_parts(price_item.get("price", 0)), "sample": price_item.get("sample", 0), "created": created, } ) if not result: return_data["msg"] = "未查询到交易行价格数据" return return_data return_data["data"] = { "search_name": name, "server": server, "matched_count": len(matched_items), "result_count": len(result), "list": result, "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 # 5. 模板渲染 try: return_data["temp"] = await load_template("jiaoyihang.html") return_data["code"] = 200 except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data 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() try: limit = int(limit) except (TypeError, ValueError): return_data["msg"] = "数量必须是数字" return return_data if limit < 1 or limit > 100: return_data["msg"] = "掉落记录数量需在 1-100 之间" return return_data # 1. 构造请求参数 params = {"server": server, "name": name, "limit": limit, "token": self.token} # 2. 调用基础请求 data: Optional[List[Dict[str, Any]]] = await self._base_request( "jx3_diaoluo", "GET", params=params ) if not data or not isinstance(data, list): return_data["msg"] = "未查询到掉落记录" return return_data # 3. 处理返回数据 try: for item in data: if not isinstance(item, dict): continue drop_time = item.get("time") if drop_time: try: item["time"] = datetime.fromtimestamp(int(drop_time)).strftime("%Y-%m-%d %H:%M:%S") except (TypeError, ValueError, OSError): item["time"] = str(drop_time) else: item["time"] = "" return_data["data"] = { "items": data, "name": name, "limit": limit, "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 # 4. 模板渲染 try: return_data["temp"] = await load_template("diaoluo.html") return_data["code"] = 200 except FileNotFoundError as e: logger.error(f"加载模板失败: {e}") return_data["msg"] = "系统错误:模板文件不存在" return return_data return return_data async def bagua(self, type: str) -> Dict[str, Any]: """八卦""" return_data = self._init_return_data() # 1. 构造请求参数 params = {"class": type, "limit": "5", "token": self.token} # 2. 调用基础请求 data: Optional[list[Dict[str, Any]]] = await self._base_request( "jx3_bagua", "GET", params=params ) # 3. 处理返回数据 try: if not data: result_msg = f"未找到相关 {type} 记录。\n" result_msg += f"可选范围:818 616 鬼网三 鬼网3 树洞 记录 教程 街拍 故事 避雷 吐槽 提问" else: result_msg = f"类型:【{type}】\n\n" for item in data: result_msg += f"{item['title']}\n" result_msg += f"分区:{item['zone']} 服务器:{item['server']}\n" result_msg += f"所属吧:{item['name']}\n" result_msg += f"链接:https://tieba.baidu.com/p/{item['url']}\n" result_msg += f"日期:{item['date']}\n\n" return_data["data"] = result_msg except Exception as e: logger.exception("处理返回数据失败") return_data["msg"] = "处理返回数据失败" return return_data return_data["code"] = 200 return return_data async def hong1(self, name: str) -> Dict[str, Any]: """宏 心法""" return_data = self._init_return_data() # 数据库查询数据 result = await self._sql_db.select_one( "kungfu", "name=? OR name1=? OR name2=? OR name3=? OR name4=? OR name5=?", (name, name, name, name, name, name) ) if result is None: return_data["msg"] = "未找到该心法" return return_data kungfu = result.get("name", None) if kungfu is None: return_data["msg"] = "未找到该心法" return return_data logger.debug(f"查询到数据:{kungfu}") # 1. 构造请求参数 params = {"subtype": kungfu} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3box_hong", "GET", params=params ) logger.debug(data) if not data: return_data["msg"] = "未找到该心法一键宏" return return_data # 提取ID pid_list = [0] msg = "按照热度排列\n" n = 1 try: for m in data.get('list',[]): msg += f"{n}、{m['author']}\t{m['post_title']}\n" pid_list.append(m["ID"]) n += 1 return_data["msg"] = msg return_data["data"]["list"] = pid_list return_data["data"]["num"] = n except Exception as e: logger.exception("处理返回数据失败") return_data["msg"] = "处理返回数据失败" return return_data return_data["code"] = 200 return return_data async def hong2(self, pid: str) -> Dict[str, Any]: """宏 心法""" 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") # 验证数据 if not data: return_data["msg"] = "获取宏数据异常" return return_data # 4. 处理数据 try: return_data["temp"] = data["post_content"] msg = "" for m in data["post_meta"]["data"]: msg += f"【宏名称】\n{m['name']}\n" msg += f"【使用说明】\n{m['desc']}\n" msg += f"【宏脚本】\n{m['macro']}\n\n" return_data["data"] = msg except Exception as e: logger.exception("处理返回数据失败") return_data["msg"] = "处理返回数据失败" return return_data return_data["code"] = 200 return return_data async def peizhuang(self, name: str, tags: str) -> Dict[str, Any]: """配装""" return_data = self._init_return_data() # 数据库查询数据 result = await self._sql_db.select_one( "kungfu", "name=? OR name1=? OR name2=? OR name3=? OR name4=? OR name5=?", (name, name, name, name, name, name) ) logger.debug(result) if result is None: return_data["msg"] = "未找到该心法" return return_data mount = result.get("pzid", None) if not mount: return_data["msg"] = "未找到该心法" return return_data logger.debug(f"查询到数据:{mount}") # 1. 构造请求参数 params = {"mount": mount, "tags": tags} # 2. 调用基础请求 data: Optional[Dict[str, Any]] = await self._base_request( "jx3box_peizhuang", "GET", params=params ) logger.debug(data) # 验证数据 if not data: return_data["msg"] = "配装数据获取异常" return return_data # 3. 处理返回数据 try: result_msg = f"{name}--配装\n" for item in data["list"]: result_msg += f"【{item['zlp']}】--{item['title']}\n" result_msg += f"链接:https://www.jx3box.com/pz/view/{item['id']}\n\n" return_data["data"] = result_msg except Exception as e: logger.exception("处理返回数据失败:",e) return_data["msg"] = "处理返回数据失败" return return_data return_data["code"] = 200 return return_data