From c4712f0b071ac974205c202761c5861cc0320ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C4=93lla=20Caerulea?= <69615283+StellaCaerulea@users.noreply.github.com> Date: Thu, 19 Oct 2023 21:55:12 +0800 Subject: [PATCH] :recycle: Add Abyss team Co-authored-by: xtaodada --- metadata/shortname.py | 19 ++- modules/apihelper/client/components/abyss.py | 28 ++-- plugins/genshin/abyss.py | 23 ++- plugins/genshin/abyss_team.py | 131 ++++++++++++++++++ resources/genshin/abyss/overview.jinja2 | 10 +- .../genshin/abyss_team/abyss_team.jinja2 | 9 +- 6 files changed, 176 insertions(+), 44 deletions(-) create mode 100644 plugins/genshin/abyss_team.py 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 @@
战斗次数: {{ data.total_battles }}
最多击破:{{ data.ranks.most_kills[0].value }} - +
最强一击: {{ data.ranks.strongest_strike[0].value }} - +
{% if data.ranks.most_damage_taken is defined and data.ranks.most_damage_taken|length > 0 %} 最多承伤: {{ data.ranks.most_damage_taken[0].value }} - + {% else %} 最多承伤: {% endif %} @@ -81,7 +81,7 @@
{% if data.ranks.most_bursts_used is defined and data.ranks.most_bursts_used|length > 0 %} 元素爆发: {{ data.ranks.most_bursts_used[0].value }} - + {% else %} 元素爆发: {% endif %} @@ -89,7 +89,7 @@
{% if data.ranks.most_skills_used is defined and data.ranks.most_skills_used|length > 0 %} 元素战技: {{ data.ranks.most_skills_used[0].value }} - + {% else %} 元素战技: {% endif %} diff --git a/resources/genshin/abyss_team/abyss_team.jinja2 b/resources/genshin/abyss_team/abyss_team.jinja2 index d08ec3ff..ab6a1b0a 100644 --- a/resources/genshin/abyss_team/abyss_team.jinja2 +++ b/resources/genshin/abyss_team/abyss_team.jinja2 @@ -45,7 +45,6 @@ class="flex flex-row px-6 py-1 my-1 text-neutral-100 bg-white bg-opacity-10 justify-evenly text-sm" >
UID {{ uid }}
-
版本 {{ version }}
{% for team in teams %} @@ -53,7 +52,7 @@
推荐配队 {{ loop.index }}
- {% for lane in ["up", "down"] %} + {% for lane in ["Up", "Down"] %}
{% for i in team[lane] %}
- {{ lane == 'up' and '上' or '下' }}半 + {{ lane == 'Up' and '上' or '下' }}半
- 使用率 {{ team[lane + "_rate"] }} + 使用率 {{ team[lane + "Rate"] }}
@@ -83,7 +82,7 @@ {% endfor %}
- 数据来源:游创工坊 + 数据来源:DGP 胡桃 API