241 lines
8.5 KiB
Python
241 lines
8.5 KiB
Python
from datetime import datetime
|
|
from typing import Any, Dict, Optional
|
|
|
|
from astrbot.api import logger
|
|
|
|
from .base import BaseDomainService
|
|
|
|
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 AchievementService(BaseDomainService):
|
|
async def _load_achievement_cache(self, key: str) -> tuple[Optional[Any], bool]:
|
|
"""读取资历基础数据缓存,返回数据和是否已过期。"""
|
|
return await self._cache.get(key)
|
|
|
|
async def _save_achievement_cache(self, key: str, payload: Any):
|
|
"""写入资历基础数据缓存。"""
|
|
await self._cache.set(key, payload)
|
|
|
|
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.request(config_key)
|
|
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
|
|
|
|
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.new_result()
|
|
|
|
if choice not in ACHIEVEMENT_CHOICE_MAP:
|
|
return_data["msg"] = "无效序号,结束会话"
|
|
return return_data
|
|
|
|
role_params = {"server": server, "name": name}
|
|
role_data: Optional[Dict[str, Any]] = await self.request(
|
|
"jx3_jueshexinxi", 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.request(
|
|
"jx3box_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
|
|
)
|
|
)
|
|
|
|
if not await self.attach_template(return_data, "zili.html"):
|
|
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
|