mirror of
https://github.com/PaiGramTeam/MibooGram.git
synced 2024-12-04 20:47:27 +00:00
376 lines
14 KiB
Python
376 lines
14 KiB
Python
|
import math
|
||
|
from typing import List, Tuple, Union, Optional, TYPE_CHECKING, Dict
|
||
|
|
||
|
from pydantic import BaseModel
|
||
|
from simnet import ZZZClient
|
||
|
from simnet.models.zzz.calculator import ZZZCalculatorCharacterDetails, ZZZCalculatorCharacter
|
||
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||
|
from telegram.constants import ChatAction
|
||
|
from telegram.ext import filters
|
||
|
|
||
|
from core.config import config
|
||
|
from core.dependence.assets import AssetsService
|
||
|
from core.dependence.redisdb import RedisDB
|
||
|
from core.plugin import Plugin, handler
|
||
|
from core.services.players import PlayersService
|
||
|
from core.services.template.services import TemplateService
|
||
|
from core.services.wiki.services import WikiService
|
||
|
from metadata.shortname import roleToName, idToRole
|
||
|
from plugins.tools.genshin import GenshinHelper
|
||
|
from plugins.tools.player_detail import PlayerDetailHelper, NeedClient
|
||
|
from utils.log import logger
|
||
|
from utils.uid import mask_number
|
||
|
|
||
|
if TYPE_CHECKING:
|
||
|
from telegram.ext import ContextTypes
|
||
|
from telegram import Update
|
||
|
|
||
|
try:
|
||
|
import ujson as jsonlib
|
||
|
except ImportError:
|
||
|
import json as jsonlib
|
||
|
|
||
|
|
||
|
class PlayerCards(Plugin):
|
||
|
def __init__(
|
||
|
self,
|
||
|
helper: GenshinHelper,
|
||
|
player_service: PlayersService,
|
||
|
template_service: TemplateService,
|
||
|
assets_service: AssetsService,
|
||
|
wiki_service: WikiService,
|
||
|
redis: RedisDB,
|
||
|
):
|
||
|
self.helper = helper
|
||
|
self.player_service = player_service
|
||
|
self.assets_service = assets_service
|
||
|
self.template_service = template_service
|
||
|
self.wiki_service = wiki_service
|
||
|
self.kitsune: Optional[str] = None
|
||
|
self.fight_prop_rule: Dict[str, Dict[str, float]] = {}
|
||
|
self.player_detail_helper = PlayerDetailHelper(helper, redis)
|
||
|
|
||
|
async def get_characters(
|
||
|
self, uid: int, client: "ZZZClient" = None
|
||
|
) -> Tuple[Optional[str], Optional["ZZZCalculatorCharacterDetails"]]:
|
||
|
return await self.player_detail_helper.get_characters(uid, client)
|
||
|
|
||
|
@staticmethod
|
||
|
def get_caption(character: "ZZZCalculatorCharacter") -> str:
|
||
|
tags = []
|
||
|
tags.append(character.name)
|
||
|
tags.append(f"等级{character.level}")
|
||
|
tags.append(f"命座{character.rank}")
|
||
|
if weapon := character.weapon:
|
||
|
tags.append(weapon.name)
|
||
|
tags.append(f"武器等级{weapon.level}")
|
||
|
tags.append(f"精{weapon.star}")
|
||
|
return "#" + " #".join(tags)
|
||
|
|
||
|
@handler.command(command="player_card", player=True, block=False)
|
||
|
@handler.message(filters=filters.Regex("^角色卡片查询(.*)"), player=True, block=False)
|
||
|
async def player_cards(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
|
||
|
user_id = await self.get_real_user_id(update)
|
||
|
message = update.effective_message
|
||
|
uid, offset = self.get_real_uid_or_offset(update)
|
||
|
args = self.get_args(context)
|
||
|
ch_name = None
|
||
|
for i in args:
|
||
|
if i.startswith("@"):
|
||
|
continue
|
||
|
ch_name = roleToName(i)
|
||
|
if ch_name:
|
||
|
break
|
||
|
self.log_user(
|
||
|
update,
|
||
|
logger.info,
|
||
|
"角色卡片信息查询命令请求 || character_name[%s]",
|
||
|
ch_name,
|
||
|
)
|
||
|
await message.reply_chat_action(ChatAction.TYPING)
|
||
|
async with self.helper.genshin(user_id, player_id=uid, offset=offset) as client:
|
||
|
nickname, data = await self.get_characters(client.player_id, client)
|
||
|
uid = client.player_id
|
||
|
if ch_name is not None:
|
||
|
self.log_user(
|
||
|
update,
|
||
|
logger.info,
|
||
|
"角色卡片查询命令请求 || character_name[%s] uid[%s]",
|
||
|
ch_name,
|
||
|
uid,
|
||
|
)
|
||
|
else:
|
||
|
self.log_user(update, logger.info, "角色卡片查询命令请求")
|
||
|
|
||
|
buttons = self.gen_button(data, user_id, uid)
|
||
|
if isinstance(self.kitsune, str):
|
||
|
photo = self.kitsune
|
||
|
else:
|
||
|
photo = open("resources/img/aaa.jpg", "rb")
|
||
|
reply_message = await message.reply_photo(
|
||
|
photo=photo,
|
||
|
caption=f"请选择你要查询的角色 - UID {uid}",
|
||
|
reply_markup=InlineKeyboardMarkup(buttons),
|
||
|
)
|
||
|
if reply_message.photo:
|
||
|
self.kitsune = reply_message.photo[-1].file_id
|
||
|
return
|
||
|
for characters in data.characters:
|
||
|
if idToRole(characters.id) == ch_name:
|
||
|
break
|
||
|
else:
|
||
|
await message.reply_text(f"未在游戏中找到 {ch_name} ,请检查角色是否存在,或者等待角色数据更新后重试")
|
||
|
return
|
||
|
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||
|
render_result = await RenderTemplate(
|
||
|
uid,
|
||
|
characters,
|
||
|
self.template_service,
|
||
|
self.assets_service,
|
||
|
self.wiki_service,
|
||
|
self.fight_prop_rule,
|
||
|
).render() # pylint: disable=W0631
|
||
|
await render_result.reply_photo(
|
||
|
message,
|
||
|
filename=f"player_card_{uid}_{ch_name}.png",
|
||
|
caption=self.get_caption(characters),
|
||
|
)
|
||
|
|
||
|
@handler.callback_query(pattern=r"^get_player_card\|", block=False)
|
||
|
async def get_player_cards(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None:
|
||
|
callback_query = update.callback_query
|
||
|
user = callback_query.from_user
|
||
|
message = callback_query.message
|
||
|
|
||
|
async def get_player_card_callback(
|
||
|
callback_query_data: str,
|
||
|
) -> Tuple[str, int, int]:
|
||
|
_data = callback_query_data.split("|")
|
||
|
_user_id = int(_data[1])
|
||
|
_uid = int(_data[2])
|
||
|
_result = _data[3]
|
||
|
logger.debug(
|
||
|
"callback_query_data函数返回 result[%s] user_id[%s] uid[%s]",
|
||
|
_result,
|
||
|
_user_id,
|
||
|
_uid,
|
||
|
)
|
||
|
return _result, _user_id, _uid
|
||
|
|
||
|
result, user_id, uid = await get_player_card_callback(callback_query.data)
|
||
|
if user.id != user_id:
|
||
|
await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True)
|
||
|
return
|
||
|
if result == "empty_data":
|
||
|
await callback_query.answer(text="此按钮不可用", show_alert=True)
|
||
|
return
|
||
|
page = 0
|
||
|
if result.isdigit():
|
||
|
page = int(result)
|
||
|
logger.info(
|
||
|
"用户 %s[%s] 角色卡片查询命令请求 || page[%s] uid[%s]",
|
||
|
user.full_name,
|
||
|
user.id,
|
||
|
page,
|
||
|
uid,
|
||
|
)
|
||
|
else:
|
||
|
logger.info(
|
||
|
"用户 %s[%s] 角色卡片查询命令请求 || character_name[%s] uid[%s]",
|
||
|
user.full_name,
|
||
|
user.id,
|
||
|
result,
|
||
|
uid,
|
||
|
)
|
||
|
try:
|
||
|
nickname, data = await self.get_characters(uid)
|
||
|
except NeedClient:
|
||
|
async with self.helper.genshin(user.id, player_id=uid) as client:
|
||
|
nickname, data = await self.get_characters(client.player_id, client)
|
||
|
if page:
|
||
|
buttons = self.gen_button(data, user.id, uid, page)
|
||
|
await message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(buttons))
|
||
|
await callback_query.answer(f"已切换到第 {page} 页", show_alert=False)
|
||
|
return
|
||
|
for characters in data.characters:
|
||
|
if idToRole(characters.id) == result:
|
||
|
break
|
||
|
else:
|
||
|
await message.delete()
|
||
|
await callback_query.answer(
|
||
|
f"未在游戏中找到 {result} ,请检查角色是否存在,或者等待角色数据更新后重试",
|
||
|
show_alert=True,
|
||
|
)
|
||
|
return
|
||
|
await callback_query.answer(text="正在渲染图片中 请稍等 请不要重复点击按钮", show_alert=False)
|
||
|
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||
|
render_result = await RenderTemplate(
|
||
|
uid,
|
||
|
characters,
|
||
|
self.template_service,
|
||
|
self.assets_service,
|
||
|
self.wiki_service,
|
||
|
self.fight_prop_rule,
|
||
|
).render() # pylint: disable=W0631
|
||
|
render_result.filename = f"player_card_{uid}_{result}.png"
|
||
|
render_result.caption = self.get_caption(characters)
|
||
|
await render_result.edit_media(message)
|
||
|
|
||
|
@staticmethod
|
||
|
def gen_button(
|
||
|
data: "ZZZCalculatorCharacterDetails",
|
||
|
user_id: Union[str, int],
|
||
|
uid: int,
|
||
|
page: int = 1,
|
||
|
) -> List[List[InlineKeyboardButton]]:
|
||
|
"""生成按钮"""
|
||
|
buttons = []
|
||
|
|
||
|
if data.characters:
|
||
|
buttons = [
|
||
|
InlineKeyboardButton(
|
||
|
idToRole(value.id),
|
||
|
callback_data=f"get_player_card|{user_id}|{uid}|{idToRole(value.id)}",
|
||
|
)
|
||
|
for value in data.characters
|
||
|
if value.id
|
||
|
]
|
||
|
all_buttons = [buttons[i : i + 4] for i in range(0, len(buttons), 4)]
|
||
|
send_buttons = all_buttons[(page - 1) * 3 : page * 3]
|
||
|
last_page = page - 1 if page > 1 else 0
|
||
|
all_page = math.ceil(len(all_buttons) / 3)
|
||
|
next_page = page + 1 if page < all_page and all_page > 1 else 0
|
||
|
last_button = []
|
||
|
if last_page:
|
||
|
last_button.append(
|
||
|
InlineKeyboardButton(
|
||
|
"<< 上一页",
|
||
|
callback_data=f"get_player_card|{user_id}|{uid}|{last_page}",
|
||
|
)
|
||
|
)
|
||
|
if last_page or next_page:
|
||
|
last_button.append(
|
||
|
InlineKeyboardButton(
|
||
|
f"{page}/{all_page}",
|
||
|
callback_data=f"get_player_card|{user_id}|{uid}|empty_data",
|
||
|
)
|
||
|
)
|
||
|
if next_page:
|
||
|
last_button.append(
|
||
|
InlineKeyboardButton(
|
||
|
"下一页 >>",
|
||
|
callback_data=f"get_player_card|{user_id}|{uid}|{next_page}",
|
||
|
)
|
||
|
)
|
||
|
if last_button:
|
||
|
send_buttons.append(last_button)
|
||
|
return send_buttons
|
||
|
|
||
|
|
||
|
class Artifact(BaseModel, frozen=False):
|
||
|
tid: int = 0
|
||
|
# ID
|
||
|
equipment: Dict = {}
|
||
|
# 圣遗物评分
|
||
|
score: float = 0
|
||
|
# 圣遗物评级
|
||
|
score_label: str = "E"
|
||
|
# 圣遗物评级颜色
|
||
|
score_class: str = ""
|
||
|
# 副词条分数
|
||
|
substat_scores: List[float] = []
|
||
|
|
||
|
def set_score(self, result: "Score"):
|
||
|
self.score = result.score
|
||
|
self.score_label = result.rating
|
||
|
self.score_class = self.get_score_class(result.rating)
|
||
|
self.substat_scores = result.sub_stat_score
|
||
|
|
||
|
@staticmethod
|
||
|
def get_score_class(label: str) -> str:
|
||
|
mapping = {
|
||
|
"F": "text-neutral-400",
|
||
|
"D": "text-neutral-200",
|
||
|
"C": "text-violet-400",
|
||
|
"B": "text-violet-400",
|
||
|
"A": "text-yellow-400",
|
||
|
"S": "text-yellow-400",
|
||
|
"SS": "text-yellow-400",
|
||
|
"SSS": "text-red-500",
|
||
|
"WTF": "text-red-500",
|
||
|
}
|
||
|
return mapping.get(label.replace("+", ""), "text-neutral-400")
|
||
|
|
||
|
|
||
|
class RenderTemplate:
|
||
|
def __init__(
|
||
|
self,
|
||
|
uid: Union[int, str],
|
||
|
character: "ZZZCalculatorCharacter",
|
||
|
template_service: TemplateService,
|
||
|
assets_service: AssetsService,
|
||
|
wiki_service: WikiService,
|
||
|
fight_prop_rule: Dict[str, Dict[str, float]],
|
||
|
):
|
||
|
self.uid = uid
|
||
|
self.template_service = template_service
|
||
|
self.character = character
|
||
|
self.assets_service = assets_service
|
||
|
self.wiki_service = wiki_service
|
||
|
self.fight_prop_rule = fight_prop_rule
|
||
|
|
||
|
async def render(self):
|
||
|
images = await self.cache_images()
|
||
|
|
||
|
artifact_total_score: float = 0
|
||
|
artifact_total_score_label = "N/A"
|
||
|
artifacts = list(self.character.equip_map.values())
|
||
|
|
||
|
weapon = None
|
||
|
weapon_detail = None
|
||
|
if self.character.weapon and self.character.weapon.id:
|
||
|
weapon = self.character.weapon
|
||
|
weapon_detail = self.wiki_service.weapon.get_by_id(self.character.weapon.id)
|
||
|
skills = [0, 0, 0, 0, 0, 0]
|
||
|
skills_map = [0, 2, 5, 1, 3, 4]
|
||
|
for index in range(6):
|
||
|
skills[index] = self.character.skills[skills_map[index]].level
|
||
|
data = {
|
||
|
"uid": mask_number(self.uid),
|
||
|
"character": self.character,
|
||
|
"character_detail": self.wiki_service.character.get_by_id(self.character.id),
|
||
|
"weapon": weapon,
|
||
|
"weapon_detail": weapon_detail,
|
||
|
# 圣遗物评分
|
||
|
"artifact_total_score": artifact_total_score,
|
||
|
# 圣遗物评级
|
||
|
"artifact_total_score_label": artifact_total_score_label,
|
||
|
# 圣遗物评级颜色
|
||
|
"artifact_total_score_class": Artifact.get_score_class(artifact_total_score_label),
|
||
|
"artifacts": artifacts,
|
||
|
"skills": skills,
|
||
|
"images": images,
|
||
|
}
|
||
|
|
||
|
return await self.template_service.render(
|
||
|
"zzz/player_card/player_card.html",
|
||
|
data,
|
||
|
{"width": 1000, "height": 1200},
|
||
|
full_page=True,
|
||
|
query_selector=".text-neutral-200",
|
||
|
ttl=7 * 24 * 60 * 60,
|
||
|
)
|
||
|
|
||
|
async def cache_images(self):
|
||
|
c = self.character
|
||
|
cid = c.id
|
||
|
data = {
|
||
|
"banner_url": self.assets_service.avatar.gacha(cid).as_uri(),
|
||
|
"skills": ["", "", "", "", "", ""],
|
||
|
"constellations": ["", "", "", "", "", ""],
|
||
|
"equipment": "",
|
||
|
}
|
||
|
if c.weapon and c.weapon.id:
|
||
|
data["equipment"] = self.assets_service.weapon.icon(c.weapon.id).as_uri()
|
||
|
return data
|