mirror of
https://github.com/PaiGramTeam/PamGram.git
synced 2024-11-21 13:48:19 +00:00
✨ Support starrail phone_theme assets
This commit is contained in:
parent
2ab86b71d9
commit
08b558528b
@ -11,6 +11,7 @@ from modules.wiki.base import WikiModel
|
||||
from modules.wiki.models.avatar_config import AvatarIcon
|
||||
from modules.wiki.models.head_icon import HeadIcon
|
||||
from modules.wiki.models.light_cone_config import LightConeIcon
|
||||
from modules.wiki.models.phone_theme import PhoneTheme
|
||||
from utils.const import PROJECT_ROOT
|
||||
from utils.log import logger
|
||||
from utils.typedefs import StrOrURL, StrOrInt
|
||||
@ -23,6 +24,7 @@ DATA_MAP = {
|
||||
"avatar_eidolon": WikiModel.BASE_URL + "avatar_eidolon_icons.json",
|
||||
"avatar_skill": WikiModel.BASE_URL + "skill/info.json",
|
||||
"head_icon": WikiModel.BASE_URL + "head_icons.json",
|
||||
"phone_theme": WikiModel.BASE_URL + "phone_themes.json",
|
||||
}
|
||||
|
||||
|
||||
@ -345,6 +347,59 @@ class _HeadIconAssets(_AssetsService):
|
||||
raise AssetsCouldNotFound("头像素材图标不存在", target)
|
||||
|
||||
|
||||
class _PhoneThemeAssets(_AssetsService):
|
||||
path: Path
|
||||
data: List[PhoneTheme]
|
||||
id_map: Dict[int, PhoneTheme]
|
||||
|
||||
def __init__(self, client: Optional[AsyncClient] = None) -> None:
|
||||
super().__init__(client)
|
||||
self.path = ASSETS_PATH.joinpath("phone_theme")
|
||||
self.path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
async def initialize(self):
|
||||
logger.info("正在初始化手机壁纸素材图标")
|
||||
html = await self.client.get(DATA_MAP["phone_theme"])
|
||||
self.data = [PhoneTheme(**data) for data in html.json()]
|
||||
self.id_map = {theme.id: theme for theme in self.data}
|
||||
tasks = []
|
||||
for theme in self.data:
|
||||
path = self.path / f"{theme.id}.png"
|
||||
if not path.exists():
|
||||
if theme.urls[0]:
|
||||
tasks.append(self._download(theme.urls[0], path))
|
||||
elif theme.urls[1]:
|
||||
tasks.append(self._download(theme.urls[1], path))
|
||||
if len(tasks) >= 100:
|
||||
await asyncio.gather(*tasks)
|
||||
tasks = []
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks)
|
||||
logger.info("手机壁纸素材图标初始化完成")
|
||||
|
||||
def get_path(self, theme: PhoneTheme, ext: str) -> Path:
|
||||
path = self.path / f"{theme.id}.{ext}"
|
||||
return path
|
||||
|
||||
def get_by_id(self, id_: int) -> Optional[PhoneTheme]:
|
||||
return self.id_map.get(id_, None)
|
||||
|
||||
def get_target(self, target: StrOrInt, second_target: StrOrInt = None) -> Optional[PhoneTheme]:
|
||||
data = self.get_by_id(target)
|
||||
if data:
|
||||
return data
|
||||
if second_target:
|
||||
return self.get_target(second_target)
|
||||
raise AssetsCouldNotFound("手机壁纸素材图标不存在", target)
|
||||
|
||||
def icon(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
theme = self.get_target(target, second_target)
|
||||
png_path = self.get_path(theme, "png")
|
||||
if png_path.exists():
|
||||
return png_path
|
||||
raise AssetsCouldNotFound("手机壁纸素材图标不存在", target)
|
||||
|
||||
|
||||
class AssetsService(BaseService.Dependence):
|
||||
"""asset服务
|
||||
|
||||
@ -361,6 +416,9 @@ class AssetsService(BaseService.Dependence):
|
||||
head_icon: _HeadIconAssets
|
||||
"""头像"""
|
||||
|
||||
phone_theme: _PhoneThemeAssets
|
||||
"""手机壁纸"""
|
||||
|
||||
light_cone: _LightConeAssets
|
||||
"""光锥"""
|
||||
|
||||
@ -368,9 +426,11 @@ class AssetsService(BaseService.Dependence):
|
||||
self.client = AsyncClient(timeout=60.0)
|
||||
self.avatar = _AvatarAssets(self.client)
|
||||
self.head_icon = _HeadIconAssets(self.client)
|
||||
self.phone_theme = _PhoneThemeAssets(self.client)
|
||||
self.light_cone = _LightConeAssets(self.client)
|
||||
|
||||
async def initialize(self): # pylint: disable=W0221
|
||||
await self.avatar.initialize()
|
||||
await self.head_icon.initialize()
|
||||
await self.phone_theme.initialize()
|
||||
await self.light_cone.initialize()
|
||||
|
@ -119,6 +119,15 @@ class PlayerInfoService(BaseService):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def set_name_card(self, player_id: int, phone_theme_id: int):
|
||||
player_info = await self._players_info_repository.get_by_player_id(player_id)
|
||||
if player_info is None:
|
||||
return False
|
||||
player_info.name_card = phone_theme_id
|
||||
player_info.last_save_time = datetime.now()
|
||||
await self._players_info_repository.update(player_info)
|
||||
return True
|
||||
|
||||
async def get_form_sql(self, player: Player):
|
||||
return await self._players_info_repository.get(player.user_id, player.player_id)
|
||||
|
||||
|
23
modules/wiki/models/phone_theme.py
Normal file
23
modules/wiki/models/phone_theme.py
Normal file
@ -0,0 +1,23 @@
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class PhoneTheme(BaseModel):
|
||||
id: int
|
||||
"""ID"""
|
||||
name: str
|
||||
"""名称"""
|
||||
description: str
|
||||
"""描述"""
|
||||
story: Optional[str] = None
|
||||
"""故事"""
|
||||
urls: List[str]
|
||||
|
||||
|
||||
# 原始数据
|
||||
|
||||
|
||||
class PhoneThemeConfig(BaseModel):
|
||||
ID: int
|
||||
PhoneThemeMain: str
|
@ -13,6 +13,7 @@ from core.services.template.services import TemplateService
|
||||
from core.services.wiki.services import WikiService
|
||||
from plugins.tools.genshin import GenshinHelper, CharacterDetails
|
||||
from plugins.tools.head_icon import HeadIconService
|
||||
from plugins.tools.phone_theme import PhoneThemeService
|
||||
from utils.log import logger
|
||||
from utils.uid import mask_number
|
||||
|
||||
@ -64,6 +65,7 @@ class AvatarListPlugin(Plugin):
|
||||
helper: GenshinHelper = None,
|
||||
character_details: CharacterDetails = None,
|
||||
head_icon: HeadIconService = None,
|
||||
phone_theme: PhoneThemeService = None,
|
||||
) -> None:
|
||||
self.cookies_service = cookies_service
|
||||
self.assets_service = assets_service
|
||||
@ -72,6 +74,7 @@ class AvatarListPlugin(Plugin):
|
||||
self.helper = helper
|
||||
self.character_details = character_details
|
||||
self.head_icon = head_icon
|
||||
self.phone_theme = phone_theme
|
||||
|
||||
async def get_avatar_data(
|
||||
self, character_id: int, client: "StarRailClient"
|
||||
@ -170,6 +173,7 @@ class AvatarListPlugin(Plugin):
|
||||
"avatar_datas": avatar_datas, # 角色数据
|
||||
"has_more": has_more, # 是否显示了全部角色
|
||||
"avatar": (await self.head_icon.get_head_icon(client.player_id)).as_uri(),
|
||||
"background": (await self.phone_theme.get_phone_theme(client.player_id)).as_uri(),
|
||||
}
|
||||
|
||||
as_document = all_avatars and len(characters) > MAX_AVATAR_COUNT
|
||||
|
@ -7,10 +7,12 @@ from telegram.ext import CallbackContext, filters
|
||||
|
||||
from core.plugin import Plugin, handler
|
||||
from core.services.cookies.error import TooManyRequestPublicCookies
|
||||
from core.services.players.services import PlayerInfoService
|
||||
from core.services.template.models import RenderResult
|
||||
from core.services.template.services import TemplateService
|
||||
from plugins.tools.genshin import GenshinHelper
|
||||
from plugins.tools.head_icon import HeadIconService
|
||||
from plugins.tools.phone_theme import PhoneThemeService
|
||||
from utils.log import logger
|
||||
from utils.uid import mask_number
|
||||
|
||||
@ -29,10 +31,14 @@ class PlayerStatsPlugins(Plugin):
|
||||
template: TemplateService,
|
||||
helper: GenshinHelper,
|
||||
head_icon: HeadIconService,
|
||||
phone_theme: PhoneThemeService,
|
||||
player_info_service: PlayerInfoService,
|
||||
):
|
||||
self.template_service = template
|
||||
self.helper = helper
|
||||
self.head_icon = head_icon
|
||||
self.phone_theme = phone_theme
|
||||
self.player_info_service = player_info_service
|
||||
|
||||
async def get_uid(self, user_id: int, args: List[str], reply: Optional[Message]) -> int:
|
||||
"""通过消息获取 uid,优先级:args > reply > self"""
|
||||
@ -88,7 +94,7 @@ class PlayerStatsPlugins(Plugin):
|
||||
except SimnetBadRequest:
|
||||
rogue = None
|
||||
logger.debug(user_info)
|
||||
|
||||
await self.set_name_card(uid, user_info.phone_background_image_url)
|
||||
data = {
|
||||
"uid": mask_number(uid),
|
||||
"info": user_info.info,
|
||||
@ -109,7 +115,7 @@ class PlayerStatsPlugins(Plugin):
|
||||
],
|
||||
"style": "xianzhou", # nosec
|
||||
"avatar": (await self.head_icon.get_head_icon(uid)).as_uri(),
|
||||
"background": user_info.phone_background_image_url,
|
||||
"background": (await self.phone_theme.get_phone_theme(uid)).as_uri(),
|
||||
}
|
||||
|
||||
return await self.template_service.render(
|
||||
@ -118,3 +124,13 @@ class PlayerStatsPlugins(Plugin):
|
||||
{"width": 650, "height": 440},
|
||||
full_page=True,
|
||||
)
|
||||
|
||||
async def set_name_card(self, player_id: int, image_url: str):
|
||||
if not image_url:
|
||||
return
|
||||
try:
|
||||
phone_theme_id = int(image_url.split("/")[-1].replace(".png", ""))
|
||||
except (IndexError, ValueError):
|
||||
return
|
||||
await self.phone_theme.set_to_cache(player_id, phone_theme_id)
|
||||
await self.player_info_service.set_name_card(player_id, phone_theme_id)
|
||||
|
@ -1,5 +1,5 @@
|
||||
from io import BytesIO
|
||||
from typing import Optional, TYPE_CHECKING, List, Union, Tuple
|
||||
from typing import Optional, TYPE_CHECKING, List, Union, Tuple, Dict
|
||||
|
||||
from simnet.models.starrail.wish import StarRailBannerType
|
||||
from telegram import Document, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User
|
||||
@ -29,6 +29,7 @@ from modules.gacha_log.migrate import GachaLogMigrate
|
||||
from modules.gacha_log.models import GachaLogInfo
|
||||
from plugins.tools.genshin import PlayerNotFoundError
|
||||
from plugins.tools.head_icon import HeadIconService
|
||||
from plugins.tools.phone_theme import PhoneThemeService
|
||||
from utils.log import logger
|
||||
|
||||
try:
|
||||
@ -57,12 +58,14 @@ class WishLogPlugin(Plugin.Conversation):
|
||||
assets: AssetsService,
|
||||
cookie_service: CookiesService,
|
||||
head_icon: HeadIconService,
|
||||
phone_theme: PhoneThemeService,
|
||||
):
|
||||
self.template_service = template_service
|
||||
self.players_service = players_service
|
||||
self.assets_service = assets
|
||||
self.cookie_service = cookie_service
|
||||
self.head_icon = head_icon
|
||||
self.phone_theme = phone_theme
|
||||
self.gacha_log = GachaLog()
|
||||
self.wish_photo = None
|
||||
|
||||
@ -292,7 +295,7 @@ class WishLogPlugin(Plugin.Conversation):
|
||||
data = await self.gacha_log.get_analysis(user_id, player_id, pool_type, self.assets_service)
|
||||
if isinstance(data, str):
|
||||
return data
|
||||
data["avatar"] = (await self.head_icon.get_head_icon(player_id)).as_uri()
|
||||
await self.add_theme_data(data, player_id)
|
||||
png_data = await self.template_service.render(
|
||||
"starrail/gacha_log/gacha_log.html",
|
||||
data,
|
||||
@ -473,7 +476,7 @@ class WishLogPlugin(Plugin.Conversation):
|
||||
await callback_query.answer(png_data, show_alert=True)
|
||||
self.add_delete_message_job(message, delay=1)
|
||||
else:
|
||||
png_data["avatar"] = (await self.head_icon.get_head_icon(uid)).as_uri()
|
||||
await self.add_theme_data(png_data, uid)
|
||||
await callback_query.answer(text="正在渲染图片中 请稍等 请不要重复点击按钮", show_alert=False)
|
||||
document = False
|
||||
if png_data["hasMore"] and not group:
|
||||
@ -494,6 +497,11 @@ class WishLogPlugin(Plugin.Conversation):
|
||||
else:
|
||||
await png.edit_media(message)
|
||||
|
||||
async def add_theme_data(self, data: Dict, player_id: int):
|
||||
data["avatar"] = (await self.head_icon.get_head_icon(player_id)).as_uri()
|
||||
data["background"] = (await self.phone_theme.get_phone_theme(player_id)).as_uri()
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
async def get_migrate_data(
|
||||
old_user_id: int, new_user_id: int, old_players: List["Player"]
|
||||
|
64
plugins/tools/phone_theme.py
Normal file
64
plugins/tools/phone_theme.py
Normal file
@ -0,0 +1,64 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from core.dependence.assets import AssetsService, AssetsCouldNotFound
|
||||
from core.services.players.services import PlayerInfoService
|
||||
from gram_core.dependence.redisdb import RedisDB
|
||||
from gram_core.plugin import Plugin
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class PhoneThemeService(Plugin):
|
||||
def __init__(
|
||||
self,
|
||||
player_info_service: PlayerInfoService,
|
||||
asset_service: AssetsService,
|
||||
redis: RedisDB,
|
||||
) -> None:
|
||||
self.player_info_service = player_info_service
|
||||
self.assets = asset_service
|
||||
self.redis = redis.client
|
||||
self.expire = 60 * 60
|
||||
self.qname = "plugins:phone_theme"
|
||||
|
||||
def get_default_phone_theme(self) -> Optional[Path]:
|
||||
try:
|
||||
return self.assets.phone_theme.icon(221000)
|
||||
except AssetsCouldNotFound as e:
|
||||
logger.warning(str(e))
|
||||
return None
|
||||
|
||||
async def get_from_cache(self, player_id: int) -> Optional[int]:
|
||||
key = f"{self.qname}:{player_id}"
|
||||
data = await self.redis.get(key)
|
||||
if data is None:
|
||||
return None
|
||||
return int(data)
|
||||
|
||||
async def set_to_cache(self, player_id: int, phone_theme: int) -> None:
|
||||
key = f"{self.qname}:{player_id}"
|
||||
await self.redis.set(key, phone_theme, ex=self.expire)
|
||||
|
||||
async def get_from_sql(self, player_id: int) -> Optional[int]:
|
||||
player_info = await self.player_info_service.get_by_player_id(player_id)
|
||||
if player_info is None:
|
||||
return None
|
||||
return player_info.name_card
|
||||
|
||||
async def get_phone_theme_id(self, player_id: int) -> Optional[int]:
|
||||
phone_theme = await self.get_from_cache(player_id)
|
||||
if phone_theme is not None:
|
||||
return phone_theme
|
||||
phone_theme = await self.get_from_sql(player_id)
|
||||
if phone_theme is not None:
|
||||
await self.set_to_cache(player_id, phone_theme)
|
||||
return phone_theme
|
||||
return None
|
||||
|
||||
async def get_phone_theme(self, player_id: int):
|
||||
try:
|
||||
phone_theme = await self.get_phone_theme_id(player_id)
|
||||
return self.assets.phone_theme.icon(phone_theme)
|
||||
except AssetsCouldNotFound as e:
|
||||
logger.warning(str(e))
|
||||
return self.get_default_phone_theme()
|
@ -9,7 +9,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="container flex flex-col justify-center p-6">
|
||||
<div class="header rounded-xl p-2 mb-6">
|
||||
<div class="header rounded-xl p-2 mb-6" style='background-image: url("{{ background }}") '>
|
||||
<div class="frame rounded-lg border-solid border-2 bg-contain p-4 flex items-center">
|
||||
<img class="w-28 h-28 rounded-full mr-4" src="{{ avatar }}" alt="Avatar">
|
||||
<div>
|
||||
|
@ -20,11 +20,13 @@
|
||||
.header {
|
||||
background: #e0dad3 url(../gacha_log/img/starrail.png) no-repeat right;
|
||||
box-shadow: 0 0 8px #72a2ae79;
|
||||
background-size: cover;
|
||||
background-position: top;
|
||||
}
|
||||
|
||||
.frame {
|
||||
border-color: #cdbea8;
|
||||
color: #8a4d30;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.text-shadow {
|
||||
|
@ -20,12 +20,15 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #e0dad3 url(../gacha_log/img/starrail.png) no-repeat right;
|
||||
/*background: #e0dad3 url(../gacha_log/img/starrail.png) no-repeat right;*/
|
||||
box-shadow: 0 0 8px #72a2ae79;
|
||||
background-size: cover;
|
||||
background-position: top;
|
||||
}
|
||||
|
||||
.frame {
|
||||
border-color: #cdbea8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -15,7 +15,7 @@
|
||||
<body id="container" class="body_box">
|
||||
<div class="container">
|
||||
<div class="info_box">
|
||||
<div class="header p-2 rounded-xl bg-contain mb-6">
|
||||
<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">
|
||||
<img class="w-16 h-16 rounded-full mr-4" src="{{ avatar }}" alt="Avatar">
|
||||
<div>
|
||||
|
@ -6,12 +6,15 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #e0dad3 url(./img/starrail.png) no-repeat right;
|
||||
/*background: #e0dad3 url(./img/starrail.png) no-repeat right;*/
|
||||
box-shadow: 0 0 8px #72a2ae79;
|
||||
background-size: cover;
|
||||
background-position: top;
|
||||
}
|
||||
|
||||
.frame {
|
||||
border-color: #cdbea8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
@ -225,7 +228,7 @@ body {
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #fff;
|
||||
color: #fff!important;
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
padding: 2px 7px;
|
||||
|
@ -11,7 +11,7 @@
|
||||
<body id="container" class="body_box">
|
||||
<div class="container">
|
||||
<div class="info_box">
|
||||
<div class="header p-2 rounded-xl bg-contain mb-6">
|
||||
<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">
|
||||
<img class="w-16 h-16 rounded-full mr-4" src="{{ avatar }}" alt="Avatar">
|
||||
<div>
|
||||
|
Loading…
Reference in New Issue
Block a user