mirror of
https://github.com/PaiGramTeam/PaiGram.git
synced 2024-11-22 07:07:46 +00:00
✨ 增加 html to image 的缓存
* 增加 html to image 缓存 * 对 template_service.render 进行封装,管理缓存逻辑 * cache key 为 html 的 sha256 * cache value 为 reply_photo 后 telegram 返回的 file_id * 存入 redis,并设置合理的 ttl Co-authored-by: 洛水居室 <luoshuijs@outlook.com> Co-authored-by: xtaodada <xtao@xtaolink.cn>
This commit is contained in:
parent
f55077e713
commit
f122e21092
@ -2,10 +2,12 @@ from core.base.aiobrowser import AioBrowser
|
||||
from core.service import init_service
|
||||
from core.base.redisdb import RedisDB
|
||||
from core.template.services import TemplateService
|
||||
from core.template.cache import TemplatePreviewCache
|
||||
from core.template.cache import TemplatePreviewCache, HtmlToFileIdCache
|
||||
|
||||
|
||||
@init_service
|
||||
def create_template_service(browser: AioBrowser, redis: RedisDB):
|
||||
_cache = TemplatePreviewCache(redis)
|
||||
_service = TemplateService(browser, _cache)
|
||||
_preview_cache = TemplatePreviewCache(redis)
|
||||
_html_to_file_id_cache = HtmlToFileIdCache(redis)
|
||||
_service = TemplateService(browser, _html_to_file_id_cache, _preview_cache)
|
||||
return _service
|
||||
|
@ -1,12 +1,13 @@
|
||||
from typing import Any
|
||||
import pickle # nosec B403
|
||||
import gzip
|
||||
import pickle # nosec B403
|
||||
from hashlib import sha256
|
||||
from typing import Any, Optional
|
||||
|
||||
from core.base.redisdb import RedisDB
|
||||
|
||||
|
||||
class TemplatePreviewCache:
|
||||
'''暂存渲染模板的数据用于预览'''
|
||||
"""暂存渲染模板的数据用于预览"""
|
||||
|
||||
def __init__(self, redis: RedisDB):
|
||||
self.client = redis.client
|
||||
@ -16,7 +17,7 @@ class TemplatePreviewCache:
|
||||
data = await self.client.get(self.cache_key(key))
|
||||
if data:
|
||||
# skipcq: BAN-B301
|
||||
return pickle.loads(gzip.decompress(data)) # nosec B301
|
||||
return pickle.loads(gzip.decompress(data)) # nosec B301
|
||||
|
||||
async def set_data(self, key: str, data: Any, ttl: int = 8 * 60 * 60):
|
||||
ck = self.cache_key(key)
|
||||
@ -26,3 +27,26 @@ class TemplatePreviewCache:
|
||||
|
||||
def cache_key(self, key: str) -> str:
|
||||
return f"{self.qname}:{key}"
|
||||
|
||||
|
||||
class HtmlToFileIdCache:
|
||||
"""html to file_id 的缓存"""
|
||||
|
||||
def __init__(self, redis: RedisDB):
|
||||
self.client = redis.client
|
||||
self.qname = "bot:template:html-to-file-id"
|
||||
|
||||
async def get_data(self, html: str, file_type: str) -> Optional[str]:
|
||||
data = await self.client.get(self.cache_key(html, file_type))
|
||||
if data:
|
||||
return data.decode()
|
||||
|
||||
async def set_data(self, html: str, file_type: str, file_id: str, ttl: int = 24 * 60 * 60):
|
||||
ck = self.cache_key(html, file_type)
|
||||
await self.client.set(ck, file_id)
|
||||
if ttl != -1:
|
||||
await self.client.expire(ck, ttl)
|
||||
|
||||
def cache_key(self, html: str, file_type: str) -> str:
|
||||
key = sha256(html.encode()).hexdigest()
|
||||
return f"{self.qname}:{file_type}:{key}"
|
||||
|
14
core/template/error.py
Normal file
14
core/template/error.py
Normal file
@ -0,0 +1,14 @@
|
||||
class TemplateException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class QuerySelectorNotFound(TemplateException):
|
||||
pass
|
||||
|
||||
|
||||
class ErrorFileType(TemplateException):
|
||||
pass
|
||||
|
||||
|
||||
class FileIdNotFound(TemplateException):
|
||||
pass
|
126
core/template/models.py
Normal file
126
core/template/models.py
Normal file
@ -0,0 +1,126 @@
|
||||
from enum import Enum
|
||||
from typing import Optional, Union, List
|
||||
|
||||
from telegram import Message, InputMediaPhoto, InputMediaDocument
|
||||
|
||||
from core.template.cache import HtmlToFileIdCache
|
||||
from core.template.error import ErrorFileType, FileIdNotFound
|
||||
|
||||
|
||||
class FileType(Enum):
|
||||
PHOTO = 1
|
||||
DOCUMENT = 2
|
||||
|
||||
@staticmethod
|
||||
def media_type(file_type: "FileType"):
|
||||
"""对应的 Telegram media 类型"""
|
||||
if file_type == FileType.PHOTO:
|
||||
return InputMediaPhoto
|
||||
elif file_type == FileType.DOCUMENT:
|
||||
return InputMediaDocument
|
||||
else:
|
||||
raise ErrorFileType
|
||||
|
||||
|
||||
class RenderResult:
|
||||
"""渲染结果"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
html: str,
|
||||
photo: Union[bytes, str],
|
||||
file_type: FileType,
|
||||
cache: HtmlToFileIdCache,
|
||||
ttl: int = 24 * 60 * 60,
|
||||
caption: Optional[str] = None,
|
||||
parse_mode: Optional[str] = None,
|
||||
filename: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
`html`: str 渲染生成的 html
|
||||
`photo`: Union[bytes, str] 渲染生成的图片。bytes 表示是图片,str 则为 file_id
|
||||
"""
|
||||
self.caption = caption
|
||||
self.parse_mode = parse_mode
|
||||
self.filename = filename
|
||||
self.html = html
|
||||
self.photo = photo
|
||||
self.file_type = file_type
|
||||
self._cache = cache
|
||||
self.ttl = ttl
|
||||
|
||||
async def reply_photo(self, message: Message, *args, **kwargs):
|
||||
"""是 `message.reply_photo` 的封装,上传成功后,缓存 telegram 返回的 file_id,方便重复使用"""
|
||||
if self.file_type != FileType.PHOTO:
|
||||
raise ErrorFileType
|
||||
|
||||
reply = await message.reply_photo(photo=self.photo, *args, **kwargs)
|
||||
|
||||
await self.cache_file_id(reply)
|
||||
|
||||
return reply
|
||||
|
||||
async def reply_document(self, message: Message, *args, **kwargs):
|
||||
"""是 `message.reply_document` 的封装,上传成功后,缓存 telegram 返回的 file_id,方便重复使用"""
|
||||
if self.file_type != FileType.DOCUMENT:
|
||||
raise ErrorFileType
|
||||
|
||||
reply = await message.reply_document(document=self.photo, *args, **kwargs)
|
||||
|
||||
await self.cache_file_id(reply)
|
||||
|
||||
return reply
|
||||
|
||||
async def edit_media(self, message: Message, *args, **kwargs):
|
||||
"""是 `message.edit_media` 的封装,上传成功后,缓存 telegram 返回的 file_id,方便重复使用"""
|
||||
if self.file_type != FileType.PHOTO:
|
||||
raise ErrorFileType
|
||||
|
||||
media = InputMediaPhoto(
|
||||
media=self.photo, caption=self.caption, parse_mode=self.parse_mode, filename=self.filename
|
||||
)
|
||||
|
||||
edit_media = await message.edit_media(media, *args, **kwargs)
|
||||
|
||||
await self.cache_file_id(edit_media)
|
||||
|
||||
return edit_media
|
||||
|
||||
async def cache_file_id(self, reply: Message):
|
||||
"""缓存 telegram 返回的 file_id"""
|
||||
if self.is_file_id():
|
||||
return
|
||||
|
||||
if self.file_type == FileType.PHOTO and reply.photo:
|
||||
file_id = reply.photo[0].file_id
|
||||
elif self.file_type == FileType.DOCUMENT and reply.document:
|
||||
file_id = reply.document.file_id
|
||||
else:
|
||||
raise FileIdNotFound
|
||||
await self._cache.set_data(self.html, self.file_type.name, file_id, self.ttl)
|
||||
|
||||
def is_file_id(self) -> bool:
|
||||
return isinstance(self.photo, str)
|
||||
|
||||
|
||||
class RenderGroupResult:
|
||||
def __init__(self, results: List[RenderResult]):
|
||||
self.results = results
|
||||
|
||||
async def reply_media_group(self, message: Message, *args, **kwargs):
|
||||
"""是 `message.reply_media_group` 的封装,上传成功后,缓存 telegram 返回的 file_id,方便重复使用"""
|
||||
|
||||
reply = await message.reply_media_group(
|
||||
media=[
|
||||
FileType.media_type(result.file_type)(
|
||||
media=result.photo, caption=result.caption, parse_mode=result.parse_mode, filename=result.filename
|
||||
)
|
||||
for result in self.results
|
||||
],
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
for index, value in enumerate(reply):
|
||||
result = self.results[index]
|
||||
await result.cache_file_id(value)
|
@ -1,29 +1,32 @@
|
||||
import time
|
||||
from typing import Optional
|
||||
from urllib.parse import urlencode, urljoin, urlsplit
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, Template
|
||||
from playwright.async_api import ViewportSize
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import HTTPException
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from jinja2 import Environment, FileSystemLoader, Template
|
||||
from playwright.async_api import ViewportSize
|
||||
|
||||
from core.base.aiobrowser import AioBrowser
|
||||
from core.bot import bot
|
||||
from core.base.webserver import webapp
|
||||
from core.bot import bot
|
||||
from core.template.cache import HtmlToFileIdCache, TemplatePreviewCache
|
||||
from core.template.error import QuerySelectorNotFound
|
||||
from core.template.models import FileType, RenderResult
|
||||
from utils.const import PROJECT_ROOT
|
||||
from utils.log import logger
|
||||
from core.template.cache import TemplatePreviewCache
|
||||
|
||||
|
||||
class _QuerySelectorNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TemplateService:
|
||||
def __init__(self, browser: AioBrowser, preview_cache: TemplatePreviewCache, template_dir: str = "resources"):
|
||||
def __init__(
|
||||
self,
|
||||
browser: AioBrowser,
|
||||
html_to_file_id_cache: HtmlToFileIdCache,
|
||||
preview_cache: TemplatePreviewCache,
|
||||
template_dir: str = "resources",
|
||||
):
|
||||
self._browser = browser
|
||||
self.template_dir = PROJECT_ROOT / template_dir
|
||||
|
||||
@ -36,10 +39,12 @@ class TemplateService:
|
||||
|
||||
self.previewer = TemplatePreviewer(self, preview_cache)
|
||||
|
||||
self.html_to_file_id_cache = html_to_file_id_cache
|
||||
|
||||
def get_template(self, template_name: str) -> Template:
|
||||
return self._jinja2_env.get_template(template_name)
|
||||
|
||||
async def render_async(self, template_name: str, template_data: dict):
|
||||
async def render_async(self, template_name: str, template_data: dict) -> str:
|
||||
"""模板渲染
|
||||
:param template_name: 模板文件名
|
||||
:param template_data: 模板数据
|
||||
@ -54,19 +59,28 @@ class TemplateService:
|
||||
self,
|
||||
template_name: str,
|
||||
template_data: dict,
|
||||
viewport: ViewportSize = None,
|
||||
viewport: Optional[ViewportSize] = None,
|
||||
full_page: bool = True,
|
||||
evaluate: Optional[str] = None,
|
||||
query_selector: str = None
|
||||
) -> bytes:
|
||||
query_selector: Optional[str] = None,
|
||||
file_type: FileType = FileType.PHOTO,
|
||||
ttl: int = 24 * 60 * 60,
|
||||
caption: Optional[str] = None,
|
||||
parse_mode: Optional[str] = None,
|
||||
filename: Optional[str] = None,
|
||||
) -> RenderResult:
|
||||
"""模板渲染成图片
|
||||
:param template_path: 模板目录
|
||||
:param template_name: 模板文件名
|
||||
:param template_data: 模板数据
|
||||
:param viewport: 截图大小
|
||||
:param full_page: 是否长截图
|
||||
:param evaluate: 页面加载后运行的 js
|
||||
:param query_selector: 截图选择器
|
||||
:param file_type: 缓存的文件类型
|
||||
:param ttl: 缓存时间
|
||||
:param caption: 图片描述
|
||||
:param parse_mode: 图片描述解析模式
|
||||
:param filename: 文件名字
|
||||
:return:
|
||||
"""
|
||||
start_time = time.time()
|
||||
@ -79,6 +93,20 @@ class TemplateService:
|
||||
html = await template.render_async(**template_data)
|
||||
logger.debug(f"{template_name} 模板渲染使用了 {str(time.time() - start_time)}")
|
||||
|
||||
file_id = await self.html_to_file_id_cache.get_data(html, file_type.name)
|
||||
if file_id and not bot.config.debug:
|
||||
logger.debug(f"{template_name} 命中缓存,返回 file_id {file_id}")
|
||||
return RenderResult(
|
||||
html=html,
|
||||
photo=file_id,
|
||||
file_type=file_type,
|
||||
cache=self.html_to_file_id_cache,
|
||||
ttl=ttl,
|
||||
caption=caption,
|
||||
parse_mode=parse_mode,
|
||||
filename=filename,
|
||||
)
|
||||
|
||||
browser = await self._browser.get_browser()
|
||||
start_time = time.time()
|
||||
page = await browser.new_page(viewport=viewport)
|
||||
@ -92,16 +120,25 @@ class TemplateService:
|
||||
try:
|
||||
card = await page.query_selector(query_selector)
|
||||
if not card:
|
||||
raise _QuerySelectorNotFound
|
||||
raise QuerySelectorNotFound
|
||||
clip = await card.bounding_box()
|
||||
if not clip:
|
||||
raise _QuerySelectorNotFound
|
||||
except _QuerySelectorNotFound:
|
||||
raise QuerySelectorNotFound
|
||||
except QuerySelectorNotFound:
|
||||
logger.warning(f"未找到 {query_selector} 元素")
|
||||
png_data = await page.screenshot(clip=clip, full_page=full_page)
|
||||
await page.close()
|
||||
logger.debug(f"{template_name} 图片渲染使用了 {str(time.time() - start_time)}")
|
||||
return png_data
|
||||
return RenderResult(
|
||||
html=html,
|
||||
photo=png_data,
|
||||
file_type=file_type,
|
||||
cache=self.html_to_file_id_cache,
|
||||
ttl=ttl,
|
||||
caption=caption,
|
||||
parse_mode=parse_mode,
|
||||
filename=filename,
|
||||
)
|
||||
|
||||
|
||||
class TemplatePreviewer:
|
||||
@ -128,7 +165,7 @@ class TemplatePreviewer:
|
||||
"""注册预览用到的路由"""
|
||||
|
||||
@webapp.get("/preview/{path:path}")
|
||||
async def preview_template(path: str, key: Optional[str] = None): # pylint: disable=W0612
|
||||
async def preview_template(path: str, key: Optional[str] = None): # pylint: disable=W0612
|
||||
# 如果是 /preview/ 开头的静态文件,直接返回内容。比如使用相对链接 ../ 引入的静态资源
|
||||
if not path.endswith(".html"):
|
||||
full_path = self.template_service.template_dir / path
|
||||
|
@ -3,13 +3,13 @@ import asyncio
|
||||
import re
|
||||
from datetime import datetime
|
||||
from functools import lru_cache, partial
|
||||
from typing import List, Match, Optional, Tuple
|
||||
from typing import Any, Coroutine, List, Match, Optional, Tuple, Union
|
||||
|
||||
import ujson as json
|
||||
from arkowrapper import ArkoWrapper
|
||||
from genshin import Client
|
||||
from pytz import timezone
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto, Update, Message
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, Message
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.ext import CallbackContext, filters
|
||||
|
||||
@ -19,6 +19,7 @@ from core.cookies.error import CookiesNotFoundError, TooManyRequestPublicCookies
|
||||
from core.cookies.services import CookiesService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from core.template.models import RenderGroupResult, RenderResult
|
||||
from core.user import UserService
|
||||
from core.user.error import UserNotFoundError
|
||||
from metadata.genshin import game_id_to_role_id
|
||||
@ -185,8 +186,10 @@ class Abyss(Plugin, BasePlugin):
|
||||
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
|
||||
for group in ArkoWrapper(images).map(InputMediaPhoto).group(10): # 每 10 张图片分一个组
|
||||
await message.reply_media_group(list(group), allow_sending_without_reply=True, write_timeout=60)
|
||||
for group in ArkoWrapper(images).group(10): # 每 10 张图片分一个组
|
||||
await RenderGroupResult(results=group).reply_media_group(
|
||||
message, allow_sending_without_reply=True, write_timeout=60
|
||||
)
|
||||
|
||||
if reply_text is not None:
|
||||
await reply_text.delete()
|
||||
@ -195,7 +198,17 @@ class Abyss(Plugin, BasePlugin):
|
||||
|
||||
async def get_rendered_pic(
|
||||
self, client: Client, uid: int, floor: int, total: bool, previous: bool
|
||||
) -> Optional[List[bytes]]:
|
||||
) -> Union[
|
||||
tuple[
|
||||
Union[BaseException, Any],
|
||||
Union[BaseException, Any],
|
||||
Union[BaseException, Any],
|
||||
Union[BaseException, Any],
|
||||
Union[BaseException, Any],
|
||||
],
|
||||
list[RenderResult],
|
||||
None,
|
||||
]:
|
||||
"""
|
||||
获取渲染后的图片
|
||||
|
||||
@ -258,50 +271,45 @@ class Abyss(Plugin, BasePlugin):
|
||||
data = json.loads(result)
|
||||
render_data["data"] = data
|
||||
|
||||
render_result = []
|
||||
render_inputs: List[Tuple[int, Coroutine[Any, Any, RenderResult]]] = []
|
||||
|
||||
async def overview_task():
|
||||
render_result.append(
|
||||
[
|
||||
-1,
|
||||
await self.template_service.render(
|
||||
"genshin/abyss/overview.html", render_data, viewport={"width": 750, "height": 580}
|
||||
),
|
||||
]
|
||||
def overview_task():
|
||||
return -1, self.template_service.render(
|
||||
"genshin/abyss/overview.html", render_data, viewport={"width": 750, "height": 580}
|
||||
)
|
||||
|
||||
async def floor_task(floor_index: int):
|
||||
def floor_task(floor_index: int):
|
||||
floor_d = data["floors"][floor_index]
|
||||
render_result.append(
|
||||
[
|
||||
floor_d["floor"],
|
||||
await self.template_service.render(
|
||||
"genshin/abyss/floor.html",
|
||||
{
|
||||
**render_data,
|
||||
"floor": floor_d,
|
||||
"total_stars": f"{floor_d['stars']}/{floor_d['max_stars']}",
|
||||
},
|
||||
viewport={"width": 690, "height": 500},
|
||||
full_page=True,
|
||||
),
|
||||
]
|
||||
return (
|
||||
floor_d["floor"],
|
||||
self.template_service.render(
|
||||
"genshin/abyss/floor.html",
|
||||
{
|
||||
**render_data,
|
||||
"floor": floor_d,
|
||||
"total_stars": f"{floor_d['stars']}/{floor_d['max_stars']}",
|
||||
},
|
||||
viewport={"width": 690, "height": 500},
|
||||
full_page=True,
|
||||
ttl=15 * 24 * 60 * 60,
|
||||
),
|
||||
)
|
||||
|
||||
task_list = [asyncio.create_task(overview_task())]
|
||||
render_inputs.append(overview_task())
|
||||
|
||||
for i, f in enumerate(data["floors"]):
|
||||
if f["floor"] >= 9:
|
||||
task_list.append(asyncio.create_task(floor_task(i)))
|
||||
await asyncio.gather(*task_list)
|
||||
render_inputs.append(floor_task(i))
|
||||
|
||||
render_group_inputs = list(map(lambda x: x[1], sorted(render_inputs, key=lambda x: x[0])))
|
||||
|
||||
return await asyncio.gather(*render_group_inputs)
|
||||
|
||||
return list(map(lambda x: x[1], sorted(render_result, key=lambda x: x[0])))
|
||||
elif floor < 1:
|
||||
render_data["data"] = json.loads(result)
|
||||
return [
|
||||
await self.template_service.render(
|
||||
"genshin/abyss/overview.html",
|
||||
render_data,
|
||||
viewport={"width": 750, "height": 580},
|
||||
"genshin/abyss/overview.html", render_data, viewport={"width": 750, "height": 580}
|
||||
)
|
||||
]
|
||||
else:
|
||||
@ -330,6 +338,6 @@ class Abyss(Plugin, BasePlugin):
|
||||
render_data["total_stars"] = f"{floor_data[0]['stars']}/{floor_data[0]['max_stars']}"
|
||||
return [
|
||||
await self.template_service.render(
|
||||
"genshin/abyss/floor.html", render_data, viewport={"width": 690, "height": 500}, full_page=True
|
||||
"genshin/abyss/floor.html", render_data, viewport={"width": 690, "height": 500}
|
||||
)
|
||||
]
|
||||
|
@ -82,11 +82,11 @@ class AbyssTeam(Plugin, BasePlugin):
|
||||
abyss_teams_data["teams"].append(team)
|
||||
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
png_data = await self.template_service.render(
|
||||
render_result = await self.template_service.render(
|
||||
"genshin/abyss_team/abyss_team.html",
|
||||
abyss_teams_data,
|
||||
{"width": 785, "height": 800},
|
||||
full_page=True,
|
||||
query_selector=".bg-contain",
|
||||
)
|
||||
await message.reply_photo(png_data, filename=f"abyss_team_{user.id}.png", allow_sending_without_reply=True)
|
||||
await render_result.reply_photo(message, filename=f"abyss_team_{user.id}.png", allow_sending_without_reply=True)
|
||||
|
@ -1,12 +1,12 @@
|
||||
"""练度统计"""
|
||||
import asyncio
|
||||
from typing import Iterable, List, Optional, Sequence, Tuple
|
||||
from typing import Iterable, List, Optional, Sequence
|
||||
|
||||
from arkowrapper import ArkoWrapper
|
||||
from enkanetwork import Assets as EnkaAssets, EnkaNetworkAPI
|
||||
from genshin import Client, GenshinException
|
||||
from genshin.models import CalculatorCharacterDetails, CalculatorTalent, Character
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, InputFile, Message, Update, User
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.ext import CallbackContext, filters
|
||||
|
||||
@ -17,6 +17,7 @@ from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.services import CookiesService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from core.template.models import FileType
|
||||
from core.user.error import UserNotFoundError
|
||||
from metadata.genshin import AVATAR_DATA, NAMECARD_DATA
|
||||
from modules.wiki.base import Model
|
||||
@ -113,18 +114,14 @@ class AvatarListPlugin(Plugin, BasePlugin):
|
||||
async def get_avatars_data(
|
||||
self, characters: Sequence[Character], client: Client, max_length: int = None
|
||||
) -> List["AvatarData"]:
|
||||
task_result: List[Tuple[int, AvatarData]] = []
|
||||
|
||||
async def _task(c, n):
|
||||
if (result := await self.get_avatar_data(c, client)) is not None:
|
||||
task_result.append((n, result))
|
||||
return n, await self.get_avatar_data(c, client)
|
||||
|
||||
task_list = []
|
||||
for num, character in enumerate(characters[:max_length]):
|
||||
task_list.append(asyncio.create_task(_task(character, num)))
|
||||
task_results = await asyncio.gather(
|
||||
*[_task(character, num) for num, character in enumerate(characters[:max_length])]
|
||||
)
|
||||
|
||||
await asyncio.gather(*task_list)
|
||||
return list(map(lambda x: x[1], sorted(task_result, key=lambda x: x[0])))
|
||||
return list(filter(lambda x: x, map(lambda x: x[1], sorted(task_results, key=lambda x: x[0]))))
|
||||
|
||||
async def get_final_data(self, client: Client, characters: Sequence[Character], update: Update):
|
||||
try:
|
||||
@ -218,7 +215,9 @@ class AvatarListPlugin(Plugin, BasePlugin):
|
||||
"has_more": len(characters) != len(avatar_datas), # 是否显示了全部角色
|
||||
}
|
||||
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT if all_avatars else ChatAction.UPLOAD_PHOTO)
|
||||
as_document = True if all_avatars and len(characters) > 20 else False
|
||||
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT if as_document else ChatAction.UPLOAD_PHOTO)
|
||||
|
||||
image = await self.template_service.render(
|
||||
"genshin/avatar_list/main.html",
|
||||
@ -226,12 +225,14 @@ class AvatarListPlugin(Plugin, BasePlugin):
|
||||
viewport={"width": 1040, "height": 500},
|
||||
full_page=True,
|
||||
query_selector=".container",
|
||||
file_type=FileType.DOCUMENT if as_document else FileType.PHOTO,
|
||||
ttl=30 * 24 * 60 * 60,
|
||||
)
|
||||
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
|
||||
if all_avatars and len(characters) > 20:
|
||||
await message.reply_document(InputFile(image, filename="练度统计.png"))
|
||||
if as_document:
|
||||
await image.reply_document(message, filename="练度统计.png")
|
||||
else:
|
||||
await message.reply_photo(image)
|
||||
await image.reply_photo(message)
|
||||
|
||||
logger.info(
|
||||
f"用户 {user.full_name}[{user.id}] [bold]练度统计[/bold]发送{'文件' if all_avatars else '图片'}成功",
|
||||
|
@ -18,7 +18,7 @@ from bs4 import BeautifulSoup
|
||||
from genshin import Client
|
||||
from httpx import AsyncClient, HTTPError
|
||||
from pydantic import BaseModel
|
||||
from telegram import InputMediaDocument, InputMediaPhoto, Message, Update, User
|
||||
from telegram import Message, Update, User
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.error import RetryAfter, TimedOut
|
||||
from telegram.ext import CallbackContext
|
||||
@ -28,6 +28,7 @@ from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from core.template.models import FileType, RenderGroupResult
|
||||
from core.user.error import UserNotFoundError
|
||||
from metadata.genshin import AVATAR_DATA, HONEY_DATA
|
||||
from utils.bot import get_all_args
|
||||
@ -186,7 +187,7 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
title = "今日"
|
||||
weekday = now.weekday() - (1 if now.hour < 4 else 0)
|
||||
weekday = 6 if weekday < 0 else weekday
|
||||
time = now.strftime("%m-%d %H:%M") + " 星期" + WEEK_MAP[weekday]
|
||||
time = f"星期{WEEK_MAP[weekday]}"
|
||||
full = bool(args and args[-1] == "full") # 判定最后一个参数是不是 full
|
||||
|
||||
logger.info(f'用户 {user.full_name}[{user.id}] 每日素材命令请求 || 参数 weekday="{WEEK_MAP[weekday]}" full={full}')
|
||||
@ -275,35 +276,35 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
setattr(render_data, {"avatar": "character"}.get(type_, type_), areas)
|
||||
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
render_tasks = [
|
||||
asyncio.create_task(
|
||||
self.template_service.render( # 渲染角色素材页
|
||||
"genshin/daily_material/character.html", {"data": render_data}, {"width": 1164, "height": 500}
|
||||
)
|
||||
),
|
||||
asyncio.create_task(
|
||||
self.template_service.render( # 渲染武器素材页
|
||||
"genshin/daily_material/weapon.html", {"data": render_data}, {"width": 1164, "height": 500}
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*render_tasks)
|
||||
# 是否发送原图
|
||||
file_type = FileType.DOCUMENT if full else FileType.PHOTO
|
||||
|
||||
character_img_data = results[0]
|
||||
weapon_img_data = results[1]
|
||||
character_img_data, weapon_img_data = await asyncio.gather(
|
||||
self.template_service.render( # 渲染角色素材页
|
||||
"genshin/daily_material/character.html",
|
||||
{"data": render_data},
|
||||
{"width": 1164, "height": 500},
|
||||
file_type=file_type,
|
||||
ttl=7 * 24 * 60 * 60,
|
||||
),
|
||||
self.template_service.render( # 渲染武器素材页
|
||||
"genshin/daily_material/weapon.html",
|
||||
{"data": render_data},
|
||||
{"width": 1164, "height": 500},
|
||||
file_type=file_type,
|
||||
ttl=7 * 24 * 60 * 60,
|
||||
),
|
||||
)
|
||||
|
||||
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
if full: # 是否发送原图
|
||||
await message.reply_media_group(
|
||||
[
|
||||
InputMediaDocument(character_img_data, filename="可培养角色.png"),
|
||||
InputMediaDocument(weapon_img_data, filename="可培养武器.png"),
|
||||
]
|
||||
)
|
||||
else:
|
||||
await message.reply_media_group([InputMediaPhoto(character_img_data), InputMediaPhoto(weapon_img_data)])
|
||||
|
||||
character_img_data.filename = f"{title}可培养角色.png"
|
||||
weapon_img_data.filename = f"{title}可培养武器.png"
|
||||
|
||||
await RenderGroupResult([character_img_data, weapon_img_data]).reply_media_group(message)
|
||||
|
||||
logger.debug("角色、武器培养素材图发送成功")
|
||||
|
||||
@handler.command("refresh_daily_material", block=False)
|
||||
@ -428,7 +429,7 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
for ID, DATA in ITEMS.items():
|
||||
if (ITEM := [ID, DATA[1], TYPE]) not in new_items:
|
||||
new_items.append(ITEM)
|
||||
task_list.append(asyncio.create_task(task(*ITEM)))
|
||||
task_list.append(task(*ITEM))
|
||||
await asyncio.gather(*task_list) # 等待所有任务执行完成
|
||||
|
||||
logger.info("图标素材下载完成")
|
||||
|
@ -11,7 +11,7 @@ from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.services import CookiesService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template.services import TemplateService
|
||||
from core.template.services import RenderResult, TemplateService
|
||||
from core.user.error import UserNotFoundError
|
||||
from core.user.services import UserService
|
||||
from utils.decorators.error import error_callable
|
||||
@ -34,7 +34,7 @@ class DailyNote(Plugin, BasePlugin):
|
||||
self.user_service = user_service
|
||||
self.current_dir = os.getcwd()
|
||||
|
||||
async def _get_daily_note(self, client) -> bytes:
|
||||
async def _get_daily_note(self, client) -> RenderResult:
|
||||
daily_info = await client.get_genshin_notes(client.uid)
|
||||
day = datetime.datetime.now().strftime("%m-%d %H:%M") + " 星期" + "一二三四五六日"[datetime.datetime.now().weekday()]
|
||||
resin_recovery_time = (
|
||||
@ -85,14 +85,14 @@ class DailyNote(Plugin, BasePlugin):
|
||||
"transformer_ready": transformer_ready,
|
||||
"transformer_recovery_time": transformer_recovery_time,
|
||||
}
|
||||
png_data = await self.template_service.render(
|
||||
render_result = await self.template_service.render(
|
||||
"genshin/daily_note/daily_note.html", daily_data, {"width": 600, "height": 548}, full_page=False
|
||||
)
|
||||
return png_data
|
||||
return render_result
|
||||
|
||||
@handler(CommandHandler, command="dailynote", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^当前状态(.*)"), block=False)
|
||||
@restricts(return_data=ConversationHandler.END)
|
||||
@restricts(30)
|
||||
@error_callable
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> Optional[int]:
|
||||
user = update.effective_user
|
||||
@ -100,7 +100,7 @@ class DailyNote(Plugin, BasePlugin):
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 查询游戏状态命令请求")
|
||||
try:
|
||||
client = await get_genshin_client(user.id)
|
||||
png_data = await self._get_daily_note(client)
|
||||
render_result = await self._get_daily_note(client)
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
if filters.ChatType.GROUPS.filter(message):
|
||||
buttons = [[InlineKeyboardButton("点我私聊", url=f"https://t.me/{context.bot.username}?start=set_cookie")]]
|
||||
@ -120,4 +120,4 @@ class DailyNote(Plugin, BasePlugin):
|
||||
self._add_delete_message_job(context, message.chat_id, message.message_id, 300)
|
||||
return ConversationHandler.END
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await message.reply_photo(png_data, filename=f"{client.uid}.png", allow_sending_without_reply=True)
|
||||
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
|
||||
|
@ -14,6 +14,7 @@ from core.cookies import CookiesService
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.plugin import Plugin, handler, conversation
|
||||
from core.template import TemplateService
|
||||
from core.template.models import FileType
|
||||
from core.user import UserService
|
||||
from core.user.error import UserNotFoundError
|
||||
from metadata.scripts.paimon_moe import update_paimon_moe_zh, GACHA_LOG_PAIMON_MOE_PATH
|
||||
@ -29,7 +30,6 @@ from modules.gacha_log.error import (
|
||||
from modules.gacha_log.helpers import from_url_get_authkey
|
||||
from modules.gacha_log.log import GachaLog
|
||||
from utils.bot import get_all_args
|
||||
from utils.const import PROJECT_ROOT
|
||||
from utils.decorators.admins import bot_admins_rights_check
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
@ -341,7 +341,7 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
png_data = await self.template_service.render(
|
||||
"genshin/gacha_log/gacha_log.html", data, full_page=True, query_selector=".body_box"
|
||||
)
|
||||
await message.reply_photo(png_data)
|
||||
await png_data.reply_photo(message)
|
||||
except GachaLogNotFound:
|
||||
await message.reply_text("派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~")
|
||||
except GachaLogAccountNotFound:
|
||||
@ -390,15 +390,18 @@ class GachaLogPlugin(Plugin.Conversation, BasePlugin.Conversation):
|
||||
if data["hasMore"] and not group:
|
||||
document = True
|
||||
data["hasMore"] = False
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT if document else ChatAction.UPLOAD_PHOTO)
|
||||
png_data = await self.template_service.render(
|
||||
"genshin/gacha_count/gacha_count.html", data, full_page=True, query_selector=".body_box"
|
||||
"genshin/gacha_count/gacha_count.html",
|
||||
data,
|
||||
full_page=True,
|
||||
query_selector=".body_box",
|
||||
file_type=FileType.DOCUMENT if document else FileType.PHOTO,
|
||||
)
|
||||
if document:
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT)
|
||||
await message.reply_document(png_data, filename="抽卡统计.png")
|
||||
await png_data.reply_document(message, filename="抽卡统计.png")
|
||||
else:
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await message.reply_photo(png_data)
|
||||
await png_data.reply_photo(message)
|
||||
except GachaLogNotFound:
|
||||
await message.reply_text("派蒙没有找到你的抽卡记录,快来私聊派蒙导入吧~")
|
||||
except GachaLogAccountNotFound:
|
||||
|
@ -1,9 +1,7 @@
|
||||
from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.error import BadRequest
|
||||
from telegram.ext import CommandHandler, CallbackContext
|
||||
|
||||
from core.bot import bot
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from utils.decorators.error import error_callable
|
||||
@ -13,8 +11,6 @@ from utils.log import logger
|
||||
|
||||
class HelpPlugin(Plugin):
|
||||
def __init__(self, template_service: TemplateService = None):
|
||||
self.file_id = None
|
||||
self.help_png = None
|
||||
if template_service is None:
|
||||
raise ModuleNotFoundError
|
||||
self.template_service = template_service
|
||||
@ -26,18 +22,9 @@ class HelpPlugin(Plugin):
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
logger.info(f"用户 {user.full_name}[{user.id}] 发出help命令")
|
||||
if self.file_id is None or bot.config.debug:
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
help_png = await self.template_service.render("bot/help/help.html", {}, {"width": 1280, "height": 900})
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
reply_photo = await message.reply_photo(help_png, filename="help.png", allow_sending_without_reply=True)
|
||||
photo = reply_photo.photo[0]
|
||||
self.file_id = photo.file_id
|
||||
else:
|
||||
try:
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await message.reply_photo(self.file_id, allow_sending_without_reply=True)
|
||||
except BadRequest as error:
|
||||
self.file_id = None
|
||||
logger.error("发送图片失败,尝试清空已经保存的file_id,错误信息为", error)
|
||||
await message.reply_text("发送图片失败", allow_sending_without_reply=True)
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
render_result = await self.template_service.render(
|
||||
"bot/help/help.html", {}, {"width": 1280, "height": 900}, ttl=30 * 24 * 60 * 60
|
||||
)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await render_result.reply_photo(message, filename="help.png", allow_sending_without_reply=True)
|
||||
|
@ -11,7 +11,7 @@ from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.services import CookiesService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template.services import TemplateService
|
||||
from core.template.services import RenderResult, TemplateService
|
||||
from core.user.error import UserNotFoundError
|
||||
from core.user.services import UserService
|
||||
from utils.bot import get_all_args
|
||||
@ -65,7 +65,7 @@ class Ledger(Plugin, BasePlugin):
|
||||
self.user_service = user_service
|
||||
self.current_dir = os.getcwd()
|
||||
|
||||
async def _start_get_ledger(self, client, month=None) -> bytes:
|
||||
async def _start_get_ledger(self, client, month=None) -> RenderResult:
|
||||
try:
|
||||
diary_info = await client.get_diary(client.uid, month=month)
|
||||
except GenshinException as error:
|
||||
@ -98,10 +98,10 @@ class Ledger(Plugin, BasePlugin):
|
||||
"categories": categories,
|
||||
"color": color,
|
||||
}
|
||||
png_data = await self.template_service.render(
|
||||
render_result = await self.template_service.render(
|
||||
"genshin/ledger/ledger.html", ledger_data, {"width": 580, "height": 610}
|
||||
)
|
||||
return png_data
|
||||
return render_result
|
||||
|
||||
@handler(CommandHandler, command="ledger", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^旅行札记查询(.*)"), block=False)
|
||||
@ -122,7 +122,7 @@ class Ledger(Plugin, BasePlugin):
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
try:
|
||||
client = await get_genshin_client(user.id)
|
||||
png_data = await self._start_get_ledger(client, month)
|
||||
render_result = await self._start_get_ledger(client, month)
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
if filters.ChatType.GROUPS.filter(message):
|
||||
buttons = [[InlineKeyboardButton("点我私聊", url=f"https://t.me/{context.bot.username}?start=set_cookie")]]
|
||||
@ -142,4 +142,4 @@ class Ledger(Plugin, BasePlugin):
|
||||
self._add_delete_message_job(context, message.chat_id, message.message_id, 30)
|
||||
return
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await message.reply_photo(png_data, filename=f"{client.uid}.png", allow_sending_without_reply=True)
|
||||
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
|
||||
|
@ -17,7 +17,7 @@ from enkanetwork import (
|
||||
VaildateUIDError,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, InputMediaPhoto, Update
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, CallbackQueryHandler, CommandHandler, MessageHandler, filters
|
||||
|
||||
@ -132,8 +132,8 @@ class PlayerCards(Plugin, BasePlugin):
|
||||
await message.reply_text(f"角色展柜中未找到 {character_name}")
|
||||
return
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
pnd_data = await RenderTemplate(uid, characters, self.template_service).render() # pylint: disable=W0631
|
||||
await message.reply_photo(pnd_data, filename=f"player_card_{uid}_{character_name}.png")
|
||||
render_result = await RenderTemplate(uid, characters, self.template_service).render() # pylint: disable=W0631
|
||||
await render_result.reply_photo(message, filename=f"player_card_{uid}_{character_name}.png")
|
||||
|
||||
@handler(CallbackQueryHandler, pattern=r"^get_player_card\|", block=False)
|
||||
@restricts(restricts_time_of_groups=20, without_overlapping=True)
|
||||
@ -171,8 +171,9 @@ class PlayerCards(Plugin, BasePlugin):
|
||||
return
|
||||
await callback_query.answer(text="正在渲染图片中 请稍等 请不要重复点击按钮", show_alert=False)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
pnd_data = await RenderTemplate(uid, characters, self.template_service).render() # pylint: disable=W0631
|
||||
await message.edit_media(InputMediaPhoto(pnd_data, filename=f"player_card_{uid}_{result}.png"))
|
||||
render_result = await RenderTemplate(uid, characters, self.template_service).render() # pylint: disable=W0631
|
||||
render_result.filename = f"player_card_{uid}_{result}.png"
|
||||
await render_result.edit_media(message)
|
||||
|
||||
|
||||
class Artifact(BaseModel):
|
||||
@ -195,15 +196,15 @@ class Artifact(BaseModel):
|
||||
self.score = round(self.score, 1)
|
||||
|
||||
for r in (
|
||||
("D", 10),
|
||||
("C", 16.5),
|
||||
("B", 23.1),
|
||||
("A", 29.7),
|
||||
("S", 36.3),
|
||||
("SS", 42.9),
|
||||
("SSS", 49.5),
|
||||
("ACE", 56.1),
|
||||
("ACE²", 66),
|
||||
("D", 10),
|
||||
("C", 16.5),
|
||||
("B", 23.1),
|
||||
("A", 29.7),
|
||||
("S", 36.3),
|
||||
("SS", 42.9),
|
||||
("SSS", 49.5),
|
||||
("ACE", 56.1),
|
||||
("ACE²", 66),
|
||||
):
|
||||
if self.score >= r[1]:
|
||||
self.score_label = r[0]
|
||||
@ -243,15 +244,15 @@ class RenderTemplate:
|
||||
|
||||
artifact_total_score_label: str = "E"
|
||||
for r in (
|
||||
("D", 10),
|
||||
("C", 16.5),
|
||||
("B", 23.1),
|
||||
("A", 29.7),
|
||||
("S", 36.3),
|
||||
("SS", 42.9),
|
||||
("SSS", 49.5),
|
||||
("ACE", 56.1),
|
||||
("ACE²", 66),
|
||||
("D", 10),
|
||||
("C", 16.5),
|
||||
("B", 23.1),
|
||||
("A", 29.7),
|
||||
("S", 36.3),
|
||||
("SS", 42.9),
|
||||
("SSS", 49.5),
|
||||
("ACE", 56.1),
|
||||
("ACE²", 66),
|
||||
):
|
||||
if artifact_total_score / 5 >= r[1]:
|
||||
artifact_total_score_label = r[0]
|
||||
@ -285,6 +286,7 @@ class RenderTemplate:
|
||||
{"width": 950, "height": 1080},
|
||||
full_page=True,
|
||||
query_selector=".text-neutral-200",
|
||||
ttl=7 * 24 * 60 * 60,
|
||||
)
|
||||
|
||||
async def de_stats(self) -> List[Tuple[str, Any]]:
|
||||
|
@ -15,6 +15,7 @@ from telegram.ext import (
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError, TooManyRequestPublicCookies
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template.models import RenderResult
|
||||
from core.template.services import TemplateService
|
||||
from core.user.error import UserNotFoundError
|
||||
from utils.decorators.error import error_callable
|
||||
@ -51,7 +52,7 @@ class UserStatsPlugins(Plugin, BasePlugin):
|
||||
client = await get_genshin_client(user.id)
|
||||
except CookiesNotFoundError:
|
||||
client, uid = await get_public_genshin_client(user.id)
|
||||
png_data = await self.render(client, uid)
|
||||
render_result = await self.render(client, uid)
|
||||
except UserNotFoundError:
|
||||
if filters.ChatType.GROUPS.filter(message):
|
||||
buttons = [[InlineKeyboardButton("点我私聊", url=f"https://t.me/{context.bot.username}?start=set_uid")]]
|
||||
@ -73,9 +74,9 @@ class UserStatsPlugins(Plugin, BasePlugin):
|
||||
await message.reply_text("角色数据有误 估计是派蒙晕了")
|
||||
return
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await message.reply_photo(png_data, filename=f"{client.uid}.png", allow_sending_without_reply=True)
|
||||
await render_result.reply_photo(message, filename=f"{client.uid}.png", allow_sending_without_reply=True)
|
||||
|
||||
async def render(self, client: Client, uid: Optional[int] = None) -> bytes:
|
||||
async def render(self, client: Client, uid: Optional[int] = None) -> RenderResult:
|
||||
if uid is None:
|
||||
uid = client.uid
|
||||
|
||||
|
@ -111,9 +111,11 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
|
||||
template_data = await input_template_data(weapon_data)
|
||||
png_data = await self.template_service.render(
|
||||
"genshin/weapon/weapon.html", template_data, {"width": 540, "height": 540}
|
||||
"genshin/weapon/weapon.html", template_data, {"width": 540, "height": 540}, ttl=31 * 24 * 60 * 60
|
||||
)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
await message.reply_photo(
|
||||
png_data, filename=f"{template_data['weapon_name']}.png", allow_sending_without_reply=True
|
||||
await png_data.reply_photo(
|
||||
message,
|
||||
filename=f"{template_data['weapon_name']}.png",
|
||||
allow_sending_without_reply=True,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user