Support wish waiting list

This commit is contained in:
xtaodada 2024-11-19 22:08:16 +08:00
parent 96e3d72a9b
commit 6fac9df6d6
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
4 changed files with 327 additions and 1 deletions

View File

@ -40,6 +40,7 @@ class SetCommandPlugin(Plugin):
BotCommand("warp_log", "查看跃迁记录"), BotCommand("warp_log", "查看跃迁记录"),
BotCommand("warp_log_online_view", "抽卡记录在线浏览"), BotCommand("warp_log_online_view", "抽卡记录在线浏览"),
BotCommand("warp_log_rank", "抽卡排行榜"), BotCommand("warp_log_rank", "抽卡排行榜"),
BotCommand("warp_waiting_list", "未复刻列表"),
BotCommand("action_log", "查询登录记录"), BotCommand("action_log", "查询登录记录"),
BotCommand("dailynote", "查询实时便笺"), BotCommand("dailynote", "查询实时便笺"),
BotCommand("redeem", "(国际服)兑换 Key"), BotCommand("redeem", "(国际服)兑换 Key"),

View File

@ -0,0 +1,247 @@
import asyncio
import math
from datetime import datetime
from functools import partial
from typing import Dict, Tuple, List, TYPE_CHECKING, Optional
from pydantic import BaseModel
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.constants import ChatAction
from telegram.ext import filters
from core.dependence.assets import AssetsService, AssetsCouldNotFound
from gram_core.config import config
from gram_core.plugin import Plugin, handler
from gram_core.plugin.methods.inline_use_data import IInlineUseData
from gram_core.services.template.services import TemplateService
from metadata.pool.pool import POOL_11 as CHARACTER_POOL, POOL_12 as WEAPON_POOL
from plugins.tools.phone_theme import PhoneThemeService
from utils.log import logger
if TYPE_CHECKING:
from telegram import Update
from telegram.ext import ContextTypes
from gram_core.services.template.models import RenderResult
MIX_POOL = []
class WishWaitingListData(BaseModel):
name: str
"""名称"""
icon: Optional[str] = None
"""图标"""
up_times: int
"""总共 UP 次数"""
last_up_time: datetime
"""上一次 UP 时间"""
last_up_day: int
"""距离上一次 UP 多少天"""
class WishWaitingListPlugin(Plugin):
"""未复刻列表"""
def __init__(
self,
assets: AssetsService,
template_service: TemplateService,
phone_theme: PhoneThemeService,
):
self.assets_service = assets
self.template_service = template_service
self.phone_theme = phone_theme
self.waiting_list = {}
async def initialize(self) -> None:
asyncio.create_task(self.init_data())
async def init_data(self):
now = datetime.now()
if self.waiting_list and (now - self.waiting_list["time"]).total_seconds() < 3600:
return
data = {
"avatar": await self._get_waiting_list(CHARACTER_POOL, "avatar", self.assets_service.avatar.square),
"weapon": await self._get_waiting_list(WEAPON_POOL, "weapon", self.assets_service.light_cone.icon),
"time": now,
}
self.waiting_list.update(data)
@staticmethod
async def _ignore_static_pool(pool_type: str):
if pool_type == "avatar":
return ["姬子", "瓦尔特", "布洛妮娅", "杰帕德", "克拉拉", "彦卿", "白露"]
return []
@staticmethod
async def _ignore_mix_pool(
pool_type: str,
five_times: Dict[str, WishWaitingListData],
four_times: Dict[str, WishWaitingListData],
):
now = datetime.now()
for p in MIX_POOL:
does = p[pool_type]
last_up_time = datetime.strptime(p["to"], "%Y-%m-%d %H:%M:%S")
last_up_day = math.ceil((now - last_up_time).total_seconds() / 86400)
for do in does:
t: WishWaitingListData = five_times.get(do) or four_times.get(do)
if not t:
continue
t.up_times += 1
if t.last_up_time < last_up_time:
t.last_up_time = last_up_time
t.last_up_day = last_up_day
async def _get_waiting_list(
self,
pool,
pool_type: str,
assets,
) -> Tuple[Dict[str, WishWaitingListData], List[str], Dict[str, WishWaitingListData], List[str]]:
now = datetime.now()
five_times: Dict[str, WishWaitingListData] = {}
five_data = []
four_times: Dict[str, WishWaitingListData] = {}
four_data = []
ignore = await self._ignore_static_pool(pool_type)
for p in pool:
fives = p["five"]
fours = p["four"]
last_up_time = datetime.strptime(p["to"], "%Y-%m-%d %H:%M:%S")
last_up_day = max(math.ceil((now - last_up_time).total_seconds() / 86400), 0)
for i, times in [(fives, five_times), (fours, four_times)]:
for n in i:
if n in ignore:
continue
if n in times:
times[n].up_times += 1
else:
try:
icon = assets(n).as_uri()
except (AssetsCouldNotFound, AttributeError):
icon = ""
times[n] = WishWaitingListData(
name=n,
icon=icon,
up_times=1,
last_up_time=last_up_time,
last_up_day=last_up_day,
)
await self._ignore_mix_pool(pool_type, five_times, four_times)
for times, data in [(five_times, five_data), (four_times, four_data)]:
data.clear()
data.extend(list(times.keys()))
data.sort(key=lambda j: times[j].last_up_day, reverse=True) # pylint: disable=W0640
return five_times, five_data, four_times, four_data
@handler.command("warp_waiting_list", block=False)
@handler.message(filters=filters.Regex(r"^未复刻列表?(角色|武器|)$"), block=False)
async def command_start(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
user_id = await self.get_real_user_id(update)
message = update.effective_message
is_avatar = True
if args := self.get_args(context):
if "角色" in args:
is_avatar = True
elif "武器" in args:
is_avatar = False
self.log_user(update, logger.info, "查询未复刻列表 is_avatar[%s]", is_avatar)
await message.reply_chat_action(ChatAction.TYPING)
image = await self.render(user_id, is_avatar)
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
await image.reply_photo(message, reply_markup=await self.get_wish_waiting_list_button(user_id, is_avatar))
async def render(self, user_id: int, is_avatar: bool) -> "RenderResult":
await self.init_data()
_data = self.waiting_list["avatar" if is_avatar else "weapon"]
data = {
"fiveLog": _data[1],
"fiveData": _data[0],
"fourLog": _data[3],
"fourData": _data[2],
}
name_card = (await self.phone_theme.get_phone_theme(None, user_id)).as_uri()
data["background"] = name_card
return await self.template_service.render(
"starrail/gacha_log/wish_waiting_list.jinja2",
data,
full_page=True,
query_selector=".body_box",
)
@staticmethod
async def get_wish_waiting_list_button(user_id: int, is_avatar: bool):
return InlineKeyboardMarkup(
[
[
(
InlineKeyboardButton(
">> 切换到武器池 <<", callback_data=f"get_wish_waiting_list|{user_id}|weapon"
)
if is_avatar
else InlineKeyboardButton(
">> 切换到角色池 <<", callback_data=f"get_wish_waiting_list|{user_id}|avatar"
)
)
]
]
)
@handler.callback_query(pattern=r"^get_wish_waiting_list\|", block=False)
async def get_wish_waiting_list(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_waiting_list_callback(
callback_query_data: str,
) -> Tuple[str, int]:
_data = callback_query_data.split("|")
_user_id = int(_data[1])
_result = _data[2]
logger.debug(
"callback_query_data函数返回 result[%s] user_id[%s]",
_result,
_user_id,
)
return _result, _user_id
try:
pool_type, user_id = await get_wish_waiting_list_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
is_avatar = pool_type == "avatar"
await self._get_wish_waiting_list(update, context, is_avatar)
async def _get_wish_waiting_list(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE", is_avatar: bool) -> None:
callback_query = update.callback_query
user = callback_query.from_user
user_id = user.id
image = await self.render(user_id, is_avatar)
await image.edit_inline_media(
callback_query,
reply_markup=await self.get_wish_waiting_list_button(user_id, is_avatar),
)
async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]:
types = {"角色": "avatar", "武器": "weapon"}
data = []
for k, v in types.items():
data.append(
IInlineUseData(
text=f"未复刻列表 - {k}",
hash=f"wish_waiting_list_{v}",
callback=partial(self._get_wish_waiting_list, is_avatar=v == "avatar"),
)
)
return data

View File

@ -5,16 +5,19 @@ from core.dependence.assets import AssetsService, AssetsCouldNotFound
from core.services.players.services import PlayerInfoService from core.services.players.services import PlayerInfoService
from gram_core.dependence.redisdb import RedisDB from gram_core.dependence.redisdb import RedisDB
from gram_core.plugin import Plugin from gram_core.plugin import Plugin
from gram_core.services.players import PlayersService
from utils.log import logger from utils.log import logger
class PhoneThemeService(Plugin): class PhoneThemeService(Plugin):
def __init__( def __init__(
self, self,
players_service: PlayersService,
player_info_service: PlayerInfoService, player_info_service: PlayerInfoService,
asset_service: AssetsService, asset_service: AssetsService,
redis: RedisDB, redis: RedisDB,
) -> None: ) -> None:
self.players_service = players_service
self.player_info_service = player_info_service self.player_info_service = player_info_service
self.assets = asset_service self.assets = asset_service
self.redis = redis.client self.redis = redis.client
@ -55,8 +58,13 @@ class PhoneThemeService(Plugin):
return phone_theme return phone_theme
return None return None
async def get_phone_theme(self, player_id: int): async def get_phone_theme(self, player_id: Optional[int], user_id: Optional[int] = None):
try: try:
if not player_id and user_id is not None:
player = await self.players_service.get_player(user_id)
if player is None:
return self.get_default_phone_theme()
player_id = player.player_id
phone_theme = await self.get_phone_theme_id(player_id) phone_theme = await self.get_phone_theme_id(player_id)
return self.assets.phone_theme.icon(phone_theme) return self.assets.phone_theme.icon(phone_theme)
except AssetsCouldNotFound as e: except AssetsCouldNotFound as e:

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<link rel="stylesheet" type="text/css" href="gacha_log.css" />
<link type="text/css" href="../../styles/public.css" rel="stylesheet" />
<link rel="preload" href="./img/starrail.png" as="image" />
<script src="../../js/tailwindcss-3.1.8.js"></script>
<title>Title</title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="info_box">
<div class="header p-2 rounded-xl mb-6" style='background-image: url("{{ background }}")'>
<div class="frame p-4 rounded-lg border-solid border-2 flex items-center">
<div>
<h2 class="font-bold italic">未复刻列表</h2>
<h2 class="italic">
<span class="label text-neutral-600 label_302"
>角色池</span
>
</h2>
</div>
</div>
</div>
<div class="data_box">
<div class="line_box">
<span class="line"></span>
<span class="text">五星列表</span>
<span class="line"></span>
</div>
<div class="card_list">
{% for val in fiveLog %}
{% set d = fiveData[val] %}
<div class="item star5">
{% if d.last_up_day == 0 %}
<span class="minimum">UP</span>
{% endif %}
<img class="role" src="{{ d.icon }}" alt="" />
<div class="num_name">{{ d.last_up_day }}</div>
</div>
{% endfor %}
</div>
<div class="line_box">
<span class="line"></span>
<span class="text">四星列表</span>
<span class="line"></span>
</div>
<div class="card_list">
{% for val in fourLog %}
{% set d = fourData[val] %}
<div class="item star4">
{% if d.last_up_day == 0 %}
<span class="minimum">UP</span>
{% endif %}
<img class="role" src="{{ d.icon }}" alt="" />
<div class="num_name">{{ d.last_up_day }}</div>
</div>
{% endfor %}
</div>
</div>
<div class="logo">Template By Yunzai-Bot x Generated By PamGram</div>
</div>
</div>
</body>
</html>