✨ 添加查看今日素材表功能
8
core/assets/__init__.py
Normal 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
@ -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)
|
58
core/bot.py
@ -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
@ -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']
|
||||
}
|
@ -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)
|
||||
|
@ -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:
|
||||
|
0
plugins/genshin/daily/__init__.py
Normal file
367
plugins/genshin/daily/material.py
Normal 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)
|
BIN
resources/genshin/daily_material/bg/area/0.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
resources/genshin/daily_material/bg/area/1.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
resources/genshin/daily_material/bg/area/2.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
resources/genshin/daily_material/bg/area/3.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
BIN
resources/genshin/daily_material/bg/rarity/full/1.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
resources/genshin/daily_material/bg/rarity/full/2.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
resources/genshin/daily_material/bg/rarity/full/3.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
resources/genshin/daily_material/bg/rarity/full/4.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
resources/genshin/daily_material/bg/rarity/full/5.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
resources/genshin/daily_material/bg/rarity/half/1.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
resources/genshin/daily_material/bg/rarity/half/2.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
resources/genshin/daily_material/bg/rarity/half/3.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
resources/genshin/daily_material/bg/rarity/half/4.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
resources/genshin/daily_material/bg/rarity/half/5.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
resources/genshin/daily_material/bg/rarity/star.webp
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
resources/genshin/daily_material/bg/title/01.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
resources/genshin/daily_material/bg/title/02.png
Normal file
After Width: | Height: | Size: 47 KiB |
74
resources/genshin/daily_material/character.html
Normal 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>
|
316
resources/genshin/daily_material/example.html
Normal 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>
|
258
resources/genshin/daily_material/style.css
Normal 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;
|
||||
}
|
80
resources/genshin/daily_material/weapon.html
Normal 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>
|
@ -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():
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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]
|
||||
|