PaiGram/plugins/genshin/wish_log_rank.py

351 lines
14 KiB
Python
Raw Normal View History

from typing import TYPE_CHECKING, List, Tuple, Dict, Optional
2024-09-12 13:13:05 +00:00
from pydantic import BaseModel
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ChatAction
from telegram.error import BadRequest
from telegram.ext import filters
from core.dependence.assets import AssetsService
from core.plugin import Plugin, handler
from core.services.template.models import FileType
from core.services.template.services import TemplateService
from gram_core.config import config
from gram_core.dependence.redisdb import RedisDB
from gram_core.services.gacha_log_rank.models import GachaLogRank, GachaLogTypeEnum, GachaLogQueryTypeEnum
from gram_core.services.gacha_log_rank.services import GachaLogRankService
from gram_core.services.players import PlayersService
from modules.gacha_log.ranks import GachaLogRanks
from plugins.tools.player_info import PlayerInfoSystem
from utils.log import logger
from utils.uid import mask_number
if TYPE_CHECKING:
from telegram import Update
from telegram.ext import ContextTypes
class RankPlayerModel(BaseModel):
player_id: int
num: int
nickname: Optional[str]
2024-09-12 13:13:05 +00:00
score_1: int
score_2: float
score_3: float
score_4: float
score_5: float
@property
def mask_uid(self) -> str:
return mask_number(self.player_id)
class RankDataModel(BaseModel):
players: List[RankPlayerModel]
count: int
class WishLogRankPlugin(Plugin):
"""抽卡数据排行"""
TYPES = [
("角色-总抽数", GachaLogTypeEnum.CHARACTER, GachaLogQueryTypeEnum.TOTAL),
("角色-五星平均", GachaLogTypeEnum.CHARACTER, GachaLogQueryTypeEnum.FIVE_STAR_AVG),
("角色-UP平均", GachaLogTypeEnum.CHARACTER, GachaLogQueryTypeEnum.UP_STAR_AVG),
("角色-小保底百分比", GachaLogTypeEnum.CHARACTER, GachaLogQueryTypeEnum.NO_WARP),
("武器-总抽数", GachaLogTypeEnum.WEAPON, GachaLogQueryTypeEnum.TOTAL),
("武器-五星平均", GachaLogTypeEnum.WEAPON, GachaLogQueryTypeEnum.FIVE_STAR_AVG),
("常驻-总抽数", GachaLogTypeEnum.DEFAULT, GachaLogQueryTypeEnum.TOTAL),
("常驻-五星平均", GachaLogTypeEnum.DEFAULT, GachaLogQueryTypeEnum.FIVE_STAR_AVG),
("集录-总抽数", GachaLogTypeEnum.HUN, GachaLogQueryTypeEnum.TOTAL),
("集录-五星平均", GachaLogTypeEnum.HUN, GachaLogQueryTypeEnum.FIVE_STAR_AVG),
]
def __init__(
self,
assets_service: AssetsService = None,
template_service: TemplateService = None,
player_service: PlayersService = None,
redis: RedisDB = None,
player_info: PlayerInfoSystem = None,
gacha_log_rank_service: GachaLogRankService = None,
) -> None:
self.assets_service = assets_service
self.template_service = template_service
self.player_service = player_service
self.redis = redis.client
self.player_info = player_info
self.gacha_log_rank_service = gacha_log_rank_service
self.limit = 20
self.key = "plugins:gacha_log_rank"
self.expire = 30 * 60 # 30 分钟
self.expire2 = 5 * 60 # 5 分钟
self.wish_photo = None
async def get_nickname_from_uid(self, player_id: int) -> str:
nickname = "Unknown"
try:
_, _, nickname, _ = await self.player_info.get_player_info(player_id, None, "")
except Exception:
logger.warning("获取玩家昵称失败 player_id[%s]", player_id)
return nickname
@staticmethod
def mysql_to_model(rank: "GachaLogRank", num: int, nickname: str) -> RankPlayerModel:
return RankPlayerModel(
player_id=rank.player_id,
num=num,
nickname=nickname,
score_1=rank.score_1 or 0,
score_2=(rank.score_2 / 100.0) if rank.score_2 else 0,
score_3=(rank.score_3 / 100.0) if rank.score_3 else 0,
score_4=(rank.score_4 / 100.0) if rank.score_4 else 0,
score_5=(rank.score_5 / 100.0) if rank.score_5 else 0,
)
@staticmethod
def get_desc_type(query_type: "GachaLogQueryTypeEnum") -> bool:
desc = True
if query_type not in (GachaLogQueryTypeEnum.TOTAL, GachaLogQueryTypeEnum.NO_WARP):
desc = False
return desc
async def get_first_rank_players_from_sql(
self, rank_type: "GachaLogTypeEnum", query_type: "GachaLogQueryTypeEnum", desc: bool
) -> RankDataModel:
real_desc = self.get_desc_type(query_type)
if desc:
real_desc = not real_desc
ranks_uids = await self.gacha_log_rank_service.get_ranks_cache(rank_type, query_type, desc=real_desc)
count = await self.gacha_log_rank_service.get_ranks_length_cache(rank_type, query_type)
uid_list = [int(uid) for uid, _ in ranks_uids]
ranks = await self.gacha_log_rank_service.get_ranks_by_ids(rank_type, uid_list)
players = []
for rank in ranks:
nickname = await self.get_nickname_from_uid(rank.player_id)
players.append(self.mysql_to_model(rank, uid_list.index(rank.player_id) + 1, nickname))
players.sort(key=lambda x: x.num)
return RankDataModel(players=players, count=count)
async def get_first_rank_players_from_cache(
self, rank_type: "GachaLogTypeEnum", query_type: "GachaLogQueryTypeEnum", desc: bool
) -> RankDataModel:
desc_int = 1 if desc else 0
key = f"{self.key}:{rank_type.value}:{query_type.value}:{desc_int}:total"
data = await self.redis.get(key)
if data:
return RankDataModel.parse_raw(str(data, encoding="utf-8"))
data = await self.get_first_rank_players_from_sql(rank_type, query_type, desc)
await self.redis.set(key, data.json(by_alias=True), ex=self.expire)
return data
async def get_my_players_from_sql(
self, user_id: int, rank_type: "GachaLogTypeEnum", query_type: "GachaLogQueryTypeEnum", desc: bool
) -> RankDataModel:
players1 = await self.player_service.get_all_by_user_id(user_id)
ranks = await self.gacha_log_rank_service.get_ranks_by_ids(rank_type, [player.player_id for player in players1])
players = []
real_desc = self.get_desc_type(query_type)
if desc:
real_desc = not real_desc
for rank in ranks:
num = await self.gacha_log_rank_service.get_rank_by_player_id_cache(
rank_type, query_type, rank.player_id, desc=real_desc
)
if num is None:
continue
nickname = await self.get_nickname_from_uid(rank.player_id)
players.append(self.mysql_to_model(rank, num + 1, nickname))
players.sort(key=lambda x: x.num)
return RankDataModel(players=players, count=len(players))
async def get_my_players_from_cache(
self, user_id: int, rank_type: "GachaLogTypeEnum", query_type: "GachaLogQueryTypeEnum", desc: bool
) -> RankDataModel:
desc_int = 1 if desc else 0
key = f"{self.key}:{rank_type.value}:{query_type.value}:{desc_int}:{user_id}"
data = await self.redis.get(key)
if data:
return RankDataModel.parse_raw(str(data, encoding="utf-8"))
data = await self.get_my_players_from_sql(user_id, rank_type, query_type, desc)
await self.redis.set(key, data.json(by_alias=True), ex=self.expire2)
return data
@staticmethod
def get_data_key_map_by_type(rank_type: "GachaLogTypeEnum"):
data = {
"总抽数": "score_1",
"五星平均": "score_2",
}
if rank_type == GachaLogTypeEnum.CHARACTER:
data.update(
{
"UP平均": "score_3",
"小保底百分比": "score_4",
}
)
return data
def gen_button(self, user_id: int, desc: bool = False) -> List[List[InlineKeyboardButton]]:
types = [self.TYPES[i : i + 2] for i in range(0, len(self.TYPES), 2)]
if desc:
now_bind, new_bind, now_int, new_int = "非酋榜", "欧皇榜", 1, 0
else:
now_bind, new_bind, now_int, new_int = "欧皇榜", "非酋榜", 0, 1
data = [
[
InlineKeyboardButton(
idx[0],
callback_data=f"wish_log_rank|{user_id}|{idx[1].value}|{idx[2].value}|{now_int}",
)
for idx in id1
]
for id1 in types
]
page_button = [
InlineKeyboardButton(f"当前是{now_bind}", callback_data=f"wish_log_rank_button|{user_id}|ignore"),
InlineKeyboardButton(f"切换到{new_bind}", callback_data=f"wish_log_rank_button|{user_id}|{new_int}"),
]
data.append(page_button)
return data
@handler.command("wish_log_rank", block=False)
@handler.message(filters.Regex(r"^抽卡排行榜(.*)$"), block=False)
async def wish_log_rank(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE"):
user_id = await self.get_real_user_id(update)
message = update.effective_message
buttons = self.gen_button(user_id)
if isinstance(self.wish_photo, str):
photo = self.wish_photo
else:
photo = open("resources/img/wish.jpg", "rb")
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
reply_message = await message.reply_photo(
photo=photo,
caption="请选择你要查询的抽卡排行榜",
reply_markup=InlineKeyboardMarkup(buttons),
)
if reply_message.photo:
self.wish_photo = reply_message.photo[-1].file_id
@handler.callback_query(pattern=r"^wish_log_rank\|", block=False)
async def wish_log_rank_callback(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
callback_query = update.callback_query
user = callback_query.from_user
message = callback_query.message
async def get_wish_log_rank_callback(
callback_query_data: str,
) -> Tuple[int, GachaLogTypeEnum, GachaLogQueryTypeEnum, bool]:
_data = callback_query_data.split("|")
_user_id = int(_data[1])
_rank_type = GachaLogTypeEnum(int(_data[2]))
_query_type = GachaLogQueryTypeEnum(_data[3])
_desc = bool(int(_data[4]))
logger.debug(
"callback_query_data函数返回 user_id[%s] rank_type[%s] query_type[%s] desc[%s]",
_user_id,
_rank_type,
_query_type,
_desc,
)
return _user_id, _rank_type, _query_type, _desc
try:
user_id, rank_type, query_type, desc = await get_wish_log_rank_callback(callback_query.data)
except IndexError:
await callback_query.answer("按钮数据已过期,请重新获取。", show_alert=True)
self.add_delete_message_job(message, delay=1)
return
if user.id != user_id:
await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True)
return
await self.render(update, context, user_id, rank_type, query_type, desc)
async def get_render_data(
self,
user_id: int,
rank_type: "GachaLogTypeEnum",
query_type: "GachaLogQueryTypeEnum",
desc: bool,
) -> Dict:
my_data = await self.get_my_players_from_cache(user_id, rank_type, query_type, desc)
list_data = await self.get_first_rank_players_from_cache(rank_type, query_type, desc)
name_card = (await self.assets_service.namecard(0).navbar()).as_uri()
return {
"data_list": [my_data, list_data],
"count": list_data.count,
"namecard": name_card,
"pool_name": GachaLogRanks.ITEM_LIST_MAP_REV.get(rank_type),
"data_key_map": self.get_data_key_map_by_type(rank_type),
"main_key": query_type,
"desc": desc,
}
async def render(
self,
update: "Update",
_: "ContextTypes.DEFAULT_TYPE",
user_id: int,
rank_type: "GachaLogTypeEnum",
query_type: "GachaLogQueryTypeEnum",
desc: bool = False,
):
callback_query = update.callback_query
message = callback_query.message
await message.reply_chat_action(ChatAction.TYPING)
render_data = await self.get_render_data(user_id, rank_type, query_type, desc)
try:
await callback_query.answer(text="正在渲染图片中 请稍等 请不要重复点击按钮", show_alert=False)
except BadRequest:
pass
png_data = await self.template_service.render(
"genshin/wish_log_rank/rank.jinja2",
render_data,
viewport={"width": 1040, "height": 500},
full_page=True,
query_selector=".container",
file_type=FileType.PHOTO,
ttl=1 * 60 * 60,
)
await png_data.edit_media(message)
@handler.callback_query(pattern=r"^wish_log_rank_button\|", block=False)
async def wish_log_rank_button_callback(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
callback_query = update.callback_query
user = callback_query.from_user
message = callback_query.message
async def get_wish_log_rank_button_callback(
callback_query_data: str,
) -> Tuple[int, bool, bool]:
_data = callback_query_data.split("|")
_user_id = int(_data[1])
_ignore = _data[2] == "ignore"
_desc = False if _ignore else bool(int(_data[2]))
logger.debug(
"callback_query_data函数返回 user_id[%s] ignore[%s] desc[%s]",
_user_id,
_ignore,
_desc,
)
return _user_id, _ignore, _desc
try:
user_id, ignore, desc = await get_wish_log_rank_button_callback(callback_query.data)
except IndexError:
await callback_query.answer("按钮数据已过期,请重新获取。", show_alert=True)
self.add_delete_message_job(message, delay=1)
return
if user.id != user_id:
await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True)
return
if ignore:
await callback_query.answer("无效按钮", show_alert=False)
return
buttons = self.gen_button(user_id, desc)
await message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(buttons))
await callback_query.answer("已切换", show_alert=False)