♻️ Add Abyss team

Co-authored-by: xtaodada <xtao@xtaolink.cn>
This commit is contained in:
Stēlla Caerulea 2023-10-19 21:55:12 +08:00 committed by 洛水居室
parent 6b4918afe9
commit c4712f0b07
No known key found for this signature in database
GPG Key ID: C9DE87DA724B88FC
6 changed files with 176 additions and 44 deletions

View File

@ -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:

View File

@ -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()

View File

@ -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

View File

@ -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(
"<b>深渊配队推荐</b>功能使用帮助(中括号表示可选参数)\n\n"
"指令格式:\n<code>/abyss_team [n=配队数]</code>\n<code>pre</code>表示上期)\n\n"
"文本格式:\n<code>深渊配队 [n=配队数]</code> \n\n"
"如:\n"
"<code>/abyss_team</code>\n<code>/abyss_team n=5</code>\n"
"<code>深渊配队</code>\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)

View File

@ -60,18 +60,18 @@
<div class="rank">战斗次数: {{ data.total_battles }}</div>
<div class="rank">
<span>最多击破:{{ data.ranks.most_kills[0].value }}</span>
<img src="{{ data.ranks.most_kills[0].side_icon }}" alt=""/>
<img src="{{ data.ranks.most_kills[0].icon }}" alt=""/>
</div>
</div>
<div>
<div class="rank">
<span>最强一击: {{ data.ranks.strongest_strike[0].value }}</span>
<img src="{{ data.ranks.strongest_strike[0].side_icon }}" alt=""/>
<img src="{{ data.ranks.strongest_strike[0].icon }}" alt=""/>
</div>
<div class="rank">
{% if data.ranks.most_damage_taken is defined and data.ranks.most_damage_taken|length > 0 %}
<span>最多承伤: {{ data.ranks.most_damage_taken[0].value }}</span>
<img src="{{ data.ranks.most_damage_taken[0].side_icon }}" alt=""/>
<img src="{{ data.ranks.most_damage_taken[0].icon }}" alt=""/>
{% else %}
<span>最多承伤: </span>
{% endif %}
@ -81,7 +81,7 @@
<div class="rank">
{% if data.ranks.most_bursts_used is defined and data.ranks.most_bursts_used|length > 0 %}
<span>元素爆发: {{ data.ranks.most_bursts_used[0].value }}</span>
<img src="{{ data.ranks.most_bursts_used[0].side_icon }}" alt=""/>
<img src="{{ data.ranks.most_bursts_used[0].icon }}" alt=""/>
{% else %}
<span>元素爆发: </span>
{% endif %}
@ -89,7 +89,7 @@
<div class="rank">
{% if data.ranks.most_skills_used is defined and data.ranks.most_skills_used|length > 0 %}
<span>元素战技: {{ data.ranks.most_skills_used[0].value }}</span>
<img src="{{ data.ranks.most_skills_used[0].side_icon }}" alt=""/>
<img src="{{ data.ranks.most_skills_used[0].icon }}" alt=""/>
{% else %}
<span>元素战技: </span>
{% endif %}

View File

@ -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"
>
<div>UID {{ uid }}</div>
<div>版本 {{ version }}</div>
</div>
{% for team in teams %}
@ -53,7 +52,7 @@
<div class="text-center mr-auto text-yellow-500">
推荐配队 {{ loop.index }}
</div>
{% for lane in ["up", "down"] %}
{% for lane in ["Up", "Down"] %}
<div class="flex my-2 space-x-4">
{% for i in team[lane] %}
<div
@ -71,10 +70,10 @@
<div>
<div class="text-neutral-300">
{{ lane == 'up' and '上' or '下' }}半
{{ lane == 'Up' and '上' or '下' }}半
</div>
<div class="text-neutral-400 text-sm">
使用率 {{ team[lane + "_rate"] }}
使用率 {{ team[lane + "Rate"] }}
</div>
</div>
</div>
@ -83,7 +82,7 @@
{% endfor %}
<div class="mt-6 text-center p-1 text-neutral-400 text-xs">
数据来源:游创工坊
数据来源:DGP 胡桃 API
</div>
</div>
</body>