MibooGram/plugins/zzz/player_cards.py

376 lines
14 KiB
Python
Raw Normal View History

2024-08-02 09:34:30 +00:00
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