添加查看今日素材表功能

This commit is contained in:
Karako 2022-09-18 00:28:51 +08:00 committed by GitHub
parent babdb90db7
commit 5016f7cb0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1902 additions and 61 deletions

8
core/assets/__init__.py Normal file
View File

@ -0,0 +1,8 @@
from core.service import init_service
from .service import AssetsService
@init_service
def create_wiki_service():
_service = AssetsService()
return _service

164
core/assets/service.py Normal file
View File

@ -0,0 +1,164 @@
from abc import ABC, abstractmethod
from pathlib import Path
from ssl import SSLZeroReturnError
from typing import ClassVar, Optional, Union
from aiofiles import open as async_open
from httpx import AsyncClient, HTTPError
from core.service import Service
from metadata.honey import HONEY_RESERVED_ID_MAP
from metadata.shortname import roleToId, roles
from modules.wiki.base import SCRAPE_HOST
from utils.const import PROJECT_ROOT
from utils.helpers import mkdir
from utils.typedefs import StrOrURL
ASSETS_PATH = PROJECT_ROOT.joinpath('resources/assets')
ASSETS_PATH.mkdir(exist_ok=True)
class _AssetsService(ABC):
_dir: ClassVar[Path]
id: str
type: str
@property
def path(self) -> Path:
return mkdir(self._dir.joinpath(self.id))
def __init__(self, client: AsyncClient):
self._client = client
@abstractmethod
def __call__(self, target):
pass
def __init_subclass__(cls, **kwargs):
cls.type = cls.__name__.lstrip('_').split('Assets')[0].lower()
cls._dir = ASSETS_PATH.joinpath(cls.type)
cls._dir.mkdir(exist_ok=True)
return cls
async def _download(self, url: StrOrURL, path: Path, retry: int = 5) -> Optional[Path]:
import asyncio
for _ in range(retry):
try:
response = await self._client.get(url, follow_redirects=False)
except (HTTPError, SSLZeroReturnError):
await asyncio.sleep(1)
continue
if response.status_code != 200:
return None
async with async_open(path, 'wb') as file:
await file.write(response.content)
return path
@abstractmethod
async def icon(self) -> Path:
pass
class _CharacterAssets(_AssetsService):
# noinspection SpellCheckingInspection
def __call__(self, target: Union[str, int]) -> "_CharacterAssets":
if isinstance(target, int):
if target == 10000005:
self.id = 'playerboy_005'
elif target == 10000007:
self.id = 'playergirl_007'
else:
self.id = f"{roles[target][2]}_{str(target)[-3:]}"
elif not target[-1].isdigit():
target = roleToId(target)
self.id = f"{roles[target][2]}_{str(target)[-3:]}"
else:
self.id = target
return self
async def icon(self) -> Path:
if (path := self.path.joinpath('icon.webp')).exists():
return path
return await self._download(SCRAPE_HOST.join(SCRAPE_HOST.join(f'/img/{self.id}_icon.webp')), path)
async def side(self) -> Path:
if (path := self.path.joinpath('side.webp')).exists():
return path
return await self._download(SCRAPE_HOST.join(SCRAPE_HOST.join(f'/img/{self.id}_side_icon.webp')), path)
async def gacha(self) -> Path:
if (path := self.path.joinpath('gacha.webp')).exists():
return path
return await self._download(SCRAPE_HOST.join(SCRAPE_HOST.join(f'/img/{self.id}_gacha_card.webp')), path)
async def splash(self) -> Optional[Path]:
if (path := self.path.joinpath('splash.webp')).exists():
return path
return await self._download(SCRAPE_HOST.join(SCRAPE_HOST.join(f'/img/{self.id}_gacha_splash.webp')), path)
class _WeaponAssets(_AssetsService):
def __call__(self, target: str) -> '_WeaponAssets':
if not target[-1].isdigit():
self.id = HONEY_RESERVED_ID_MAP['weapon'][target][0]
else:
self.id = target
return self
async def icon(self) -> Path:
if (path := self.path.joinpath('icon.webp')).exists():
return path
return await self._download(SCRAPE_HOST.join(SCRAPE_HOST.join(f'/img/{self.id}.webp')), path)
async def awakened(self) -> Path:
if (path := self.path.joinpath('awakened.webp')).exists():
return path
return await self._download(SCRAPE_HOST.join(SCRAPE_HOST.join(f'/img/{self.id}_awaken_icon.webp')), path)
async def gacha(self) -> Path:
if (path := self.path.joinpath('gacha.webp')).exists():
return path
return await self._download(SCRAPE_HOST.join(SCRAPE_HOST.join(f'/img/{self.id}_gacha_icon.webp')), path)
class _MaterialAssets(_AssetsService):
def __call__(self, target) -> "_MaterialAssets":
if not target[-1].isdigit():
self.id = HONEY_RESERVED_ID_MAP['material'][target][0]
else:
self.id = target
return self
async def icon(self) -> Path:
if (path := self.path.joinpath('icon.webp')).exists():
return path
return await self._download(SCRAPE_HOST.join(SCRAPE_HOST.join(f'/img/{self.id}.webp')), path)
class AssetsService(Service):
"""asset服务
用于储存和管理 asset :
当对应的 asset (如某角色图标)不存在时该服务会先查找本地
若本地不存在则从网络上下载若存在则返回其路径
"""
character: _CharacterAssets
weapon: _WeaponAssets
material: _MaterialAssets
def __init__(self):
self.client = AsyncClient()
self.character = _CharacterAssets(self.client)
self.weapon = _WeaponAssets(self.client)
self.material = _MaterialAssets(self.client)

View File

@ -1,14 +1,23 @@
import asyncio
import inspect
import os
from asyncio import CancelledError
from importlib import import_module
from multiprocessing import RLock as Lock
from pathlib import Path
from typing import Any, Callable, ClassVar, Dict, Iterator, List, NoReturn, Optional, TYPE_CHECKING, Type, TypeVar
import pytz
from async_timeout import timeout
from telegram.error import NetworkError, TimedOut
from telegram.ext import AIORateLimiter, Application as TgApplication, Defaults, JobQueue, MessageHandler
from telegram.ext import (
AIORateLimiter,
Application as TgApplication,
CallbackContext,
Defaults,
JobQueue,
MessageHandler,
)
from telegram.ext.filters import StatusUpdate
from core.config import BotConfig, config # pylint: disable=W0611
@ -21,7 +30,6 @@ from utils.log import logger
if TYPE_CHECKING:
from telegram import Update
from telegram.ext import CallbackContext
__all__ = ['bot']
@ -45,12 +53,7 @@ class Bot:
_services: Dict[Type[T], T] = {}
_running: bool = False
def init_inject(self, target: Callable[..., T]) -> T:
"""用于实例化Plugin的方法。用于给插件传入一些必要组件如 MySQL、Redis等"""
if isinstance(target, type):
signature = inspect.signature(target.__init__)
else:
signature = inspect.signature(target)
def _inject(self, signature: inspect.Signature, target: Callable[..., T]) -> T:
kwargs = {}
for name, parameter in signature.parameters.items():
if name != 'self' and parameter.annotation != inspect.Parameter.empty:
@ -58,6 +61,17 @@ class Bot:
kwargs[name] = value
return target(**kwargs)
def init_inject(self, target: Callable[..., T]) -> T:
"""用于实例化Plugin的方法。用于给插件传入一些必要组件如 MySQL、Redis等"""
if isinstance(target, type):
signature = inspect.signature(target.__init__)
else:
signature = inspect.signature(target)
return self._inject(signature, target)
async def async_inject(self, target: Callable[..., T]) -> T:
return await self._inject(inspect.signature(target), target)
def _gen_pkg(self, root: Path) -> Iterator[str]:
"""生成可以用于 import_module 导入的字符串"""
for path in root.iterdir():
@ -84,7 +98,7 @@ class Bot:
try:
plugin: PluginType = self.init_inject(plugin_cls)
if hasattr(plugin, '__async_init__'):
await plugin.__async_init__()
await self.async_inject(plugin.__async_init__)
handlers = plugin.handlers
self.app.add_handlers(handlers)
if handlers:
@ -170,18 +184,22 @@ class Bot:
if not self._services:
return
logger.info('正在关闭服务')
for _, service in self._services.items():
try:
if hasattr(service, 'stop'):
if inspect.iscoroutinefunction(service.stop):
await service.stop()
else:
service.stop()
logger.success(f'服务 "{service.__class__.__name__}" 关闭成功')
except Exception as e: # pylint: disable=W0703
logger.exception(f"服务 \"{service.__class__.__name__}\" 关闭失败: \n{type(e).__name__}: {e}")
for _, service in filter(lambda x: not isinstance(x[1], TgApplication), self._services.items()):
async with timeout(5):
try:
if hasattr(service, 'stop'):
if inspect.iscoroutinefunction(service.stop):
await service.stop()
else:
service.stop()
logger.success(f'服务 "{service.__class__.__name__}" 关闭成功')
except CancelledError:
logger.warning(f'服务 "{service.__class__.__name__}" 关闭超时')
except Exception as e: # pylint: disable=W0703
logger.exception(f"服务 \"{service.__class__.__name__}\" 关闭失败: \n{type(e).__name__}: {e}")
async def _post_init(self, _) -> NoReturn:
async def _post_init(self, context: CallbackContext) -> NoReturn:
self._services.update({CallbackContext: context})
logger.info('开始初始化服务')
await self.start_services()
logger.info('开始安装插件')

464
metadata/honey.py Normal file
View File

