2022-10-21 15:52:59 +00:00
|
|
|
|
import asyncio
|
2024-05-27 13:20:51 +00:00
|
|
|
|
import math
|
2024-05-21 13:56:35 +00:00
|
|
|
|
from typing import List, Optional, Sequence, TYPE_CHECKING, Union, Tuple, Any, Dict
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2024-05-21 13:56:35 +00:00
|
|
|
|
from arkowrapper import ArkoWrapper
|
2023-07-18 09:29:31 +00:00
|
|
|
|
from simnet import GenshinClient
|
2023-08-21 14:42:44 +00:00
|
|
|
|
from simnet.errors import BadRequest as SimnetBadRequest
|
2023-07-18 09:29:31 +00:00
|
|
|
|
from simnet.models.genshin.calculator import CalculatorTalent, CalculatorCharacterDetails
|
|
|
|
|
from simnet.models.genshin.chronicle.characters import Character
|
2023-08-21 14:42:44 +00:00
|
|
|
|
from telegram.constants import ChatAction
|
2023-04-28 01:29:16 +00:00
|
|
|
|
from telegram.ext import filters
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2024-06-07 15:22:30 +00:00
|
|
|
|
from core.config import config
|
2023-03-14 01:27:22 +00:00
|
|
|
|
from core.dependence.assets import AssetsService
|
2022-10-16 10:11:04 +00:00
|
|
|
|
from core.plugin import Plugin, handler
|
2023-03-14 01:27:22 +00:00
|
|
|
|
from core.services.cookies import CookiesService
|
|
|
|
|
from core.services.players import PlayersService
|
|
|
|
|
from core.services.players.services import PlayerInfoService
|
|
|
|
|
from core.services.template.models import FileType
|
|
|
|
|
from core.services.template.services import TemplateService
|
2024-06-19 08:29:47 +00:00
|
|
|
|
from gram_core.plugin.methods.inline_use_data import IInlineUseData
|
2024-05-21 13:56:35 +00:00
|
|
|
|
from gram_core.services.template.models import RenderGroupResult
|
2022-10-16 10:11:04 +00:00
|
|
|
|
from modules.wiki.base import Model
|
2024-07-08 17:46:13 +00:00
|
|
|
|
from modules.wiki.other import Element, WeaponType
|
2023-08-21 14:42:44 +00:00
|
|
|
|
from plugins.tools.genshin import CharacterDetails, GenshinHelper
|
2024-05-04 14:47:44 +00:00
|
|
|
|
from plugins.tools.player_info import PlayerInfoSystem
|
2022-10-16 10:11:04 +00:00
|
|
|
|
from utils.log import logger
|
2023-08-26 10:19:00 +00:00
|
|
|
|
from utils.uid import mask_number
|
2023-04-28 01:29:16 +00:00
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
2023-07-18 09:29:31 +00:00
|
|
|
|
from telegram import Update
|
2023-04-28 01:29:16 +00:00
|
|
|
|
from telegram.ext import ContextTypes
|
2024-05-21 13:56:35 +00:00
|
|
|
|
from gram_core.services.template.models import RenderResult
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2024-04-28 15:41:25 +00:00
|
|
|
|
MAX_AVATAR_COUNT = 40
|
|
|
|
|
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2024-07-08 17:46:13 +00:00
|
|
|
|
def parse_element(msg: str) -> set[Element]:
|
|
|
|
|
elements = set()
|
|
|
|
|
for element in Element:
|
|
|
|
|
if element.value in msg:
|
|
|
|
|
elements.add(element)
|
|
|
|
|
return elements
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_weapon_type(msg: str) -> set[WeaponType]:
|
|
|
|
|
weapon_types = set()
|
|
|
|
|
for weapon_type in WeaponType:
|
|
|
|
|
if weapon_type.value in msg:
|
|
|
|
|
weapon_types.add(weapon_type)
|
|
|
|
|
return weapon_types
|
|
|
|
|
|
|
|
|
|
|
2024-06-19 08:29:47 +00:00
|
|
|
|
class TooManyRequests(Exception):
|
|
|
|
|
"""请求过多"""
|
|
|
|
|
|
|
|
|
|
|
2023-01-05 07:06:59 +00:00
|
|
|
|
class SkillData(Model):
|
|
|
|
|
"""天赋数据"""
|
|
|
|
|
|
|
|
|
|
skill: CalculatorTalent
|
|
|
|
|
buffed: bool = False
|
|
|
|
|
"""是否得到了命座加成"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AvatarData(Model):
|
|
|
|
|
avatar: Character
|
|
|
|
|
detail: CalculatorCharacterDetails
|
|
|
|
|
icon: str
|
|
|
|
|
weapon: Optional[str]
|
|
|
|
|
skills: List[SkillData]
|
|
|
|
|
|
|
|
|
|
def sum_of_skills(self) -> int:
|
|
|
|
|
total_level = 0
|
2023-03-14 01:27:22 +00:00
|
|
|
|
for skill_data in self.skills:
|
|
|
|
|
total_level += skill_data.skill.level
|
2023-01-05 07:06:59 +00:00
|
|
|
|
return total_level
|
|
|
|
|
|
|
|
|
|
|
2023-03-14 01:27:22 +00:00
|
|
|
|
class AvatarListPlugin(Plugin):
|
2023-04-28 01:29:16 +00:00
|
|
|
|
"""练度统计"""
|
|
|
|
|
|
2022-10-16 10:11:04 +00:00
|
|
|
|
def __init__(
|
2022-12-27 13:56:12 +00:00
|
|
|
|
self,
|
2023-03-14 01:27:22 +00:00
|
|
|
|
player_service: PlayersService = None,
|
2022-12-27 13:56:12 +00:00
|
|
|
|
cookies_service: CookiesService = None,
|
|
|
|
|
assets_service: AssetsService = None,
|
|
|
|
|
template_service: TemplateService = None,
|
2023-03-14 01:27:22 +00:00
|
|
|
|
helper: GenshinHelper = None,
|
|
|
|
|
character_details: CharacterDetails = None,
|
|
|
|
|
player_info_service: PlayerInfoService = None,
|
2024-05-04 14:47:44 +00:00
|
|
|
|
player_info_system: PlayerInfoSystem = None,
|
2022-10-16 10:11:04 +00:00
|
|
|
|
) -> None:
|
|
|
|
|
self.cookies_service = cookies_service
|
|
|
|
|
self.assets_service = assets_service
|
|
|
|
|
self.template_service = template_service
|
2023-03-14 01:27:22 +00:00
|
|
|
|
self.helper = helper
|
|
|
|
|
self.character_details = character_details
|
|
|
|
|
self.player_service = player_service
|
|
|
|
|
self.player_info_service = player_info_service
|
2024-05-04 14:47:44 +00:00
|
|
|
|
self.player_info_system = player_info_system
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2023-07-18 09:29:31 +00:00
|
|
|
|
async def get_avatar_data(self, character: Character, client: "GenshinClient") -> Optional["AvatarData"]:
|
2023-03-14 01:27:22 +00:00
|
|
|
|
detail = await self.character_details.get_character_details(client, character)
|
|
|
|
|
if detail is None:
|
2022-10-21 15:52:59 +00:00
|
|
|
|
return None
|
|
|
|
|
if character.id == 10000005: # 针对男草主
|
|
|
|
|
talents = []
|
|
|
|
|
for talent in detail.talents:
|
|
|
|
|
if "普通攻击" in talent.name:
|
|
|
|
|
talent.group_id = 1131
|
|
|
|
|
if talent.type in ["attack", "skill", "burst"]:
|
|
|
|
|
talents.append(talent)
|
|
|
|
|
else:
|
|
|
|
|
talents = [t for t in detail.talents if t.type in ["attack", "skill", "burst"]]
|
|
|
|
|
buffed_talents = []
|
|
|
|
|
for constellation in filter(lambda x: x.pos in [3, 5], character.constellations[: character.constellation]):
|
|
|
|
|
if result := list(
|
|
|
|
|
filter(lambda x: all([x.name in constellation.effect]), talents) # pylint: disable=W0640
|
|
|
|
|
):
|
|
|
|
|
buffed_talents.append(result[0].type)
|
|
|
|
|
return AvatarData(
|
|
|
|
|
avatar=character,
|
|
|
|
|
detail=detail,
|
|
|
|
|
icon=(await self.assets_service.avatar(character.id).side()).as_uri(),
|
|
|
|
|
weapon=(
|
|
|
|
|
await self.assets_service.weapon(character.weapon.id).__getattr__(
|
|
|
|
|
"icon" if character.weapon.ascension < 2 else "awaken"
|
|
|
|
|
)()
|
|
|
|
|
).as_uri(),
|
|
|
|
|
skills=[
|
|
|
|
|
SkillData(skill=s, buffed=s.type in buffed_talents)
|
|
|
|
|
for s in sorted(talents, key=lambda x: ["attack", "skill", "burst"].index(x.type))
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def get_avatars_data(
|
2023-07-18 09:29:31 +00:00
|
|
|
|
self, characters: Sequence[Character], client: "GenshinClient", max_length: int = None
|
2022-10-21 15:52:59 +00:00
|
|
|
|
) -> List["AvatarData"]:
|
2023-01-05 07:06:59 +00:00
|
|
|
|
async def _task(c):
|
|
|
|
|
return await self.get_avatar_data(c, client)
|
2022-10-21 15:52:59 +00:00
|
|
|
|
|
2023-01-05 07:06:59 +00:00
|
|
|
|
task_results = await asyncio.gather(*[_task(character) for character in characters])
|
2022-10-21 15:52:59 +00:00
|
|
|
|
|
2023-01-05 16:04:59 +00:00
|
|
|
|
return sorted(
|
|
|
|
|
list(filter(lambda x: x, task_results)),
|
|
|
|
|
key=lambda x: (
|
|
|
|
|
x.avatar.level,
|
|
|
|
|
x.avatar.rarity,
|
|
|
|
|
x.sum_of_skills(),
|
|
|
|
|
x.avatar.constellation,
|
|
|
|
|
# TODO 如果加入武器排序条件,需要把武器转化为图片url的处理后置
|
|
|
|
|
# x.weapon.level,
|
|
|
|
|
# x.weapon.rarity,
|
|
|
|
|
# x.weapon.refinement,
|
|
|
|
|
x.avatar.friendship,
|
|
|
|
|
),
|
|
|
|
|
reverse=True,
|
|
|
|
|
)[:max_length]
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2024-05-21 13:56:35 +00:00
|
|
|
|
async def avatar_list_render(
|
|
|
|
|
self,
|
|
|
|
|
base_render_data: Dict,
|
|
|
|
|
avatar_datas: List[AvatarData],
|
|
|
|
|
only_one_page: bool,
|
|
|
|
|
) -> Union[Tuple[Any], List["RenderResult"], None]:
|
|
|
|
|
def render_task(start_id: int, c: List[AvatarData]):
|
|
|
|
|
_render_data = {
|
|
|
|
|
"avatar_datas": c, # 角色数据
|
|
|
|
|
"start_id": start_id, # 开始序号
|
|
|
|
|
}
|
|
|
|
|
_render_data.update(base_render_data)
|
|
|
|
|
return self.template_service.render(
|
|
|
|
|
"genshin/avatar_list/main.jinja2",
|
|
|
|
|
_render_data,
|
|
|
|
|
viewport={"width": 1040, "height": 500},
|
|
|
|
|
full_page=True,
|
|
|
|
|
query_selector=".container",
|
|
|
|
|
file_type=FileType.PHOTO,
|
|
|
|
|
ttl=30 * 24 * 60 * 60,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if only_one_page:
|
|
|
|
|
return [await render_task(0, avatar_datas)]
|
2024-05-27 13:20:51 +00:00
|
|
|
|
image_count = len(avatar_datas)
|
|
|
|
|
while image_count > MAX_AVATAR_COUNT:
|
|
|
|
|
image_count /= 2
|
|
|
|
|
image_count = math.ceil(image_count)
|
|
|
|
|
avatar_datas_group = [avatar_datas[i : i + image_count] for i in range(0, len(avatar_datas), image_count)]
|
|
|
|
|
tasks = [render_task(i * image_count, c) for i, c in enumerate(avatar_datas_group)]
|
2024-05-21 13:56:35 +00:00
|
|
|
|
return await asyncio.gather(*tasks)
|
|
|
|
|
|
2024-06-19 08:29:47 +00:00
|
|
|
|
async def render(
|
2024-07-08 17:46:13 +00:00
|
|
|
|
self,
|
|
|
|
|
client: "GenshinClient",
|
|
|
|
|
user_id: int,
|
|
|
|
|
user_name: str,
|
|
|
|
|
all_avatars: bool = False,
|
|
|
|
|
filter_elements: set[Element] = None,
|
|
|
|
|
filter_weapon_types: set[WeaponType] = None,
|
2024-06-19 08:29:47 +00:00
|
|
|
|
) -> List["RenderResult"]:
|
|
|
|
|
characters = await client.get_genshin_characters(client.player_id)
|
2024-07-08 17:46:13 +00:00
|
|
|
|
if filter_elements:
|
|
|
|
|
characters = [c for c in characters if c.element in {element.name for element in filter_elements}]
|
|
|
|
|
if filter_weapon_types:
|
|
|
|
|
characters = [c for c in characters if WeaponType(c.weapon.type) in filter_weapon_types]
|
2024-06-19 08:29:47 +00:00
|
|
|
|
avatar_datas: List[AvatarData] = await self.get_avatars_data(
|
|
|
|
|
characters, client, None if all_avatars else MAX_AVATAR_COUNT
|
|
|
|
|
)
|
|
|
|
|
if not avatar_datas:
|
|
|
|
|
raise TooManyRequests()
|
|
|
|
|
name_card, avatar, nickname, rarity = await self.player_info_system.get_player_info(
|
|
|
|
|
client.player_id, user_id, user_name
|
|
|
|
|
)
|
|
|
|
|
base_render_data = {
|
|
|
|
|
"uid": mask_number(client.player_id), # 玩家uid
|
|
|
|
|
"nickname": nickname, # 玩家昵称
|
|
|
|
|
"avatar": avatar, # 玩家头像
|
|
|
|
|
"rarity": rarity, # 玩家头像对应的角色星级
|
|
|
|
|
"namecard": name_card, # 玩家名片
|
|
|
|
|
"has_more": len(characters) != len(avatar_datas), # 是否显示了全部角色
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return await self.avatar_list_render(base_render_data, avatar_datas, not all_avatars)
|
|
|
|
|
|
2024-03-25 12:48:19 +00:00
|
|
|
|
@handler.command("avatars", cookie=True, block=False)
|
|
|
|
|
@handler.message(filters.Regex(r"^(全部)?练度统计$"), cookie=True, block=False)
|
2023-08-21 14:42:44 +00:00
|
|
|
|
async def avatar_list(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE"):
|
2024-03-10 12:40:26 +00:00
|
|
|
|
user_id = await self.get_real_user_id(update)
|
|
|
|
|
user_name = self.get_real_user_name(update)
|
2024-06-12 11:59:16 +00:00
|
|
|
|
uid, offset = self.get_real_uid_or_offset(update)
|
2022-10-16 10:11:04 +00:00
|
|
|
|
message = update.effective_message
|
2023-05-04 14:27:49 +00:00
|
|
|
|
all_avatars = "全部" in message.text or "all" in message.text # 是否发送全部角色
|
2024-07-08 17:46:13 +00:00
|
|
|
|
filter_elements = parse_element(message.text)
|
|
|
|
|
filter_weapon_types = parse_weapon_type(message.text)
|
|
|
|
|
|
|
|
|
|
self.log_user(
|
|
|
|
|
update,
|
|
|
|
|
logger.info,
|
|
|
|
|
"[bold]练度统计[/bold]: all=%s, filter_elements=%s, filter_weapon_types=%s",
|
|
|
|
|
all_avatars,
|
|
|
|
|
filter_elements,
|
|
|
|
|
filter_weapon_types,
|
|
|
|
|
extra={"markup": True},
|
|
|
|
|
)
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2022-10-16 14:00:28 +00:00
|
|
|
|
try:
|
2024-06-12 11:59:16 +00:00
|
|
|
|
async with self.helper.genshin(user_id, player_id=uid, offset=offset) as client:
|
2024-06-07 15:22:30 +00:00
|
|
|
|
notice = await message.reply_text(f"{config.notice.bot_name}需要收集整理数据,还请耐心等待哦~")
|
2024-06-06 15:52:01 +00:00
|
|
|
|
self.add_delete_message_job(notice, delay=60)
|
2023-08-21 14:42:44 +00:00
|
|
|
|
await message.reply_chat_action(ChatAction.TYPING)
|
2024-07-08 17:46:13 +00:00
|
|
|
|
images = await self.render(
|
|
|
|
|
client, user_id, user_name, all_avatars, filter_elements, filter_weapon_types
|
|
|
|
|
)
|
2024-06-19 08:29:47 +00:00
|
|
|
|
except TooManyRequests:
|
|
|
|
|
reply_message = await message.reply_html("服务器熟啦 ~ 请稍后再试")
|
|
|
|
|
self.add_delete_message_job(reply_message, delay=20)
|
|
|
|
|
return
|
2023-07-18 09:29:31 +00:00
|
|
|
|
except SimnetBadRequest as e:
|
|
|
|
|
if e.ret_code == -502002:
|
2022-12-23 13:35:26 +00:00
|
|
|
|
reply_message = await message.reply_html("请先在米游社中使用一次<b>养成计算器</b>后再使用此功能~")
|
2023-03-14 01:27:22 +00:00
|
|
|
|
self.add_delete_message_job(reply_message, delay=20)
|
2022-10-16 14:00:28 +00:00
|
|
|
|
return
|
|
|
|
|
raise e
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2024-05-21 13:56:35 +00:00
|
|
|
|
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
|
|
|
|
|
|
|
|
|
for group in ArkoWrapper(images).group(10): # 每 10 张图片分一个组
|
2024-06-10 16:58:39 +00:00
|
|
|
|
await RenderGroupResult(results=group).reply_media_group(message, write_timeout=60)
|
2022-10-16 10:11:04 +00:00
|
|
|
|
|
2024-03-10 12:40:26 +00:00
|
|
|
|
self.log_user(
|
|
|
|
|
update,
|
|
|
|
|
logger.info,
|
2024-05-21 13:56:35 +00:00
|
|
|
|
"[bold]练度统计[/bold]发送图片成功",
|
2022-10-16 10:11:04 +00:00
|
|
|
|
extra={"markup": True},
|
|
|
|
|
)
|
2024-06-19 08:29:47 +00:00
|
|
|
|
|
|
|
|
|
async def avatar_list_use_by_inline(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
|
|
|
|
|
callback_query = update.callback_query
|
|
|
|
|
user_id = await self.get_real_user_id(update)
|
|
|
|
|
user_name = self.get_real_user_name(update)
|
|
|
|
|
uid = IInlineUseData.get_uid_from_context(context)
|
|
|
|
|
self.log_user(update, logger.info, "查询练度统计")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
async with self.helper.genshin(user_id, player_id=uid) as client:
|
|
|
|
|
images = await self.render(client, user_id, user_name)
|
|
|
|
|
render = images[0]
|
|
|
|
|
except TooManyRequests:
|
|
|
|
|
await callback_query.answer("服务器熟啦 ~ 请稍后再试", show_alert=True)
|
|
|
|
|
return
|
|
|
|
|
except SimnetBadRequest as e:
|
|
|
|
|
if e.ret_code == -502002:
|
|
|
|
|
await callback_query.answer("请先在米游社中使用一次养成计算器后再使用此功能~", show_alert=True)
|
|
|
|
|
return
|
|
|
|
|
raise e
|
|
|
|
|
await render.edit_inline_media(callback_query)
|
|
|
|
|
|
|
|
|
|
async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]:
|
|
|
|
|
return [
|
|
|
|
|
IInlineUseData(
|
|
|
|
|
text="练度统计",
|
|
|
|
|
hash="avatar_list",
|
|
|
|
|
callback=self.avatar_list_use_by_inline,
|
|
|
|
|
cookie=True,
|
|
|
|
|
player=True,
|
|
|
|
|
)
|
|
|
|
|
]
|