diff --git a/metadata/shortname.py b/metadata/shortname.py
index 997170c3..a1c5be6e 100644
--- a/metadata/shortname.py
+++ b/metadata/shortname.py
@@ -5,7 +5,17 @@ from typing import List
from metadata.genshin import WEAPON_DATA
-__all__ = ["roles", "weapons", "roleToId", "roleToName", "weaponToName", "weaponToId", "not_real_roles", "roleToTag"]
+__all__ = [
+ "roles",
+ "weapons",
+ "idToName",
+ "roleToId",
+ "roleToName",
+ "weaponToName",
+ "weaponToId",
+ "not_real_roles",
+ "roleToTag",
+]
# noinspection SpellCheckingInspection
roles = {
@@ -749,6 +759,13 @@ def roleToId(name: str) -> int | None:
return next((key for key, value in roles.items() for n in value if n == name), None)
+# noinspection PyPep8Naming
+@functools.lru_cache()
+def idToName(id: int) -> str | None:
+ """从角色ID获取正式名"""
+ return roles[id][0] if id in roles else None
+
+
# noinspection PyPep8Naming
@functools.lru_cache()
def weaponToName(shortname: str) -> str:
diff --git a/modules/apihelper/client/components/abyss.py b/modules/apihelper/client/components/abyss.py
index 1abaddb9..1ea07b0a 100644
--- a/modules/apihelper/client/components/abyss.py
+++ b/modules/apihelper/client/components/abyss.py
@@ -1,23 +1,22 @@
import time
-from typing import List
+from typing import List, Dict
import httpx
-from pydantic import parse_obj_as
-
-from ...models.genshin.abyss import TeamRateResult, TeamRate
__all__ = ("AbyssTeam",)
class AbyssTeam:
- TEAM_RATE_API = "https://www.youchuang.fun/gamerole/formationRate"
+ TEAM_RATE_API = "https://homa.snapgenshin.com/Statistics/Team/Combination"
HEADERS = {
- "Host": "www.youchuang.fun",
+ "Host": "homa.snapgenshin.com",
"Referer": "https://servicewechat.com/wxce4dbe0cb0f764b3/91/page-frame.html",
"User-Agent": "Mozilla/5.0 (iPad; CPU OS 15_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) "
"Mobile/15E148 MicroMessenger/8.0.20(0x1800142f) NetType/WIFI Language/zh_CN",
"content-type": "application/json",
}
+
+ # This should not be there
VERSION = "3.5"
def __init__(self):
@@ -26,20 +25,13 @@ class AbyssTeam:
self.data = None
self.ttl = 10 * 60
- async def get_data(self) -> TeamRateResult:
+ async def get_data(self) -> List[Dict[str, Dict]]:
if self.data is None or self.time + self.ttl < time.time():
- data_up = await self.client.post(self.TEAM_RATE_API, json={"version": self.VERSION, "layer": 1})
- data_up_json = data_up.json()["result"]
- data_down = await self.client.post(self.TEAM_RATE_API, json={"version": self.VERSION, "layer": 2})
- data_down_json = data_down.json()["result"]
- self.data = TeamRateResult(
- version=self.VERSION,
- rate_list_up=parse_obj_as(List[TeamRate], data_up_json["rateList"]),
- rate_list_down=parse_obj_as(List[TeamRate], data_down_json["rateList"]),
- user_count=data_up_json["userCount"],
- )
+ data = await self.client.get(self.TEAM_RATE_API)
+ data_json = data.json()["data"]
+ self.data = data_json
self.time = time.time()
- return self.data.copy(deep=True)
+ return self.data.copy()
async def close(self):
await self.client.aclose()
diff --git a/plugins/genshin/abyss.py b/plugins/genshin/abyss.py
index 9e674e1b..e1a9004e 100644
--- a/plugins/genshin/abyss.py
+++ b/plugins/genshin/abyss.py
@@ -28,24 +28,17 @@ except ImportError:
import json as jsonlib
TZ = timezone("Asia/Shanghai")
-cmd_pattern = r"(?i)^/abyss(?:@[\w]+)?\s*((?:\d+)|(?:all))?\s*(pre)?"
-msg_pattern = r"^深渊数据((?:查询)|(?:总览))(上期)?\D?(\d*)?.*?$"
-
-regex_01 = r"['\"]icon['\"]:\s*['\"](.*?)['\"]"
-regex_02 = r"['\"]side_icon['\"]:\s*['\"](.*?)['\"]"
@lru_cache
def get_args(text: str) -> Tuple[int, bool, bool]:
- if text.startswith("/"):
- result = re.match(cmd_pattern, text).groups()
- try:
- floor = int(result[0] or 0)
- except ValueError:
- floor = 0
- return floor, result[0] == "all", bool(result[1])
- result = re.match(msg_pattern, text).groups()
- return int(result[2] or 0), result[0] == "总览", result[1] == "上期"
+ total = "all" in text or "总览" in text
+ prev = "pre" in text or "上期" in text
+ try:
+ floor = 0 if total else int(re.search(r"\d+", text).group(0))
+ except (ValueError, IndexError):
+ floor = 0
+ return floor, total, prev
class AbyssUnlocked(Exception):
@@ -74,7 +67,7 @@ class AbyssPlugin(Plugin):
self.assets_service = assets_service
@handler.command("abyss", block=False)
- @handler.message(filters.Regex(msg_pattern), block=False)
+ @handler.message(filters.Regex(r"^深渊数据"), block=False)
async def command_start(self, update: Update, _: CallbackContext) -> None: # skipcq: PY-R1000 #
user = update.effective_user
message = update.effective_message
diff --git a/plugins/genshin/abyss_team.py b/plugins/genshin/abyss_team.py
new file mode 100644
index 00000000..123320c2
--- /dev/null
+++ b/plugins/genshin/abyss_team.py
@@ -0,0 +1,131 @@
+"""Recommend teams for Spiral Abyss"""
+
+import re
+
+from telegram import Update
+from telegram.constants import ChatAction, ParseMode
+from telegram.ext import CallbackContext, filters
+
+from core.dependence.assets import AssetsService
+from core.plugin import Plugin, handler
+from core.services.template.services import TemplateService
+from metadata.genshin import AVATAR_DATA
+from metadata.shortname import idToName
+from modules.apihelper.client.components.abyss import AbyssTeam as AbyssTeamClient
+from plugins.tools.genshin import GenshinHelper
+from utils.log import logger
+
+
+class AbyssTeamPlugin(Plugin):
+ """Recommend teams for Spiral Abyss"""
+
+ def __init__(
+ self,
+ template: TemplateService,
+ helper: GenshinHelper,
+ assets_service: AssetsService,
+ ):
+ self.template_service = template
+ self.helper = helper
+ self.team_data = AbyssTeamClient()
+ self.assets_service = assets_service
+
+ @handler.command("abyss_team", block=False)
+ @handler.message(filters.Regex(r"^深渊配队"), block=False)
+ async def command_start(self, update: Update, _: CallbackContext) -> None: # skipcq: PY-R1000 #
+ user = update.effective_user
+ message = update.effective_message
+
+ if "help" in message.text or "帮助" in message.text:
+ await message.reply_text(
+ "深渊配队推荐功能使用帮助(中括号表示可选参数)\n\n"
+ "指令格式:\n/abyss_team [n=配队数]
\n(pre
表示上期)\n\n"
+ "文本格式:\n深渊配队 [n=配队数]
\n\n"
+ "如:\n"
+ "/abyss_team
\n/abyss_team n=5
\n"
+ "深渊配队
\n",
+ parse_mode=ParseMode.HTML,
+ )
+ logger.info("用户 %s[%s] 查询[bold]深渊配队推荐[/bold]帮助", user.full_name, user.id, extra={"markup": True})
+ return
+
+ logger.info("用户 %s[%s] [bold]深渊配队推荐[/bold]请求", user.full_name, user.id, extra={"markup": True})
+
+ client = await self.helper.get_genshin_client(user.id)
+
+ await message.reply_chat_action(ChatAction.TYPING)
+ team_data = await self.team_data.get_data()
+
+ # Set of uids
+ characters = {c.id for c in await client.get_genshin_characters(client.player_id)}
+
+ teams = {
+ "Up": [],
+ "Down": [],
+ }
+
+ # All of the effective and available teams
+ for lane in ["Up", "Down"]:
+ for a_team in team_data[12 - 9][lane]:
+ t_characters = [int(s) for s in re.findall(r"\d+", a_team["Item"])]
+ t_rate = a_team["Rate"]
+
+ # Check availability
+ if not all(c in characters for c in t_characters):
+ continue
+
+ teams[lane].append(
+ {
+ "Characters": t_characters,
+ "Rate": t_rate,
+ }
+ )
+
+ # If a number is specified, use it as the number of expected teams.
+ match = re.search(r"(?<=n=)\d+", message.text)
+ n_team = int(match.group()) if match is not None else 4
+
+ if "fast" in message.text:
+ # TODO: Give it a faster method?
+ # Maybe we can allow characters exist on both side.
+ return
+
+ # Otherwise, we'd find a team in a complexity
+ # O(len(teams[up]) * len(teams[down]))
+
+ abyss_teams_data = {"uid": client.player_id, "teams": []}
+
+ async def _get_render_data(id_list):
+ return [
+ {
+ "icon": (await self.assets_service.avatar(cid).icon()).as_uri(),
+ "name": idToName(cid),
+ "star": AVATAR_DATA[str(cid)]["rank"],
+ "hava": True,
+ }
+ for cid in id_list
+ ]
+
+ for u in teams["Up"]:
+ for d in teams["Down"]:
+ if not all(c not in d["Characters"] for c in u["Characters"]):
+ continue
+ team = {
+ "Up": await _get_render_data(u["Characters"]),
+ "UpRate": u["Rate"],
+ "Down": await _get_render_data(d["Characters"]),
+ "DownRate": d["Rate"],
+ }
+ abyss_teams_data["teams"].append(team)
+ abyss_teams_data["teams"].sort(key=lambda t: t["UpRate"] * t["DownRate"], reverse=True)
+ abyss_teams_data["teams"] = abyss_teams_data["teams"][0 : min(n_team, len(abyss_teams_data["teams"]))]
+
+ await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
+ render_result = await self.template_service.render(
+ "genshin/abyss_team/abyss_team.jinja2",
+ abyss_teams_data,
+ {"width": 785, "height": 800},
+ full_page=True,
+ query_selector=".bg-contain",
+ )
+ await render_result.reply_photo(message, filename=f"abyss_team_{user.id}.png", allow_sending_without_reply=True)
diff --git a/resources/genshin/abyss/overview.jinja2 b/resources/genshin/abyss/overview.jinja2
index f2ce53ee..bf1db9dc 100644
--- a/resources/genshin/abyss/overview.jinja2
+++ b/resources/genshin/abyss/overview.jinja2
@@ -60,18 +60,18 @@