@ -0,0 +1,464 @@
"""此文件用于储存 honey impact 中的部分基础数据"""
__all__ = [
'HONEY_ID_MAP', 'HONEY_RESERVED_ID_MAP',
'HONEY_ROLE_NAME_MAP'
]
# noinspection SpellCheckingInspection
HONEY_ID_MAP = {
'character': {
'ayaka_002': ['神里绫华', 5],
'xiangling_023': ['香菱', 4],
'xingqiu_025': ['行秋', 4],
'albedo_038': ['阿贝多', 5],
'lisa_006': ['丽莎', 4],
'sucrose_043': ['砂糖', 4],
'mona_041': ['莫娜', 5],
'diona_039': ['迪奥娜', 4],
'venti_022': ['温迪', 5],
'xinyan_044': ['辛焱', 4],
'rosaria_045': ['罗莎莉亚', 4],
'hutao_046': ['胡桃', 5],
'zhongli_030': ['钟离', 5],
'ningguang_027': ['凝光', 4],
'eula_051': ['优菈', 5],
'shougun_052': ['雷电将军', 5],
'sayu_053': ['早柚', 4],
'keqing_042': ['刻晴', 5],
'ganyu_037': ['甘雨', 5],
'gorou_055': ['五郎', 4],
'tartaglia_033': ['达达利亚', 5],
'beidou_024': ['北斗', 4],
'itto_057': ['荒泷一斗', 5],
'ambor_021': ['安柏', 4],
'diluc_016': ['迪卢克', 5],
'chongyun_036': ['重云', 4],
'kaeya_015': ['凯亚', 4],
'aloy_062': ['埃洛伊', 4],
'yunjin_064': ['云堇', 4],
'shinobu_065': ['久岐忍', 4],
'ayato_066': ['神里绫人', 5],
'collei_067': ['柯莱', 4],
'feiyan_048': ['烟绯', 4],
'razor_020': ['雷泽', 4],
'barbara_014': ['芭芭拉', 4],
'dori_068': ['多莉', 4],
'noel_034': ['诺艾尔', 4],
'tighnari_069': ['提纳里', 5],
'kazuha_047': ['枫原万叶', 5],
'qiqi_035': ['七七', 5],
'bennett_032': ['班尼特', 4],
'nilou_070': ['妮露', 5],
'fischl_031': ['菲谢尔', 4],
'klee_029': ['可莉', 5],
'cyno_071': ['赛诺', 5],
'candace_072': ['坎蒂丝', 4],
'qin_003': ['', 5],
'xiao_026': ['', 5],
'playergirl_007': ['', 5],
'heizo_059': ['鹿野院平藏', 4],
'yoimiya_049': ['宵宫', 5],
'playerboy_005': ['', 5],
'sara_056': ['九条裟罗', 4],
'tohma_050': ['托马', 4],
'kokomi_054': ['珊瑚宫心海', 5],
'shenhe_063': ['申鹤', 5],
'yae_058': ['八重神子', 5],
'yelan_060': ['夜兰', 5]
},
'weapon': {
'i_n11401': ['西风剑', 4],
'i_n11305': ['吃虎鱼刀', 3],
'i_n11101': ['无锋剑', 1],
'i_n11303': ['旅行剑', 3],
'i_n11410': ['暗巷闪光', 4],
'i_n11301': ['冷刃', 3],
'i_n11416': ['笼钓瓶一心', 4],
'i_n11407': ['铁蜂刺', 4],
'i_n11501': ['风鹰剑', 5],
'i_n11419': ['「一心传」名刀', 4],
'i_n13409': ['龙脊长枪', 4],
'i_n13406': ['千岩长枪', 4],
'i_n11412': ['降临之剑', 4],
'i_n13505': ['和璞鸢', 5],
'i_n11504': ['斫峰之刃', 5],
'i_n11417': ['原木刀', 4],
'i_n13101': ['新手长枪', 1],
'i_n11509': ['雾切之回光', 5],
'i_n11502': ['天空之刃', 5],
'i_n14304': ['翡玉法球', 3],
'i_n13401': ['匣里灭辰', 4],
'i_n11413': ['腐殖之剑', 4],
'i_n11404': ['宗室长剑', 4],
'i_n11418': ['西福斯的月光', 4],
'i_n11415': ['辰砂之纺锤', 4],
'i_n11503': ['苍古自由之誓', 5],
'i_n13402': ['试作星镰', 4],
'i_n11511': ['圣显之钥', 5],
'i_n11409': ['黑剑', 4],
'i_n11414': ['天目影打刀', 4],
'i_n11405': ['匣里龙吟', 4],
'i_n11510': ['波乱月白经津', 5],
'i_n13405': ['决斗之枪', 4],
'i_n13407': ['西风长枪', 4],
'i_n11408': ['黑岩长剑', 4],
'i_n14306': ['琥珀玥', 3],
'i_n11505': ['磐岩结绿', 5],
'i_n14408': ['黑岩绯玉', 4],
'i_n14417': ['盈满之实', 4],
'i_n14416': ['流浪的晚星', 4],
'i_n12301': ['铁影阔剑', 3],
'i_n14506': ['不灭月华', 5],
'i_n14305': ['甲级宝珏', 3],
'i_n13415': ['「渔获」', 4],
'i_n14402': ['流浪乐章', 4],
'i_n12402': ['钟剑', 4],
'i_n12201': ['佣兵重剑', 2],
'i_n14403': ['祭礼残章', 4],
'i_n14405': ['匣里日月', 4],
'i_n12101': ['训练大剑', 1],
'i_n14501': ['天空之卷', 5],
'i_n14413': ['嘟嘟可故事集', 4],
'i_n14302': ['讨龙英杰谭', 3],
'i_n14303': ['异世界行记', 3],
'i_n12303': ['白铁大剑', 3],
'i_n14409': ['昭心', 4],
'i_n13502': ['天空之脊', 5],
'i_n14404': ['宗室秘法录', 4],
'i_n14401': ['西风秘典', 4],
'i_n14415': ['证誓之明瞳', 4],
'i_n13301': ['白缨枪', 3],
'i_n13404': ['黑岩刺枪', 4],
'i_n13408': ['宗室猎枪', 4],
'i_n13201': ['铁尖枪', 2],
'i_n13511': ['赤沙之杖', 5],
'i_n13416': ['断浪长鳍', 4],
'i_n13509': ['薙草之稻光', 5],
'i_n13403': ['流月针', 4],
'i_n13417': ['贯月矢', 4],
'i_n13419': ['风信之锋', 4],
'i_n13302': ['钺矛', 3],
'i_n11406': ['试作斩岩', 4],
'i_n13414': ['喜多院十文字', 4],
'i_n14201': ['口袋魔导书', 2],
'i_n13501': ['护摩之杖', 5],
'i_n13303': ['黑缨枪', 3],
'i_n14101': ['学徒笔记', 1],
'i_n12401': ['西风大剑', 4],
'i_n12304': ['石英大剑', 3],
'i_n14412': ['忍冬之果', 4],
'i_n14414': ['白辰之环', 4],
'i_n14509': ['神乐之真意', 5],
'i_n14406': ['试作金珀', 4],
'i_n14502': ['四风原典', 5],
'i_n12305': ['以理服人', 3],
'i_n14504': ['尘世之锁', 5],
'i_n12306': ['飞天大御剑', 3],
'i_n14301': ['魔导绪论', 3],
'i_n12302': ['沐浴龙血的剑', 3],
'i_n14407': ['万国诸海图谱', 4],
'i_n13504': ['贯虹之槊', 5],
'i_n12416': ['恶王丸', 4],
'i_n12409': ['螭骨剑', 4],
'i_n12404': ['宗室大剑', 4],
'i_n12405': ['雨裁', 4],
'i_n12414': ['桂木斩长正', 4],
'i_n12408': ['黑岩斩刀', 4],
'i_n12410': ['千岩古剑', 4],
'i_n12406': ['试作古华', 4],
'i_n12415': ['玛海菈的水色', 4],
'i_n12403': ['祭礼大剑', 4],
'i_n12411': ['雪葬的星银', 4],
'i_n12412': ['衔珠海皇', 4],
'i_n12407': ['白影剑', 4],
'i_n11201': ['银剑', 2],
'i_n12504': ['无工之剑', 5],
'i_n15305': ['信使', 3],
'i_n15411': ['落霞', 4],
'i_n15413': ['风花之颂', 4],
'i_n15401': ['西风猎弓', 4],
'i_n12510': ['赤角石溃杵', 5],
'i_n15405': ['弓藏', 4],
'i_n15403': ['祭礼弓', 4],
'i_n15201': ['历练的猎弓', 2],
'i_n15404': ['宗室长弓', 4],
'i_n15302': ['神射手之誓', 3],
'i_n12501': ['天空之傲', 5],
'i_n15402': ['绝弦', 4],
'i_n12417': ['森林王器', 4],
'i_n11306': ['飞天御剑', 3],
'i_n15410': ['暗巷猎手', 4],
'i_n15414': ['破魔之弓', 4],
'i_n15101': ['猎弓', 1],
'i_n15415': ['掠食者', 4],
'i_n15301': ['鸦羽弓', 3],
'i_n11304': ['暗铁剑', 3],
'i_n15303': ['反曲弓', 3],
'i_n15306': ['黑檀弓', 3],
'i_n15408': ['黑岩战弓', 4],
'i_n15304': ['弹弓', 3],
'i_n15409': ['苍翠猎弓', 4],
'i_n15412': ['幽夜华尔兹', 4],
'i_n15406': ['试作澹月', 4],
'i_n15417': ['王下近侍', 4],
'i_n15501': ['天空之翼', 5],
'i_n15418': ['竭泽', 4],
'i_n15507': ['冬极白星', 5],
'i_n15508': ['若水', 5],
'i_n15503': ['终末嗟叹之诗', 5],
'i_n15509': ['飞雷之弦振', 5],
'i_n15511': ['猎人之径', 5],
'i_n12502': ['狼的末路', 5],
'i_n15407': ['钢轮弓', 4],
'i_n12503': ['松籁响起之时', 5],
'i_n15416': ['曚云之月', 4],
'i_n11402': ['笛剑', 4],
'i_n15502': ['阿莫斯之弓', 5],
'i_n11302': ['黎明神剑', 3],
'i_n13507': ['息灾', 5],
'i_n11403': ['祭礼剑', 4],
'i_n14410': ['暗巷的酒与诗', 4]
},
'material': {
'i_413': ['「勤劳」的哲学', 4],
'i_411': ['「勤劳」的教导', 2],
'i_n104333': ['「巧思」的指引', 3],
'i_584': ['今昔剧画之鬼人', 5],
'i_427': ['「天光」的指引', 3],
'i_453': ['「抗争」的哲学', 4],
'i_n112063': ['休眠菌核', 3],
'i_408': ['「浮世」的哲学', 4],
'i_n104336': ['「笃行」的指引', 3],
'i_n104337': ['「笃行」的哲学', 4],
'i_421': ['「自由」的教导', 2],
'i_n112068': ['混沌容器', 2],
'i_n104331': ['「诤言」的哲学', 4],
'i_n104330': ['「诤言」的指引', 3],
'i_402': ['「诗文」的指引', 3],
'i_416': ['「风雅」的教导', 2],
'i_423': ['「自由」的哲学', 4],
'i_407': ['「浮世」的指引', 3],
'i_581': ['今昔剧画之恶尉', 2],
'i_401': ['「诗文」的教导', 2],
'i_441': ['「繁荣」的教导', 2],
'i_n104335': ['「笃行」的教导', 2],
'i_422': ['「自由」的指引', 3],
'i_432': ['「黄金」的指引', 3],
'i_n104332': ['「巧思」的教导', 2],
'i_53': ['历战的箭簇', 3],
'i_417': ['「风雅」的指引', 3],
'i_431': ['「黄金」的教导', 2],
'i_403': ['「诗文」的哲学', 4],
'i_451': ['「抗争」的教导', 2],
'i_462': ['东风之爪', 5],
'i_461': ['东风之翎', 5],
'i_524': ['凛风奔狼的怀乡', 5],
'i_433': ['「黄金」的哲学', 4],
'i_406': ['「浮世」的教导', 2],
'i_452': ['「抗争」的指引', 3],
'i_n104334': ['「巧思」的哲学', 4],
'i_133': ['原素花蜜', 3],
'i_582': ['今昔剧画之虎啮', 3],
'i_442': ['「繁荣」的指引', 3],
'i_483': ['凶将之手眼', 5],
'i_583': ['今昔剧画之一角', 4],
'i_418': ['「风雅」的哲学', 4],
'i_463': ['东风的吐息', 5],
'i_443': ['「繁荣」的哲学', 4],
'i_521': ['凛风奔狼的始龀', 2],
'i_n104329': ['「诤言」的教导', 2],
'i_464': ['北风之尾', 5],
'i_61': ['沉重号角', 2],
'i_523': ['凛风奔狼的断牙', 4],
'i_485': ['万劫之真意', 5],
'i_33': ['不祥的面具', 3],
'i_467': ['吞天之鲸·只角', 5],
'i_465': ['北风之环', 5],
'i_21': ['史莱姆凝液', 1],
'i_513': ['孤云寒林的圣骸', 4],
'i_185': ['浮游干核', 1],
'i_511': ['孤云寒林的光砂', 2],
'i_522': ['凛风奔狼的裂齿', 3],
'i_514': ['孤云寒林的神体', 5],
'i_412': ['「勤劳」的指引', 3],
'i_466': ['北风的魂匣', 5],
'i_173': ['混沌真眼', 4],
'i_73': ['地脉的新芽', 4],
'i_183': ['偏光棱镜', 4],
'i_n112061': ['孢囊晶尘', 3],
'i_83': ['混沌炉心', 4],
'i_142': ['结实的骨片', 3],
'i_22': ['史莱姆清', 2],
'i_112': ['士官的徽记', 2],
'i_23': ['史莱姆原浆', 3],
'i_163': ['名刀镡', 3],
'i_n112062': ['失活菌核', 2],
'i_n112072': ['混浊棱晶', 3],
'i_428': ['「天光」的哲学', 4],
'i_552': ['漆黑陨铁的一片', 3],
'i_172': ['混沌枢纽', 3],
'i_72': ['地脉的枯叶', 3],
'i_426': ['「天光」的教导', 2],
'i_n112070': ['混沌锚栓', 4],
'i_491': ['智识之冕', 5],
'i_123': ['攫金鸦印', 3],
'i_121': ['寻宝鸦印', 1],
'i_153': ['幽邃刻像', 4],
'i_41': ['导能绘卷', 1],
'i_187': ['浮游晶化核', 3],
'i_32': ['污秽的面具', 2],
'i_469': ['武炼之魂·孤影', 5],
'i_553': ['漆黑陨铁的一角', 4],
'i_151': ['晦暗刻像', 2],
'i_182': ['水晶棱镜', 3],
'i_71': ['地脉的旧枝', 2],
'i_512': ['孤云寒林的辉岩', 3],
'i_42': ['封魔绘卷', 2],
'i_132': ['微光花蜜', 2],
'i_152': ['夤夜刻像', 3],
'i_186': ['浮游幽核', 2],
'i_482': ['灰烬之心', 5],
'i_81': ['混沌装置', 2],
'i_82': ['混沌回路', 3],
'i_n114045': ['烈日威权的残响', 2],
'i_541': ['狮牙斗士的枷锁', 2],
'i_n114046': ['烈日威权的余光', 3],
'i_171': ['混沌机关', 2],
'i_51': ['牢固的箭簇', 1],
'i_542': ['狮牙斗士的铁链', 3],
'i_111': ['新兵的徽记', 1],
'i_n112069': ['混沌模块', 3],
'i_n114048': ['烈日威权的旧日', 5],
'i_162': ['影打刀镡', 2],
'i_481': ['狱火之蝶', 5],
'i_554': ['漆黑陨铁的一块', 5],
'i_n112071': ['破缺棱晶', 2],
'i_143': ['石化的骨片', 4],
'i_103': ['督察长祭刀', 4],
'i_31': ['破损的面具', 1],
'i_43': ['禁咒绘卷', 3],
'i_n114043': ['绿洲花园的哀思', 4],
'i_n112067': ['织金红绸', 3],
'i_n114042': ['绿洲花园的恩惠', 3],
'i_n114044': ['绿洲花园的真谛', 5],
'i_n112060': ['荧光孢粉', 2],
'i_122': ['藏银鸦印', 2],
'i_n112065': ['褪色红绸', 1],
'i_141': ['脆弱的骨片', 2],
'i_n114040': ['谧林涓露的金符', 5],
'i_n114039': ['谧林涓露的银符', 4],
'i_562': ['远海夷地的玉枝', 3],
'i_563': ['远海夷地的琼枝', 4],
'i_564': ['远海夷地的金枝', 5],
'i_n114038': ['谧林涓露的铁符', 3],
'i_n114037': ['谧林涓露的铜符', 2],
'i_52': ['锐利的箭簇', 2],
'i_n112066': ['镶边红绸', 2],
'i_174': ['隐兽指爪', 2],
'i_176': ['隐兽鬼爪', 4],
'i_534': ['雾海云间的转还', 5],
'i_531': ['雾海云间的铅丹', 2],
'i_503': ['高塔孤王的断片', 4],
'i_91': ['雾虚花粉', 2],
'i_92': ['雾虚草囊', 3],
'i_501': ['高塔孤王的破瓦', 2],
'i_504': ['高塔孤王的碎梦', 5],
'i_468': ['魔王之刃·残片', 5],
'i_544': ['狮牙斗士的理想', 5],
'i_470': ['龙王之冕', 5],
'i_572': ['鸣神御灵的欢喜', 3],
'i_574': ['鸣神御灵的勇武', 5],
'i_573': ['鸣神御灵的亲爱', 4],
'i_480': ['熔毁之刻', 5],
'i_62': ['黑铜号角', 3],
'i_101': ['猎兵祭刀', 2],
'i_551': ['漆黑陨铁的一粒', 2],
'i_484': ['祸神之禊泪', 5],
'i_n114041': ['绿洲花园的追忆', 2],
'i_n112059': ['蕈兽孢子', 1],
'i_561': ['远海夷地的瑚枝', 2],
'i_n112073': ['辉光棱晶', 4],
'i_175': ['隐兽利爪', 3],
'i_532': ['雾海云间的汞丹', 3],
'i_93': ['雾虚灯芯', 4],
'i_131': ['骗骗花蜜', 1],
'i_571': ['鸣神御灵的明惠', 2],
'i_n114047': ['烈日威权的梦想', 4],
'i_63': ['黑晶号角', 4],
'i_543': ['狮牙斗士的镣铐', 4],
'i_n112064': ['茁壮菌核', 4],
'i_161': ['破旧的刀镡', 1],
'i_472': ['鎏金之鳞', 5],
'i_113': ['尉官的徽记', 3],
'i_502': ['高塔孤王的残垣', 3],
'i_181': ['黯淡棱镜', 2],
'i_102': ['特工祭刀', 3],
'i_471': ['血玉之枝', 5],
'i_533': ['雾海云间的金丹', 4]
}
}
HONEY_RESERVED_ID_MAP = {
k: {j[0]: [i, j[1]] for i, j in v.items()} for k, v in HONEY_ID_MAP.items()
}
# noinspection SpellCheckingInspection
HONEY_ROLE_NAME_MAP = {
10000002: ['ayaka_002', '神里绫华', 'ayaka'],
10000042: ['keqing_042', '刻晴', 'keqing'],
10000030: ['zhongli_030', '钟离', 'zhongli'],
10000026: ['xiao_026', '', 'xiao'],
10000020: ['razor_020', '雷泽', 'razor'],
10000015: ['kaeya_015', '凯亚', 'kaeya'],
10000037: ['ganyu_037', '甘雨', 'ganyu'],
10000041: ['mona_041', '莫娜', 'mona'],
10000038: ['albedo_038', '阿贝多', 'albedo'],
10000014: ['barbara_014', '芭芭拉', 'barbara'],
10000027: ['ningguang_027', '凝光', 'ningguang'],
10000054: ['kokomi_054', '珊瑚宫心海', 'kokomi'],
10000044: ['xinyan_044', '辛焱', 'xinyan'],
10000056: ['sara_056', '九条裟罗', 'sara'],
10000053: ['sayu_053', '早柚', 'sayu'],
10000043: ['sucrose_043', '砂糖', 'sucrose'],
10000059: ['heizo_059', '鹿野院平藏', 'heizo'],
10000060: ['yelan_060', '夜兰', 'yelan'],
10000064: ['yunjin_064', '云堇', 'yunjin'],
10000050: ['tohma_050', '托马', 'tohma'],
10000066: ['ayato_066', '神里绫人', 'ayato'],
10000067: ['collei_067', '柯莱', 'collei'],
10000052: ['shougun_052', '雷电将军', 'shougun'],
10000069: ['tighnari_069', '提纳里', 'tighnari'],
10000007: ['playergirl_007', '', 'playergirl'],
10000016: ['diluc_016', '迪卢克', 'diluc'],
10000070: ['nilou_070', '妮露', 'nilou'],
10000047: ['kazuha_047', '枫原万叶', 'kazuha'],
10000055: ['gorou_055', '五郎', 'gorou'],
10000034: ['noel_034', '诺艾尔', 'noel'],
10000024: ['beidou_024', '北斗', 'beidou'],
10000032: ['bennett_032', '班尼特', 'bennett'],
10000062: ['aloy_062', '埃洛伊', 'aloy'],
10000025: ['xingqiu_025', '行秋', 'xingqiu'],
10000022: ['venti_022', '温迪', 'venti'],
10000036: ['chongyun_036', '重云', 'chongyun'],
10000049: ['yoimiya_049', '宵宫', 'yoimiya'],
10000029: ['klee_029', '可莉', 'klee'],
10000006: ['lisa_006', '丽莎', 'lisa'],
10000033: ['tartaglia_033', '达达利亚', 'tartaglia'],
10000039: ['diona_039', '迪奥娜', 'diona'],
10000063: ['shenhe_063', '申鹤', 'shenhe'],
10000072: ['candace_072', '坎蒂丝', 'candace'],
10000045: ['rosaria_045', '罗莎莉亚', 'rosaria'],
10000051: ['eula_051', '优菈', 'eula'],
10000035: ['qiqi_035', '七七', 'qiqi'],
10000057: ['itto_057', '荒泷一斗', 'itto'],
10000005: ['playerboy_005', '', 'playerboy'],
10000048: ['feiyan_048', '烟绯', 'feiyan'],
10000003: ['qin_003', '', 'qin'],
10000023: ['xiangling_023', '香菱', 'xiangling'],
10000071: ['cyno_071', '赛诺', 'cyno'],
10000031: ['fischl_031', '菲谢尔', 'fischl'],
10000046: ['hutao_046', '胡桃', 'hutao'],
10000021: ['ambor_021', '安柏', 'ambor'],
10000068: ['dori_068', '多莉', 'dori'],
10000065: ['shinobu_065', '久岐忍', 'shinobu'],
10000058: ['yae_058', '八重神子', 'yae']
}

