mirror of
https://github.com/PaiGramTeam/PaiGram.git
synced 2024-11-16 04:35:49 +00:00
♻️ Add Abyss team
Co-authored-by: xtaodada <xtao@xtaolink.cn>
This commit is contained in:
parent
6b4918afe9
commit
c4712f0b07
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
131
plugins/genshin/abyss_team.py
Normal file
131
plugins/genshin/abyss_team.py
Normal 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)
|
@ -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 %}
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user