This commit is contained in:
qsc
2026-09-04 18:29:49 +08:00
parent f872ffae71
commit 3845950862
42 changed files with 808 additions and 1054 deletions
+82 -266
View File
@@ -14,30 +14,6 @@ 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}
JX3BOX_API_BASE_URLS = {
"node": "https://node.jx3box.com",
"next2": "https://next2.jx3box.com",
@@ -226,11 +202,8 @@ class JX3BOXService:
return return_data
async def hong1(self, name: str) -> Dict[str, Any]:
"""宏 心法"""
return_data = self._init_return_data()
# 数据库查询数据
async def _macro_posts(self, name: str) -> Optional[List[Dict[str, Any]]]:
"""按心法查询宏帖子,并保留接口热度排序。"""
result = await self._sql_db.select_one(
"kungfu",
"name=? OR name1=? OR name2=? OR name3=? OR name4=? OR name5=?",
@@ -238,52 +211,91 @@ class JX3BOXService:
)
if result is None:
return_data["msg"] = "未找到该心法"
return return_data
return None
kungfu = result.get("name", None)
if kungfu is None:
return_data["msg"] = "未找到该心法"
return return_data
return None
logger.debug(f"查询到数据:{kungfu}")
# 1. 构造请求参数
params = {"subtype": kungfu}
# 2. 调用基础请求
data: Optional[Dict[str, Any]] = await self._base_request(
"cms",
"/api/cms/posts",
params=params,
params={"subtype": kungfu},
)
if not isinstance(data, dict):
return None
if not data:
return_data["msg"] = "未找到该心法一键宏"
return return_data
return [
item
for item in data.get("list", [])
if isinstance(item, dict) and item.get("ID")
]
async def hong1(self, name: str) -> Dict[str, str]:
"""宏首轮:返回“序号 -> 显示内容”的选择数据。"""
posts = await self._macro_posts(name)
if not posts:
raise ValueError("未找到该心法一键宏")
# 提取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
options = {
str(index): f"{post.get('author', '')}\t{post.get('post_title', '')}"
for index, post in enumerate(posts, start=1)
}
if not options:
raise ValueError("未找到该心法一键宏")
except Exception as e:
logger.exception("处理返回数据失败")
return_data["msg"] = "处理返回数据失败"
return return_data
return_data["code"] = 200
if isinstance(e, ValueError):
raise
raise RuntimeError("处理返回数据失败") from e
return return_data
return options
async def hong_select(self,name: str,selected: Dict[str, Any],) -> Dict[str, Any]:
"""宏次轮:根据单项选择数据重新定位帖子并查询详情。"""
if not isinstance(selected, dict) or len(selected) != 1:
return_data = self._init_return_data()
return_data["msg"] = "宏选项数据格式异常"
return return_data
choice, label = next(iter(selected.items()))
posts = await self._macro_posts(name)
if not posts:
return_data = self._init_return_data()
return_data["msg"] = "未找到该心法一键宏"
return return_data
selected_post = next(
(
post
for post in posts
if f"{post.get('author', '')}\t{post.get('post_title', '')}"
== str(label)
),
None,
)
if selected_post is None:
try:
selected_post = posts[int(choice) - 1]
except (TypeError, ValueError, IndexError):
return_data = self._init_return_data()
return_data["msg"] = "无效宏序号"
return return_data
pid = str(selected_post.get("ID") or "").strip()
if not pid:
return_data = self._init_return_data()
return_data["msg"] = "宏帖子 ID 无效"
return return_data
return await self.hong2(pid)
async def hong2(self, pid: str) -> Dict[str, Any]:
@@ -328,12 +340,12 @@ class JX3BOXService:
return return_data
async def _load_achievement_cache(self, key: str) -> tuple[Optional[Any], bool]:
"""读取资历基础数据缓存,返回数据和是否已过期"""
async def _load_trade_cache(self, key: str) -> tuple[Optional[Any], bool]:
"""读取交易行基础数据缓存,返回数据和是否已过期"""
try:
row = await self._cache_db.select_one("achievement_cache", "key=?", (key,))
row = await self._cache_db.select_one("trade_item_cache", "key=?", (key,))
except Exception as e:
logger.error(f"读取资历缓存失败: {e}")
logger.error(f"读取交易行缓存失败: {e}")
return None, True
if not row:
@@ -345,16 +357,16 @@ class JX3BOXService:
expired = datetime.now() - updated_at > timedelta(days=30)
return payload, expired
except Exception as e:
logger.error(f"解析资历缓存失败: {e}")
logger.error(f"解析交易行缓存失败: {e}")
return None, True
async def _save_achievement_cache(self, key: str, payload: Any):
"""写入资历基础数据缓存"""
async def _save_trade_cache(self, key: str, payload: Any):
"""写入交易行基础数据缓存"""
try:
await self._cache_db.execute(
"""
INSERT INTO achievement_cache (key, content, updated_at)
INSERT INTO trade_item_cache (key, content, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(key) DO UPDATE SET
content=excluded.content,
@@ -367,31 +379,13 @@ class JX3BOXService:
),
)
except Exception as e:
logger.error(f"写入资历缓存失败: {e}")
async def _get_achievement_base_data(self, cache_key: str, api_path: str) -> Optional[Dict[str, Any]]:
"""获取资历菜单或点数数据,优先使用未过期缓存"""
cached, expired = await self._load_achievement_cache(cache_key)
if cached and not expired:
return cached
data: Optional[Dict[str, Any]] = await self._base_request("node", api_path)
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
logger.error(f"写入交易行缓存失败: {e}")
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)
cached, expired = await self._load_trade_cache(cache_key)
if isinstance(cached, list) and not expired:
return cached
@@ -402,7 +396,7 @@ class JX3BOXService:
params={"client": "std"},
)
if isinstance(data, list) and data:
await self._save_achievement_cache(cache_key, data)
await self._save_trade_cache(cache_key, data)
return data
if isinstance(cached, list) and cached:
@@ -461,184 +455,6 @@ class JX3BOXService:
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}
api_url = "https://www.jx3api.com/role/detail"
role_data: Optional[Dict[str, Any]] = await self._api.get(api_url, params=role_params, out_key="data")
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._base_request(
"next2",
"/api/next2/user-achievements",
params={"jx3id": global_id},
)
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",
"/api/node/achievement/menus",
)
point_payload = await self._get_achievement_base_data(
"achievement_points",
"/api/node/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 jiaoyihang(self, name: str , server: str) -> Dict[str, Any]:
"""区服交易行"""
return_data = self._init_return_data()