View File

@ -1,22 +1,27 @@
from typing import Optional
__all__ = [
'roles', 'weapons',
'roleToId', 'roleToName', 'weaponToName',
]
# noinspection SpellCheckingInspection
roles = {
20000000: [
'主角', '旅行者', '卑鄙的外乡人', '荣誉骑士', '', '风主', '岩主', '雷主', '草主', '履刑者', '抽卡不歪真君'
],
10000002: ['神里绫华', 'Kamisato Ayaka', 'Ayaka', 'ayaka', '神里', '绫华', '神里凌华', '凌华', '白鹭公主',
10000002: ['神里绫华', 'Ayaka', 'ayaka', 'Kamisato Ayaka', '神里', '绫华', '神里凌华', '凌华', '白鹭公主',
'神里大小 姐'],
10000003: ['', 'Jean', 'jean', '团长', '代理团长', '琴团长', '蒲公英骑士'],
10000005: ['', '男主', '男主角', '龙哥', '空哥'],
10000005: ['', 'Aether', 'aether', '男主', '男主角', '龙哥', '空哥'],
10000006: ['丽莎', 'Lisa', 'lisa', '图书管理员', '图书馆管理员', '蔷薇魔女'],
10000007: ['', '女主', '女主角', '', '', '黄毛阿姨', '荧妹'],
10000007: ['', 'Lumine', 'lumine', '女主', '女主角', '', '', '黄毛阿姨', '荧妹'],
10000014: ['芭芭拉', 'Barbara', 'barbara', '巴巴拉', '拉粑粑', '拉巴巴', '内鬼', '加湿器', '闪耀偶像', '偶像'],
10000015: ['凯亚', 'Kaeya', 'kaeya', '盖亚', '凯子哥', '凯鸭', '矿工', '矿工头子', '骑兵队长', '凯子',
'凝冰渡海真君'],
10000016: ['迪卢克', 'diluc', 'Diluc', '卢姥爷', '姥爷', '卢老爷', '卢锅巴', '正义人', '正e人', '正E人', '卢本伟',
10000016: ['迪卢克', 'Diluc', 'diluc', '卢姥爷', '姥爷', '卢老爷', '卢锅巴', '正义人', '正e人', '正E人', '卢本伟',
'暗夜英雄', '卢卢伯爵', '落魄了', '落魄了家人们'],
10000020: ['雷泽', 'razor', 'Razor', '狼少年', '狼崽子', '狼崽', '卢皮卡', '小狼', '小狼狗'],
10000020: ['雷泽', 'Razor', 'razor', '狼少年', '狼崽子', '狼崽', '卢皮卡', '小狼', '小狼狗'],
10000021: ['安柏', 'Amber', 'amber', '安伯', '兔兔伯爵', '飞行冠军', '侦查骑士', '点火姬', '点火机', '打火机',
'打火姬'],
10000022: ['温迪', 'Venti', 'venti', '温蒂', '风神', '卖唱的', '巴巴托斯', '巴巴脱丝', '芭芭托斯', '芭芭脱丝',
@ -53,9 +58,9 @@ roles = {
10000045: ['罗莎莉亚', 'Rosaria', 'rosaria', '罗莎莉娅', '白色史莱姆', '白史莱姆', '修女', '罗莎利亚', '罗莎利娅',
'罗沙莉亚', '罗沙莉娅', '罗沙利亚', '罗沙利娅', '萝莎莉亚', '萝莎莉娅', '萝莎利亚', '萝莎利娅',
'萝沙莉亚', '萝沙莉娅', '萝沙利亚', '萝沙利娅'],
10000046: ['胡桃', 'Hu Tao', 'hu tao', 'HuTao', 'hutao', 'Hutao', '胡 淘', '往生堂堂主', '火化', '抬棺的', '蝴蝶',
10000046: ['胡桃', 'HuTao', 'hutao', 'Hu Tao', 'hu tao', 'Hutao', '胡 淘', '往生堂堂主', '火化', '抬棺的', '蝴蝶',
'核桃', '堂主', '胡堂主', '雪霁梅香'],
10000047: ['枫原万叶', 'Kaedehara Kazuha', 'Kazuha', 'kazuha', '万叶', '叶天帝', '天帝', '叶师傅'],
10000047: ['枫原万叶', 'Kazuha', 'kazuha', 'Kaedehara Kazuha', '万叶', '叶天帝', '天帝', '叶师傅'],
10000048: ['烟绯', 'Yanfei', 'yanfei', '烟老师', '律师', '罗翔'],
10000049: ['宵宫', 'Yoimiya', 'yoimiya', '霄宫', '烟花', '肖宫', '肖工', '绷带女孩'],
10000050: ['托马', 'Thoma', 'thoma', '家政官', '太郎丸', '地头蛇', '男仆', '拖马'],
@ -63,27 +68,30 @@ roles = {
10000052: ['雷电将军', 'Raiden Shogun', 'Raiden', 'raiden', '雷神', '将军', '雷军', '巴尔', '阿影', '',
'巴尔泽布', '煮饭婆', '奶香一刀', '无想一刀', '宅女'],
10000053: ['早柚', 'Sayu', 'sayu', '小狸猫', '狸 猫', '忍者'],
10000054: ['珊瑚宫心海', 'Sangonomiya Kokomi', 'Kokomi', 'kokomi', '心海', '军师', '珊瑚宫', '书记', '观赏鱼',
10000054: ['珊瑚宫心海', 'Kokomi', 'kokomi', 'Sangonomiya Kokomi', '心海', '军师', '珊瑚宫', '书记', '观赏鱼',
'水母', '', '美人鱼'],
10000055: ['五郎', 'Gorou', 'gorou', '柴犬', '土狗', '希娜', '希娜小姐'],
10000056: ['九条裟罗', 'Kujou Sara', 'Sara', 'sara', '九条', '九条沙罗', '裟罗', '沙罗', '天狗'],
10000057: ['荒泷一斗', 'Arataki Itto', 'Itto', 'itto', '荒龙一斗', '荒泷天下第一斗', '一斗', '一抖', '荒泷', '1斗',
10000056: ['九条裟罗', 'Sara', 'sara', 'Kujou Sara', '九条', '九条沙罗', '裟罗', '沙罗', '天狗'],
10000057: ['荒泷一斗', 'Itto', 'itto', 'Arataki Itto', '荒龙一斗', '荒泷天下第一斗', '一斗', '一抖', '荒泷', '1斗',
'牛牛', '斗子哥', '牛子哥', '牛子', '孩子 王', '斗虫', '巧乐兹', '放牛的'],
10000058: ['八重神子', 'Yae Miko', 'Miko', 'miko', '八重', '神子', '狐狸', '想得美哦', '巫女', '屑狐狸', '骚狐狸',
10000058: ['八重神子', 'Miko', 'miko', 'Yae Miko', '八重', '神子', '狐狸', '想得美哦', '巫女', '屑狐狸', '骚狐狸',
'八重宫司', '婶子', '小八'],
10000059: ['鹿野院平藏', 'shikanoin heizou', 'Heizou', 'heizou', 'heizo', '鹿野苑', '鹿野院', '平藏', '鹿野苑平藏',
10000059: ['鹿野院平藏', 'Heizou', 'heizou', 'shikanoin heizou', 'heizo', '鹿野苑', '鹿野院', '平藏', '鹿野苑平藏',
'鹿野', '小鹿'],
10000060: ['夜兰', 'Yelan', 'yelan', '夜阑', '叶 澜', '腋兰', '夜天后'],
10000062: ['埃洛伊', 'Aloy', 'aloy'],
10000063: ['申鹤', 'Shenhe', 'shenhe', '神鹤', '小姨', '小姨子', '审鹤'],
10000064: ['云堇', 'Yun Jin', 'yunjin', 'yun jin', '云瑾', '云先生', '云锦', '神女劈观'],
10000065: ['久岐忍', 'Kuki Shinobu', 'Kuki', 'kuki', 'Shinobu', 'shinobu', '97忍', '小忍', '久歧忍', '97', '茄忍',
10000064: ['云堇', 'YunJin', 'yunjin', 'Yun Jin', 'yun jin', '云瑾', '云先生', '云锦', '神女劈观'],
10000065: ['久岐忍', 'Kuki', 'kuki', 'Kuki Shinobu', 'Shinobu', 'shinobu', '97忍', '小忍', '久歧忍', '97', '茄忍',
'阿忍', '忍姐'],
10000066: ['神里绫人', 'Kamisato Ayato', 'Ayato', 'ayato', '绫人', '神里凌人', '凌人', '0人', '神人', '零人',
10000066: ['神里绫人', 'Ayato', 'ayato', 'Kamisato Ayato', '绫人', '神里凌人', '凌人', '0人', '神人', '零人',
'大舅哥'],
10000067: ['柯莱', 'Collei', 'collei', '克莱', '科莱', '须弥飞行冠军', '草安伯'],
10000068: ['多莉', 'Dori', 'dori', '多利', '多丽'],
10000069: ['提纳里', 'Tighnari', 'tighnari', '巡林官', '小提', '缇娜里', '提哪里', '提那里']
10000069: ['提纳里', 'Tighnari', 'tighnari', '巡林官', '小提', '缇娜里', '提哪里', '提那里'],
10000070: ['妮露', 'Nilou', 'nilou'],
10000071: ['赛诺', 'Cyno', 'cyno'],
10000072: ['坎蒂丝', 'Candace', 'candace'],
}
weapons = {
"磐岩结绿": ["绿箭", "绿剑"],
@ -166,16 +174,19 @@ weapons = {
}
# noinspection PyPep8Naming
def roleToName(shortname: str) -> str:
"""讲角色昵称转为正式名"""
return next((value[0] for value in roles.values() for name in value if name == shortname), shortname)
# noinspection PyPep8Naming
def roleToId(name: str) -> Optional[int]:
"""获取角色ID"""
return next((key for key, value in roles.items() for n in value if n == name), None)
# noinspection PyPep8Naming
def weaponToName(shortname: str) -> str:
"""讲武器昵称转为正式名"""
return next((key for key, value in weapons.items() if shortname == key or shortname in value), shortname)

View File

@ -1,56 +1,79 @@
import re
from typing import List
from typing import List, Optional, Tuple, Union
from bs4 import BeautifulSoup
from httpx import URL
from typing_extensions import Self
from modules.wiki.base import SCRAPE_HOST, WikiModel
__all__ = ['Material']
WEEKDAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
class Material(WikiModel):
"""材料
Attributes:
type: 类型
source: 获取方式
description: 描述
serise: 材料系列
"""
# noinspection PyUnresolvedReferences
"""武器、角色培养素材
Attributes:
type: 类型
weekdays: 每周开放的时间
source: 获取方式
description: 描述
"""
type: str
source: List[str]
source: Optional[List[str]] = None
weekdays: Optional[List[int]] = None
description: str
@staticmethod
def scrape_urls() -> List[URL]:
return [SCRAPE_HOST.join(f'fam_wep_{i}/?lang=CHS') for i in ['primary', 'secondary', 'common']]
weapon = [SCRAPE_HOST.join(f'fam_wep_{i}/?lang=CHS') for i in ['primary', 'secondary', 'common']]
talent = [SCRAPE_HOST.join(f'fam_talent_{i}/?lang=CHS') for i in ['book', 'boss', 'common', 'reward']]
return weapon + talent
@classmethod
async def _parse_soup(cls, soup: BeautifulSoup) -> Self:
"""解析材料页"""
async def get_name_list(cls, *, with_url: bool = False) -> List[Union[str, Tuple[str, URL]]]:
return list(sorted(set(await super(Material, cls).get_name_list(with_url=with_url)), key=lambda x: x[0]))
@classmethod
async def _parse_soup(cls, soup: BeautifulSoup) -> "Material":
"""解析突破素材页"""
soup = soup.select('.wp-block-post-content')[0]
tables = soup.find_all('table')
table_rows = tables[0].find_all('tr')
def get_table_row(target: str):
"""一个便捷函数,用于返回对应表格头的对应行的最后一个单元格中的文本"""
for row in table_rows:
if target in row.find('td').text:
return row.find_all('td')[-1]
def get_table_text(row_num: int) -> str:
"""一个快捷函数,用于返回表格对应行的最后一个单元格中的文本"""
"""一个便捷函数,用于返回表格对应行的最后一个单元格中的文本"""
return table_rows[row_num].find_all('td')[-1].text.replace('\xa0', '')
id_ = re.findall(r'/img/(.*?)\.webp', str(table_rows[0]))[0]
name = get_table_text(0)
rarity = len(table_rows[3].find_all('img'))
type_ = get_table_text(1)
source = list(
filter(
lambda x: x, # filter 在这里的作用是过滤掉为空的数据
table_rows[-2].find_all('td')[-1].encode_contents().decode().split('<br/>')
if (item_source := get_table_row('Item Source')) is not None:
item_source = list(
# filter 在这里的作用是过滤掉为空的数据
filter(lambda x: x, item_source.encode_contents().decode().split('<br/>'))
)
)
if (alter_source := get_table_row('Alternative Item')) is not None:
alter_source = list(
# filter 在这里的作用是过滤掉为空的数据
filter(lambda x: x, alter_source.encode_contents().decode().split('<br/>'))
)
source = list(sorted(set((item_source or []) + (alter_source or []))))
if (weekdays := get_table_row('Weekday')) is not None:
weekdays = [*(WEEKDAYS.index(weekdays.text.replace('\xa0', '').split(',')[0]) + 3 * i for i in range(2)), 6]
description = get_table_text(-1)
return Material(id=id_, name=name, rarity=rarity, type=type_, source=source, description=description)
return Material(
id=id_, name=name, rarity=rarity, type=type_, description=description, source=source, weekdays=weekdays
)
@property
def icon(self) -> str:

View File

View File

@ -0,0 +1,367 @@
import asyncio
import itertools
import os
import re
from asyncio import Lock
from ctypes import c_double
from datetime import datetime
from multiprocessing import Value
from pathlib import Path
from typing import Any, Dict, Iterable, List, Literal, Optional, Tuple, Union
import ujson as json
from aiofiles import open as async_open
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.constants import ChatAction, ParseMode
from telegram.error import RetryAfter, TimedOut
from telegram.ext import CallbackContext
from core.assets import AssetsService
from core.baseplugin import BasePlugin
from core.cookies.error import CookiesNotFoundError
from core.plugin import Plugin, handler
from core.template import TemplateService
from core.user.error import UserNotFoundError
from metadata.honey import HONEY_ID_MAP, HONEY_ROLE_NAME_MAP
from utils.bot import get_all_args
from utils.const import RESOURCE_DIR
from utils.decorators.admins import bot_admins_rights_check
from utils.decorators.error import error_callable
from utils.decorators.restricts import restricts
from utils.helpers import get_genshin_client
from utils.log import logger
DATA_TYPE = Dict[str, List[List[str]]]
DATA_FILE_PATH = Path(__file__).joinpath('../daily.json').resolve()
AREA = ['蒙德', '璃月', '稻妻', '须弥']
DOMAINS = ['忘却之峡', '太山府', '菫色之庭', '昏识塔', '塞西莉亚苗圃', '震雷连山密宫', '砂流之庭', '有顶塔']
DOMAIN_AREA_MAP = dict(zip(DOMAINS, AREA * 2))
WEEK_MAP = ['', '', '', '', '', '', '']
def convert_path(path: Union[str, Path]) -> str:
return f"..{os.sep}..{os.sep}" + str(path.relative_to(RESOURCE_DIR))
def sort_item(items: List['ItemData']) -> Iterable['ItemData']:
result_a = []
for _, group_a in itertools.groupby(sorted(items, key=lambda x: x.rarity, reverse=True), lambda x: x.rarity):
result_b = []
for _, group_b in itertools.groupby(
sorted(group_a, key=lambda x: x.level or -1, reverse=True), lambda x: x.level or -1
):
result_b.append(sorted(group_b, key=lambda x: x.constellation or x.refinement or -1, reverse=True))
result_a.append(itertools.chain(*result_b))
return itertools.chain(*result_a)
class DailyMaterial(Plugin, BasePlugin):
"""每日素材表"""
data: DATA_TYPE
locks: Tuple[Lock] = (Lock(), Lock())
def __init__(self, assets: AssetsService, template: TemplateService):
self.assets_service = assets
self.template_service = template
self.client = AsyncClient()
async def __async_init__(self):
data = None
if not DATA_FILE_PATH.exists():
async def task_daily():
async with self.locks[0]:
logger.info("正在开始获取每日素材缓存")
self.data = await self._refresh_data()
self.refresh_task = asyncio.create_task(task_daily())
if not data and DATA_FILE_PATH.exists():
async with async_open(DATA_FILE_PATH) as file:
data = json.loads(await file.read())
self.data = data
async def _get_data_from_user(self, user: User) -> Tuple[Optional[Client], Dict[str, List[Any]]]:
client = None
user_data = {'character': [], 'weapon': []}
try:
logger.debug("尝试获取已绑定的原神账号")
client = await get_genshin_client(user.id)
logger.debug(f"获取成功, UID: {client.uid}")
characters = await client.get_genshin_characters(client.uid)
for character in characters:
cid = HONEY_ROLE_NAME_MAP[character.id][0]
weapon = character.weapon
user_data['character'].append(
ItemData(
id=cid, name=character.name, rarity=character.rarity, level=character.level,
constellation=character.constellation,
icon=convert_path(await self.assets_service.character(cid).icon())
)
)
user_data['weapon'].append(
ItemData(
id=(wid := f"i_n{weapon.id}"), name=weapon.name, level=weapon.level, rarity=weapon.rarity,
refinement=weapon.refinement,
icon=convert_path(
await getattr(
self.assets_service.weapon(wid), 'icon' if weapon.ascension < 2 else 'awakened'
)()
),
c_path=convert_path(await self.assets_service.character(cid).side())
)
)
except (UserNotFoundError, CookiesNotFoundError):
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
return client, user_data
@handler.command('daily_material', block=False)
@restricts(restricts_time_of_groups=20, without_overlapping=True)
@error_callable
async def daily_material(self, update: Update, context: CallbackContext):
user = update.effective_user
args = get_all_args(context)
now = datetime.now()
if args and str(args[0]).isdigit():
weekday = int(args[0]) - 1
if weekday < 0:
weekday = 0
elif weekday > 6:
weekday = 6
time = title = f"星期{WEEK_MAP[weekday]}"
else: # 获取今日是星期几判定了是否过了凌晨4点
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]
full = args and args[-1] == 'full'
if weekday == 6:
notice = await update.message.reply_text(
("今天" if title == '今日' else '这天') + "是星期天, <b>全部素材都可以</b>刷哦~",
parse_mode=ParseMode.HTML
)
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
return
if self.locks[0].locked():
notice = await update.message.reply_text("派蒙正在摘抄每日素材表,以后再来探索吧~")
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
return
if self.locks[1].locked():
notice = await update.message.reply_text("派蒙正在搬运每日素材的图标,以后再来探索吧~")
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 5)
return
notice = await update.message.reply_text("派蒙可能需要找找图标素材,还请耐心等待哦~")
await update.message.reply_chat_action(ChatAction.TYPING)
# 获取已经缓存至本地的秘境素材信息
local_data = {'character': [], 'weapon': []}
if not self.data:
logger.info("正在获取每日素材缓存")
await self._refresh_data()
for domain, sche in self.data.items():
area = DOMAIN_AREA_MAP[domain]
type_ = 'character' if DOMAINS.index(domain) < 4 else 'weapon'
local_data[type_].append({'name': area, 'materials': sche[weekday][0], 'items': sche[weekday][1]})
# 尝试获取用户已绑定的原神账号信息
client, user_data = self._get_data_from_user(user)
await update.message.reply_chat_action(ChatAction.TYPING)
render_data = RenderData(title=title, time=time, uid=client.uid if client else client)
for type_ in ['character', 'weapon']:
areas = []
for area_data in local_data[type_]:
items = []
for id_ in area_data['items']:
added = False
for i in user_data[type_]:
if id_ == i.id:
if i.rarity > 3: # 跳过 3 星及以下的武器
items.append(i)
added = True
break
if added:
continue
item = HONEY_ID_MAP[type_][id_]
if item[1] < 4: # 跳过 3 星及以下的武器
continue
items.append(ItemData(
id=id_, name=item[0], rarity=item[1],
icon=convert_path(await getattr(self.assets_service, f'{type_}')(id_).icon())
))
materials = []
for mid in area_data['materials']:
path = convert_path(await self.assets_service.material(mid).icon())
material = HONEY_ID_MAP['material'][mid]
materials.append(ItemData(id=mid, icon=path, name=material[0], rarity=material[1]))
areas.append(AreaData(name=area_data['name'], materials=materials, items=sort_item(items)))
setattr(render_data, type_, areas)
await update.message.reply_chat_action(ChatAction.TYPING)
character_img_data = await self.template_service.render(
'genshin/daily_material', 'character.html', {'data': render_data}, {'width': 1164, 'height': 500}
)
weapon_img_data = await self.template_service.render(
'genshin/daily_material', 'weapon.html', {'data': render_data}, {'width': 1164, 'height': 500}
)
await update.message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
if full:
await update.message.reply_media_group([
InputMediaDocument(character_img_data, filename="可培养角色.png"),
InputMediaDocument(weapon_img_data, filename="可培养武器.png")
])
else:
await update.message.reply_media_group(
[InputMediaPhoto(character_img_data), InputMediaPhoto(weapon_img_data)]
)
await notice.delete()
@handler.command('refresh_daily_material', block=False)
@bot_admins_rights_check
async def refresh(self, update: Update, context: CallbackContext):
message = update.effective_message
if self.locks[0].locked():
notice = await message.reply_text("派蒙还在抄每日素材表呢,我有在好好工作哦~")
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 10)
return
if self.locks[1].locked():
notice = await message.reply_text("派蒙正在搬运每日素材图标,在努力工作呢!")
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 10)
return
async with self.locks[1]:
notice = await message.reply_text("派蒙正在重新摘抄每日素材表,请稍等~", parse_mode=ParseMode.HTML)
async with self.locks[0]:
data = await self._refresh_data()
notice = await notice.edit_text(
"每日素材表" +
("摘抄<b>完成!</b>" if data else "坏掉了!等会它再长好了之后我再抄。。。") +
'\n正搬运每日素材的图标中。。。',
parse_mode=ParseMode.HTML
)
self.data = data or self.data
await self._download_icon(notice)
notice = await notice.edit_text(
notice.text_html.split('\n')[0] + "\n每日素材图标搬运<b>完成!</b>",
parse_mode=ParseMode.HTML
)
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 10)
async def _refresh_data(self, retry: int = 5) -> DATA_TYPE:
from bs4 import Tag
from asyncio import sleep
result = {}
for i in range(retry):
try:
response = await self.client.get("https://genshin.honeyhunterworld.com/?lang=CHS")
soup = BeautifulSoup(response.text, 'lxml')
calendar = soup.select(".calendar_day_wrap")[0]
key: str = ''
for tag in calendar:
tag: Tag
if tag.name == 'span':
key = tag.find('a').text
result[key] = [[[], []] for _ in range(7)]
for day, div in enumerate(tag.find_all('div')):
result[key][day][0] = [re.findall(r"/(.*)?/", a['href'])[0] for a in div.find_all('a')]
else:
id_ = re.findall(r"/(.*)?/", tag['href'])[0]
if tag.text.strip() == '旅行者':
continue
for day in map(int, tag.find('div')['data-days']):
result[key][day][1].append(id_)
for stage, schedules in result.items():
for day, _ in enumerate(schedules):
result[stage][day][1] = list(set(result[stage][day][1]))
async with async_open(DATA_FILE_PATH, 'w', encoding='utf-8') as file:
await file.write(json.dumps(result)) # pylint: disable=PY-W0079
logger.info("每日素材刷新成功")
break
except HTTPError:
await sleep(1)
if i <= retry - 1:
logger.warning("每日素材刷新失败, 正在重试")
else:
logger.error("每日素材刷新失败, 请稍后重试")
continue
# noinspection PyTypeChecker
return result
async def _download_icon(self, message: Optional[Message] = None):
from time import time as time_
lock = asyncio.Lock()
the_time = Value(c_double, time_() - 1)
interval = 0.2
async def task(_id, _item, _type):
logger.debug(f"正在开始下载 \"{_item[0]}\" 的图标素材")
async with lock:
if message is not None and time_() >= the_time.value + interval:
text = '\n'.join(message.text_html.split('\n')[:2]) + f"\n正在搬运 <b>{_item[0]}</b> 的图标素材。。。"
try:
await message.edit_text(text, parse_mode=ParseMode.HTML)
except (TimedOut, RetryAfter):
pass
the_time.value = time_()
asset = getattr(self.assets_service, _type)(_id)
icon_types = list(filter(
lambda x: not x.startswith('_') and x not in ['path'] and callable(getattr(asset, x)),
dir(asset)
))
icon_coroutines = map(lambda x: getattr(asset, x), icon_types)
for coroutine in icon_coroutines:
await coroutine()
logger.debug(f"\"{_item[0]}\" 的图标素材下载成功")
async with lock:
if message is not None and time_() >= the_time.value + interval:
text = (
'\n'.join(message.text_html.split('\n')[:2]) +
f"\n正在搬运 <b>{_item[0]}</b> 的图标素材。。。<b>成功!</b>"
)
try:
await message.edit_text(text, parse_mode=ParseMode.HTML)
except (TimedOut, RetryAfter):
pass
the_time.value = time_()
for type_, items in HONEY_ID_MAP.items():
task_list = []
for id_, item in items.items():
task_list.append(asyncio.create_task(task(id_, item, type_)))
await asyncio.gather(*task_list)
logger.info("图标素材下载完成")
class ItemData(BaseModel):
id: str
name: str
rarity: int
icon: str
level: Optional[int] = None
constellation: Optional[int] = None
refinement: Optional[int] = None
c_path: Optional[str] = None
class AreaData(BaseModel):
name: Literal['蒙德', '璃月', '稻妻', '须弥']
materials: List[ItemData] = []
items: Iterable[ItemData] = []
class RenderData(BaseModel):
title: str
time: str
uid: Optional[int] = None
character: List[AreaData] = []
weapon: List[AreaData] = []
def __getitem__(self, item):
return self.__getattribute__(item)

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<!--suppress HtmlUnknownTag, SpellCheckingInspection -->
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>每日素材</title>
<link type="text/css" href="./style.css" rel="stylesheet"/>
</head>
<body>
<main class="container">
<div class="title">{{ data.title }}可培养角色</div>
<div class="user-info">
{% if data.uid != none %}
<span>UID: {{ data.uid }}</span>
{% else %}
<span>暂未绑定UID</span>
{% endif %}
<span>{{ data.time }}</span>
</div>
<hr/>
<div class="box">
{% for area in data.character %}
<div class="area">
<div class="area-head">
<img src="./bg/area/{{ loop.index0 }}.png" alt="{{ area.name }}"/>
<span>{{ area.name }}</span>
<span class="materials">
{% for material in area.materials %}
<div class="material">
<div class="material-icon"
style="background-image: url(./bg/rarity/half/{{ material.rarity }}.png)">
<img alt="{{ material.name }}" src="{{ material.icon }}"/>
</div>
<div class="material-star">
{% for _ in range(material.rarity) %}
<img alt="star" src="./bg/rarity/star.webp"/>
{% endfor %}
</div>
</div>
{% endfor %}
</span>
</div>
<div class="area-content">
{% for item in area.items %}
<div class="item character">
<div class="item-icon"
style="background-image: url(./bg/rarity/full/{{ item.rarity }}.png)">
{% if item.level != none %}
<div>Lv.{{ item.level }}</div>
{% endif %}
{% if item.constellation != none %}
{% if item.constellation == 6 %}
<div style="background-color: rgba(255,20,147, 0.8);backdrop-filter: blur(3px);">
6命
</div>
{% elif item.constellation != 0 %}
<div style="background-color: rgba(103,167,230, 0.8);backdrop-filter: blur(3px);">{{ item.constellation }}命</div>
{% endif %}
{% endif %}
<img src="{{ item.icon }}" alt="{{ item.name }}"/>
</div>
<div class="item-name">
<div>{{ item.name }}</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,316 @@
<!DOCTYPE html>
<!--suppress HtmlUnknownTag, SpellCheckingInspection -->
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>每日素材</title>
<link type="text/css" href="./style.css" rel="stylesheet"/>
</head>
<body>
<main class="container">
<div class="title">今日素材表</div>
<div class="user-info">
<span>UID: 100206192</span>
<span>09-15 01:05 星期四</span>
</div>
<hr/>
<div class="box">
<div class="title">角色培养素材</div>
<div class="area">
<div class="area-head">
<img src="./bg/area/0.png" alt="蒙德"/>
<span>蒙德</span>
<span class="materials">
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/2.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/3.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/4.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/5.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
</span>
</div>
<div class="area-content">
<div class="item character">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: rgba(255, 20, 147, 0.8); backdrop-filter: blur(3px);">6命</div>
<img src="../../assets/character/itto_057/icon.webp" alt="神里绫华"/>
</div>
<div class="item-name">
<div>神里绫华</div>
</div>
</div>
<div class="item character">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: rgba(255, 20, 147, 0.8); backdrop-filter: blur(3px);">6命</div>
<img src="../../assets/character/ayaka_002/icon.webp" alt="神里绫华"/>
</div>
<div class="item-name">
<div>神里绫华</div>
</div>
</div>
<div class="item character">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: rgba(255, 20, 147, 0.8); backdrop-filter: blur(3px);">6命</div>
<img src="../../assets/character/ayaka_002/icon.webp" alt="神里绫华"/>
</div>
<div class="item-name">
<div>神里绫华</div>
</div>
</div>
<div class="item character">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<img src="../../assets/character/ayaka_002/icon.webp" alt="神里绫华"/>
</div>
<div class="item-name">
<div>神里绫华</div>
</div>
</div>
<div class="item character">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<img src="../../assets/character/ayaka_002/icon.webp" alt="神里绫华"/>
</div>
<div class="item-name">
<div>神里绫华</div>
</div>
</div>
<div class="item character">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<img src="../../assets/character/ayaka_002/icon.webp" alt="神里绫华"/>
</div>
<div class="item-name">
<div>神里绫华</div>
</div>
</div>
</div>
</div>
<div class="area">
<div class="area-head">
<img src="./bg/area/0.png" alt="蒙德"/>
<span>蒙德</span>
<span class="materials">
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/3.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/4.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/5.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
</span>
</div>
<div class="area-content">
<div class="item character">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: rgba(255, 20, 147, 0.8); backdrop-filter: blur(3px);">6命</div>
<img src="../../assets/character/ayaka_002/icon.webp" alt="神里绫华"/>
</div>
<div class="item-name">
<div>神里绫华</div>
</div>
</div>
<div class="item character">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: rgba(255, 20, 147, 0.8); backdrop-filter: blur(3px);">6命</div>
<img src="../../assets/character/ayaka_002/icon.webp" alt="神里绫华"/>
</div>
<div class="item-name">
<div>神里绫华</div>
</div>
</div>
</div>
</div>
</div>
<hr/>
<div class="box">
<div class="title">武器培养素材</div>
<div class="area">
<div class="area-head">
<img src="./bg/area/0.png" alt="蒙德"/>
<span>蒙德</span>
<span class="materials">
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/2.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/3.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/4.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
<div class="material">
<div class="material-icon" style="background-image: url(./bg/rarity/half/5.png)">
<img alt="" src="../../assets/material/i_502.webp"/>
</div>
<div class="material-star">
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
<img alt="star" src="./bg/rarity/star.webp"/>
</div>
</div>
</span>
</div>
<div class="area-content">
<div class="item weapon">
<div class="role">
<img src="../../assets/character/shenhe_063/side.webp" alt="神里绫华"/>
</div>
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: deepskyblue">精炼5</div>
<img src="../../assets/weapon/i_n14501/awakened.webp" alt="天空之刃"/>
</div>
<div class="item-name">
<div>天空之刃</div>
</div>
</div>
<div class="item weapon">
<div class="role">
<img src="../../assets/character/mona_041/side.webp" alt="神里绫华"/>
</div>
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: deepskyblue">精炼5</div>
<img src="../../assets/weapon/i_n11502/awakened.webp" alt="天空之刃"/>
</div>
<div class="item-name">
<div>天空之刃</div>
</div>
</div>
<div class="item weapon">
<div class="role">
<img src="../../assets/character/collei_067/side.webp" alt="神里绫华"/>
</div>
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: deepskyblue">精炼5</div>
<img src="../../assets/weapon/i_n11502/awakened.webp" alt="天空之刃"/>
</div>
<div class="item-name">
<div>天空之刃</div>
</div>
</div>
<div class="item weapon">
<div class="role">
<img src="../../assets/character/ayaka_002/side.webp" alt="神里绫华"/>
</div>
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<div>Lv.90</div>
<div style="background-color: deepskyblue">精炼5</div>
<img src="../../assets/weapon/i_n11502/awakened.webp" alt="天空之刃"/>
</div>
<div class="item-name">
<div>天空之刃</div>
</div>
</div>
<div class="item weapon">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<img src="../../assets/weapon/i_n11502/awakened.webp" alt="天空之刃"/>
</div>
<div class="item-name">
<div>天空之刃</div>
</div>
</div>
<div class="item weapon">
<div class="item-icon" style="background-image: url(./bg/rarity/full/5.png)">
<img src="../../assets/weapon/i_n11502/awakened.webp" alt="天空之刃"/>
</div>
<div class="item-name">
<div>天空之刃</div>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,258 @@
@font-face {
font-family: 'HYWH';
src: url('../../fonts/汉仪文黑-85W.ttf') format('truetype');
}
:root {
--bg-color: #ebe5d9;
--font-color: #514e49;
--color-1: #d6ba92;
--shadow: #726c65;
}
* {
font-family: 'HYWH', serif;
color: var(--font-color);
background-repeat: no-repeat;
}
body {
margin: 0;
padding: 0;
}
.container {
width: 1104px;
background-color: var(--bg-color);
position: absolute;
left: 50%;
transform: translateX(-50%);
padding: 15px 30px;
}
.container > div.title {
width: 100%;
font-size: 40px;
text-align: center;
}
.user-info {
margin-top: 20px;
font-size: 25px;
border-left-color: var(--color-1);
border-left-style: solid;
border-left-width: 10px;
padding: 0;
font-style: italic;
}
.user-info > span:last-child {
float: right;
}
.box {
width: 100%;
margin: 10px 0;
}
.title {
font-size: 30px;
width: 100%;
text-align: center;
position: relative;
margin: 20px 0;
}
.area {
width: calc(100% - 20px);
padding: 0 10px;
}
.area-head {
background-image: url("bg/title/01.png");
background-size: contain;
width: calc(100% - 20px);
height: 80px;
filter: drop-shadow(5px 5px 5px var(--shadow));
position: relative;
padding-left: 15px;
}
.area-head > img {
height: inherit;
filter: drop-shadow(3px 3px 5px black);
}
.area-head > span {
font-size: 23px;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.materials {
width: 335px;
height: inherit;
right: 0;
display: flex;
align-items: center;
justify-content: center;
background-image: url("bg/title/02.png");
background-size: 100% 100%;
position: relative;
top: 50%;
transform: translateY(-50%);
}
.material {
position: relative;
top: 5px;
width: 60px;
margin: 0 5px;
filter: drop-shadow(1px 1px 5px #838383);
}
.material-icon {
width: 60px;
height: 60px;
background-size: 100% auto;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
border-radius: 5px;
}
.material-icon > img {
height: calc(100% - 10px);
}
.material-star {
position: relative;
top: -5px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
filter: drop-shadow(1px 1px 2px #6c6c6c);
}
.material-star > img {
width: 10px;
}
.area-content {
margin: 20px 0;
padding: 0 20px;
display: flex;
flex-flow: wrap;
}
.item {
width: 150px;
background-color: var(--bg-color);
border-radius: 10px;
box-shadow: 3px 3px 10px var(--shadow);
margin: 10px 12px;
justify-content: center;
}
.item-icon {
width: inherit;
height: 152px;
background-size: 104% auto;
background-position: center;
overflow: hidden;
border-radius: 10px 10px 35px 0;
position: relative;
}
.item-icon > img {
max-height: 100%;
max-width: 150px;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
.item-name {
text-align: center;
display: flex;
align-items: center;
height: 35px;
}
.item-name > div {
width: 100%;
font-size: 22px;
text-align: center;
}
.item > .item-icon > div {
position: absolute;
z-index: 1;
color: white;
}
.item > .item-icon > div:first-child {
background-color: rgba(100, 149, 237, 0.7);
backdrop-filter: blur(3px);
border-radius: 0 0 5px 0;
box-shadow: 1px 1px 5px var(--shadow);
left: 0;
font-size: 18px;
padding: 4px 5px;
}
.character {
overflow: hidden;
}
.character > .item-icon > div:nth-child(2) {
border-radius: 0 0 0 5px;
box-shadow: -1px 1px 5px var(--shadow);
right: 0;
font-size: 20px;
padding: 3px;
}
.weapon > .item-icon > div:nth-child(2) {
border-radius: 0 5px 0 0;
box-shadow: -1px -1px 5px var(--shadow);
bottom: 0;
font-size: 18px;
padding: 3px;
}
.weapon {
overflow: unset;
position: relative;
}
.weapon > .role {
background-color: rgb(76 82 107 / 80%);
backdrop-filter: blur(3px);
position: absolute;
right: -10px;
top: -10px;
z-index: 2;
width: 30px;
height: 30px;
padding: 5px;
border-radius: 100%;
border-style: solid;
border-width: 3px;
border-color: white;
}
.weapon > .role > img {
height: 50px;
position: absolute;
left: 58%;
transform: translateX(-50%);
bottom: 0;
}

View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<!--suppress HtmlUnknownTag, SpellCheckingInspection -->
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>每日素材</title>
<link type="text/css" href="./style.css" rel="stylesheet"/>
</head>
<body>
<main class="container">
<div class="title">{{ data.title }}可培养武器</div>
<div class="user-info">
{% if data.uid != none %}
<span>UID: {{ data.uid }}</span>
{% else %}
<span>暂未绑定UID</span>
{% endif %}
<span>{{ data.time }}</span>
</div>
<hr/>
<div class="box">
{% for area in data.weapon %}
<div class="area">
<div class="area-head">
<img src="./bg/area/{{ loop.index0 }}.png" alt="{{ area.name }}"/>
<span>{{ area.name }}</span>
<span class="materials">
{% for material in area.materials %}
<div class="material">
<div class="material-icon"
style="background-image: url(./bg/rarity/half/{{ material.rarity }}.png)">
<img alt="{{ material.name }}" src="{{ material.icon }}"/>
</div>
<div class="material-star">
{% for _ in range(material.rarity) %}
<img alt="star" src="./bg/rarity/star.webp"/>
{% endfor %}
</div>
</div>
{% endfor %}
</span>
</div>
<div class="area-content">
{% for item in area.items %}
<div class="item weapon">
{% if item.c_path != none %}
<div class="role">
<img src="{{ item.c_path }}" alt=""/>
</div>
{% endif %}
<div class="item-icon"
style="background-image: url(./bg/rarity/full/{{ item.rarity }}.png)">
{% if item.level != none %}
<div>Lv.{{ item.level }}</div>
{% endif %}
{% if item.refinement != none %}
{% if item.refinement == 5 %}
<div style="background-color: rgba(251,86,33, 0.8);backdrop-filter: blur(3px);">
精炼5
</div>
{% else %}
<div style="background-color: rgba(103,167,230, 0.8);backdrop-filter: blur(3px);">
精炼{{ item.refinement }}</div>
{% endif %}
{% endif %}
<img src="{{ item.icon }}" alt="{{ item.name }}"/>
</div>
<div class="item-name">
<div>{{ item.name }}</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</main>
</body>
</html>

View File

@ -102,6 +102,12 @@ class TestMaterial:
assert '合成获得' in material.source
assert '巴巴托斯' in material.description
material = await Material.get_by_id('i_483')
assert material.name == '凶将之手眼'
assert material.type == '角色培养素材'
assert '70级以上永恒的守护者挑战奖励' in material.source
assert '所见即所为' in material.description
@staticmethod
@flaky(3, 1)
async def test_get_by_name():
@ -111,6 +117,12 @@ class TestMaterial:
assert '60级以上深渊法师掉落' in material.source
assert '勃发' in material.description
material = await Material.get_by_name('「黄金」的教导')
assert material.id == 'i_431'
assert material.type == '天赋培养素材'
assert 2 in material.weekdays
assert '土的象' in material.description
@staticmethod
@flaky(3, 1)
async def test_name_list():

View File

@ -2,7 +2,7 @@
from pathlib import Path
__all__ = [
'PROJECT_ROOT', 'PLUGIN_DIR',
'PROJECT_ROOT', 'PLUGIN_DIR', 'RESOURCE_DIR',
'NOT_SET',
]
@ -10,5 +10,7 @@ __all__ = [
PROJECT_ROOT = Path(__file__).joinpath('../..').resolve()
# 插件目录
PLUGIN_DIR = PROJECT_ROOT / 'plugins'
# 资源目录
RESOURCE_DIR = PROJECT_ROOT / 'resources'
NOT_SET = object()

View File

@ -1,7 +1,9 @@
import asyncio
import hashlib
import os
from multiprocessing import RLock
from pathlib import Path
from typing import Tuple, Union, Optional, cast
from typing import Optional, Tuple, Union, cast
import aiofiles
import genshin
@ -130,3 +132,42 @@ def region_server(uid: Union[int, str]) -> RegionEnum:
return region
else:
raise TypeError(f"UID {uid} isn't associated with any region")
def mkdir(path: Path) -> Path:
"""根据路径依次创建文件夹"""
path_list = []
parent = path.parent if path.suffix else path
while not parent.exists():
path_list.append(parent)
try:
parent.mkdir(exist_ok=True)
except FileNotFoundError:
parent = parent.parent
while path_list:
path_list.pop().mkdir(exist_ok=True)
return path
class Event:
"""一个线程安装的事件对象"""
_event: asyncio.Event = asyncio.Event()
_lock = RLock()
async def wait(self) -> bool:
return await self._event.wait()
def set(self):
with self._lock:
self._event.set()
def clear(self):
with self._lock:
self._event.clear()
def is_set(self) -> bool:
with self._lock:
return self._event.is_set()

View File

@ -1,14 +1,17 @@
from pathlib import Path
from types import TracebackType
from typing import Optional, Tuple, Type, Union, Dict, Any
from typing import Any, Dict, Optional, Tuple, Type, Union
from httpx import URL
__all__ = [
'StrOrPath',
'StrOrPath', 'StrOrURL',
'SysExcInfoType', 'ExceptionInfoType',
'JSONDict',
]
StrOrPath = Union[str, Path]
StrOrURL = Union[str, URL]
SysExcInfoType = Union[
Tuple[Type[BaseException], BaseException, Optional[TracebackType]],
Tuple[None, None, None]