mirror of
https://github.com/PaiGramTeam/PamGram.git
synced 2024-11-24 07:10:36 +00:00
✨ Enhance AssetsService
Co-authored-by: xtaodada <xtao@xtaolink.cn>
This commit is contained in:
parent
e32a4c6e99
commit
4c702515a0
@ -1,8 +0,0 @@
|
||||
from core.service import init_service
|
||||
from core.assets.service import AssetsService
|
||||
|
||||
|
||||
@init_service
|
||||
def create_wiki_service():
|
||||
_service = AssetsService()
|
||||
return _service
|
@ -1,174 +0,0 @@
|
||||
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.log import logger
|
||||
from utils.typedefs import StrOrURL
|
||||
|
||||
ASSETS_PATH = PROJECT_ROOT.joinpath('resources/assets')
|
||||
ASSETS_PATH.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
|
||||
class _AssetsService(ABC):
|
||||
_dir: ClassVar[Path]
|
||||
|
||||
id: str
|
||||
type: str
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
path = self._dir.joinpath(self.id)
|
||||
path.mkdir(exist_ok=True, parents=True)
|
||||
return path
|
||||
|
||||
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
|
||||
|
||||
async def _task():
|
||||
logger.debug(f"正在从 {url} 下载图标至 {path}")
|
||||
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
|
||||
|
||||
task = asyncio.create_task(_task())
|
||||
while not task.done():
|
||||
await asyncio.sleep(0)
|
||||
return task.result()
|
||||
|
||||
@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)
|
513
core/base/assets.py
Normal file
513
core/base/assets.py
Normal file
@ -0,0 +1,513 @@
|
||||
"""用于下载和管理角色、武器、材料等的图标"""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import cached_property, partial
|
||||
from multiprocessing import RLock as Lock
|
||||
from pathlib import Path
|
||||
from ssl import SSLZeroReturnError
|
||||
from typing import Awaitable, Callable, ClassVar, Dict, Optional, TYPE_CHECKING, TypeVar, Union
|
||||
|
||||
from aiofiles import open as async_open
|
||||
from aiofiles.os import remove as async_remove
|
||||
from enkanetwork import Assets as EnkaAssets
|
||||
from enkanetwork.model.assets import CharacterAsset as EnkaCharacterAsset
|
||||
from httpx import AsyncClient, HTTPError, URL
|
||||
from typing_extensions import Self
|
||||
|
||||
from core.service import Service
|
||||
from metadata.genshin import AVATAR_DATA, HONEY_DATA, MATERIAL_DATA, NAMECARD_DATA, WEAPON_DATA
|
||||
from metadata.scripts.honey import update_honey_metadata
|
||||
from metadata.scripts.metadatas import update_metadata_from_ambr, update_metadata_from_github
|
||||
from metadata.shortname import roleToId, weaponToId
|
||||
from modules.wiki.base import HONEY_HOST
|
||||
from utils.const import AMBR_HOST, ENKA_HOST, PROJECT_ROOT
|
||||
from utils.log import logger
|
||||
from utils.typedefs import StrOrInt, StrOrURL
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from multiprocessing.synchronize import RLock
|
||||
ICON_TYPE = Union[
|
||||
Callable[[bool], Awaitable[Optional[Path]]],
|
||||
Callable[..., Awaitable[Optional[Path]]]
|
||||
]
|
||||
NAME_MAP_TYPE = Dict[str, StrOrURL]
|
||||
|
||||
ASSETS_PATH = PROJECT_ROOT.joinpath('resources/assets')
|
||||
ASSETS_PATH.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
DATA_MAP = {'avatar': AVATAR_DATA, 'weapon': WEAPON_DATA, 'material': MATERIAL_DATA}
|
||||
|
||||
|
||||
class AssetsServiceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetsCouldNotFound(AssetsServiceError):
|
||||
pass
|
||||
|
||||
|
||||
class _AssetsService(ABC):
|
||||
_lock: ClassVar['RLock'] = Lock()
|
||||
_dir: ClassVar[Path]
|
||||
icon_types: ClassVar[list[str]]
|
||||
|
||||
_client: Optional[AsyncClient] = None
|
||||
|
||||
id: int
|
||||
type: str
|
||||
|
||||
icon: ICON_TYPE
|
||||
"""图标"""
|
||||
|
||||
@abstractmethod
|
||||
@cached_property
|
||||
def game_name(self) -> str:
|
||||
"""游戏数据中的名称"""
|
||||
|
||||
@cached_property
|
||||
def honey_id(self) -> str:
|
||||
"""当前资源在 Honey Impact 所对应的 ID"""
|
||||
return HONEY_DATA[self.type].get(str(self.id), [''])[0]
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
"""当前资源的文件夹"""
|
||||
result = self._dir.joinpath(str(self.id)).resolve()
|
||||
result.mkdir(exist_ok=True, parents=True)
|
||||
return result
|
||||
|
||||
@property
|
||||
def client(self) -> AsyncClient:
|
||||
with self._lock:
|
||||
if self._client is None or self._client.is_closed:
|
||||
self._client = AsyncClient()
|
||||
return self._client
|
||||
|
||||
def __init__(self, client: Optional[AsyncClient] = None) -> None:
|
||||
self._client = client
|
||||
|
||||
def __call__(self, target: int) -> Self:
|
||||
"""用于生成与 target 对应的 assets"""
|
||||
result = self.__class__(self.client)
|
||||
result.id = target
|
||||
return result
|
||||
|
||||
def __init_subclass__(cls, **kwargs) -> None:
|
||||
"""初始化一些类变量"""
|
||||
from itertools import chain
|
||||
cls.icon_types = [ # 支持的图标类型
|
||||
k
|
||||
for k, v in
|
||||
chain(
|
||||
cls.__annotations__.items(),
|
||||
*map(
|
||||
lambda x: x.__annotations__.items(),
|
||||
cls.__bases__
|
||||
)
|
||||
)
|
||||
if v in [ICON_TYPE, 'ICON_TYPE']
|
||||
]
|
||||
cls.type = cls.__name__.lstrip('_').split('Assets')[0].lower() # 当前 assert 的类型
|
||||
cls._dir = ASSETS_PATH.joinpath(cls.type) # 图标保存的文件夹
|
||||
cls._dir.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
async def _download(self, url: StrOrURL, path: Path, retry: int = 5) -> Path | None:
|
||||
"""从 url 下载图标至 path"""
|
||||
logger.debug(f"正在从 {url} 下载图标至 {path}")
|
||||
headers = {'user-agent': 'TGPaimonBot/3.0'} if URL(url).host == 'enka.network' else None
|
||||
for time in range(retry):
|
||||
try:
|
||||
response = await self.client.get(url, follow_redirects=False, headers=headers)
|
||||
except Exception as error: # pylint: disable=W0703
|
||||
if not isinstance(error, (HTTPError, SSLZeroReturnError)):
|
||||
logger.error(error) # 打印未知错误
|
||||
if time != retry - 1: # 未达到重试次数
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
raise error
|
||||
continue
|
||||
if response.status_code != 200: # 判定页面是否正常
|
||||
return None
|
||||
async with async_open(path, 'wb') as file:
|
||||
await file.write(response.content) # 保存图标
|
||||
return path.resolve()
|
||||
|
||||
async def _get_from_ambr(self, item: str) -> Path | None: # pylint: disable=W0613,R0201
|
||||
return None
|
||||
|
||||
async def _get_from_enka(self, item: str) -> Path | None: # pylint: disable=W0613,R0201
|
||||
return None
|
||||
|
||||
async def _get_from_honey(self, item: str) -> Path | None:
|
||||
"""从 honey 获取图标"""
|
||||
if (url := self.honey_name_map.get(item, None)) is not None:
|
||||
# 先尝试下载 png 格式的图片
|
||||
path = self.path.joinpath(f"{item}.png")
|
||||
if (result := await self._download(HONEY_HOST.join(f"img/{url}.png"), path)) is not None:
|
||||
return result
|
||||
path = self.path.joinpath(f"{item}.webp")
|
||||
return await self._download(HONEY_HOST.join(f"img/{url}.webp"), path)
|
||||
|
||||
async def _get_img(self, overwrite: bool = False, *, item: str) -> Path | None:
|
||||
"""获取图标"""
|
||||
path = next(filter(lambda x: x.stem == item, self.path.iterdir()), None)
|
||||
if not overwrite and path:
|
||||
return path.resolve()
|
||||
if overwrite and path is not None and path.exists():
|
||||
await async_remove(path)
|
||||
# 依次从使用当前 assets class 中的爬虫下载图标,顺序为爬虫名的字母顺序
|
||||
for func in map(lambda x: getattr(self, x), sorted(filter(lambda x: x.startswith('_get_from_'), dir(self)))):
|
||||
if (path := await func(item)) is not None:
|
||||
return path
|
||||
|
||||
def __getattr__(self, item: str):
|
||||
"""魔法"""
|
||||
if item in self.icon_types:
|
||||
return partial(self._get_img, item=item)
|
||||
else:
|
||||
object.__getattribute__(self, item)
|
||||
|
||||
@abstractmethod
|
||||
@cached_property
|
||||
def game_name_map(self) -> dict[str, str]:
|
||||
"""游戏中的图标名"""
|
||||
|
||||
@abstractmethod
|
||||
@cached_property
|
||||
def honey_name_map(self) -> dict[str, str]:
|
||||
"""来自honey的图标名"""
|
||||
|
||||
|
||||
class _AvatarAssets(_AssetsService):
|
||||
enka: EnkaCharacterAsset | None
|
||||
|
||||
side: ICON_TYPE
|
||||
"""侧视图图标"""
|
||||
|
||||
card: ICON_TYPE
|
||||
"""卡片图标"""
|
||||
|
||||
gacha: ICON_TYPE
|
||||
"""抽卡立绘"""
|
||||
|
||||
gacha_card: ICON_TYPE
|
||||
"""抽卡卡片"""
|
||||
|
||||
@cached_property
|
||||
def game_name(self) -> str:
|
||||
icon = "UI_AvatarIcon_"
|
||||
if (avatar := AVATAR_DATA.get(str(self.id), None)) is not None:
|
||||
icon = avatar['icon']
|
||||
else:
|
||||
for aid, avatar in AVATAR_DATA.items():
|
||||
if aid.startswith(str(self.id)):
|
||||
icon = avatar['icon']
|
||||
return re.findall(r"UI_AvatarIcon_(.*)", icon)[0]
|
||||
|
||||
@cached_property
|
||||
def honey_id(self) -> str:
|
||||
return HONEY_DATA['avatar'].get(str(self.id), '')[0]
|
||||
|
||||
@cached_property
|
||||
def enka(self) -> Optional[EnkaCharacterAsset]:
|
||||
api = getattr(self, '_enka_api', None)
|
||||
cid = getattr(self, 'id', None)
|
||||
return None if api is None or cid is None else api.character(cid)
|
||||
|
||||
def __init__(self, client: Optional[AsyncClient] = None, enka: Optional[EnkaAssets] = None):
|
||||
super().__init__(client)
|
||||
self._enka_api = enka or EnkaAssets()
|
||||
|
||||
def __call__(self, target: StrOrInt) -> "_AvatarAssets":
|
||||
temp = target
|
||||
result = _AvatarAssets(self.client)
|
||||
if isinstance(target, str):
|
||||
try:
|
||||
target = int(target)
|
||||
except ValueError:
|
||||
target = roleToId(target)
|
||||
if isinstance(target, str) or target is None:
|
||||
raise AssetsCouldNotFound(f"找不到对应的角色: target={temp}")
|
||||
result.id = target
|
||||
result._enka_api = self._enka_api
|
||||
return result
|
||||
|
||||
async def _get_from_ambr(self, item: str) -> Path | None:
|
||||
if item in {'icon', 'side', 'gacha'}:
|
||||
url = AMBR_HOST.join(f"assets/UI/{self.game_name_map[item]}.png")
|
||||
return await self._download(url, self.path.joinpath(f"{item}.png"))
|
||||
|
||||
async def _get_from_enka(self, item: str) -> Path | None:
|
||||
path = self.path.joinpath(f"{item}.png")
|
||||
item = 'banner' if item == 'gacha' else item
|
||||
# noinspection PyUnboundLocalVariable
|
||||
if (
|
||||
self.enka is not None
|
||||
and
|
||||
item in (data := self.enka.images.dict()).keys()
|
||||
and
|
||||
(url := data[item]['url'])
|
||||
):
|
||||
return await self._download(url, path)
|
||||
|
||||
@cached_property
|
||||
def honey_name_map(self) -> dict[str, str]:
|
||||
return {
|
||||
'icon': f"{self.honey_id}_icon",
|
||||
'side': f"{self.honey_id}_side_icon",
|
||||
'gacha': f"{self.honey_id}_gacha_splash",
|
||||
'gacha_card': f"{self.honey_id}_gacha_card",
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def game_name_map(self) -> dict[str, str]:
|
||||
return {
|
||||
'icon': f"UI_AvatarIcon_{self.game_name}",
|
||||
'card': f"UI_AvatarIcon_{self.game_name}_Card",
|
||||
'side': f"UI_AvatarIcon_Side_{self.game_name}",
|
||||
'gacha': f"UI_Gacha_AvatarImg_{self.game_name}",
|
||||
}
|
||||
|
||||
|
||||
class _WeaponAssets(_AssetsService):
|
||||
awaken: ICON_TYPE
|
||||
"""突破后图标"""
|
||||
|
||||
gacha: ICON_TYPE
|
||||
"""抽卡立绘"""
|
||||
|
||||
@cached_property
|
||||
def game_name(self) -> str:
|
||||
return re.findall(r"UI_EquipIcon_(.*)", WEAPON_DATA[str(self.id)]['icon'])[0]
|
||||
|
||||
@cached_property
|
||||
def game_name_map(self) -> dict[str, str]:
|
||||
return {
|
||||
'icon': f"UI_EquipIcon_{self.game_name}",
|
||||
'awaken': f"UI_EquipIcon_{self.game_name}_Awaken",
|
||||
'gacha': f"UI_Gacha_EquipIcon_{self.game_name}"
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def honey_id(self) -> str:
|
||||
return f"i_n{self.id}"
|
||||
|
||||
def __call__(self, target: StrOrInt) -> Self:
|
||||
temp = target
|
||||
result = _WeaponAssets(self.client)
|
||||
if isinstance(target, str):
|
||||
target = int(target) if target.isnumeric() else weaponToId(target)
|
||||
if isinstance(target, str) or target is None:
|
||||
raise AssetsCouldNotFound(f"找不到对应的武器: target={temp}")
|
||||
result.id = target
|
||||
return result
|
||||
|
||||
async def _get_from_enka(self, item: str) -> Path | None:
|
||||
if item in self.game_name_map:
|
||||
url = ENKA_HOST.join(f'ui/{self.game_name_map.get(item)}.png')
|
||||
path = self.path.joinpath(f"{item}.png")
|
||||
return await self._download(url, path)
|
||||
|
||||
@cached_property
|
||||
def honey_name_map(self) -> dict[str, str]:
|
||||
return {
|
||||
'icon': f'{self.honey_id}',
|
||||
'awaken': f'{self.honey_id}_awaken_icon',
|
||||
'gacha': f'{self.honey_id}_gacha_icon',
|
||||
}
|
||||
|
||||
|
||||
class _MaterialAssets(_AssetsService):
|
||||
@cached_property
|
||||
def game_name(self) -> str:
|
||||
return str(self.id)
|
||||
|
||||
@cached_property
|
||||
def game_name_map(self) -> dict[str, str]:
|
||||
return {'icon': f"UI_ItemIcon_{self.game_name}"}
|
||||
|
||||
@cached_property
|
||||
def honey_name_map(self) -> dict[str, str]:
|
||||
return {'icon': self.honey_id}
|
||||
|
||||
def __call__(self, target: StrOrInt) -> Self:
|
||||
temp = target
|
||||
result = _MaterialAssets(self.client)
|
||||
if isinstance(target, str):
|
||||
if target.isnumeric():
|
||||
target = int(target)
|
||||
else:
|
||||
target = {v['name']: int(k) for k, v in MATERIAL_DATA.items()}.get(target)
|
||||
if isinstance(target, str) or target is None:
|
||||
raise AssetsCouldNotFound(f"找不到对应的素材: target={temp}")
|
||||
result.id = target
|
||||
return result
|
||||
|
||||
async def _get_from_ambr(self, item: str) -> Path | None:
|
||||
if item == 'icon':
|
||||
url = AMBR_HOST.join(f"assets/UI/{self.game_name_map.get(item)}.png")
|
||||
path = self.path.joinpath(f"{item}.png")
|
||||
return await self._download(url, path)
|
||||
|
||||
async def _get_from_honey(self, item: str) -> Path | None:
|
||||
path = self.path.joinpath(f"{item}.png")
|
||||
url = HONEY_HOST.join(f'/img/{self.honey_name_map.get(item)}.png')
|
||||
if (result := await self._download(url, path)) is None:
|
||||
path = self.path.joinpath(f"{item}.webp")
|
||||
url = HONEY_HOST.join(f'/img/{self.honey_name_map.get(item)}.webp')
|
||||
return await self._download(url, path)
|
||||
return result
|
||||
|
||||
|
||||
class _ArtifactAssets(_AssetsService):
|
||||
flower: ICON_TYPE
|
||||
"""生之花"""
|
||||
|
||||
plume: ICON_TYPE
|
||||
"""死之羽"""
|
||||
|
||||
sands: ICON_TYPE
|
||||
"""时之沙"""
|
||||
|
||||
goblet: ICON_TYPE
|
||||
"""空之杯"""
|
||||
|
||||
circlet: ICON_TYPE
|
||||
"""理之冠"""
|
||||
|
||||
@cached_property
|
||||
def honey_id(self) -> str:
|
||||
return HONEY_DATA['artifact'][str(self.id)][0]
|
||||
|
||||
@cached_property
|
||||
def game_name(self) -> str:
|
||||
return f"UI_RelicIcon_{self.id}"
|
||||
|
||||
async def _get_from_enka(self, item: str) -> Path | None:
|
||||
if item in self.game_name_map:
|
||||
url = ENKA_HOST.join(f'ui/{self.game_name_map.get(item)}.png')
|
||||
path = self.path.joinpath(f"{item}.png")
|
||||
return await self._download(url, path)
|
||||
|
||||
async def _get_from_ambr(self, item: str) -> Path | None:
|
||||
if item in self.game_name_map:
|
||||
url = AMBR_HOST.join(f"assets/UI/reliquary/{self.game_name_map[item]}.png")
|
||||
return await self._download(url, self.path.joinpath(f"{item}.png"))
|
||||
|
||||
@cached_property
|
||||
def game_name_map(self) -> dict[str, str]:
|
||||
return {
|
||||
"icon": f"UI_RelicIcon_{self.id}_4",
|
||||
"flower": f"UI_RelicIcon_{self.id}_4",
|
||||
"plume": f"UI_RelicIcon_{self.id}_2",
|
||||
"sands": f"UI_RelicIcon_{self.id}_5",
|
||||
"goblet": f"UI_RelicIcon_{self.id}_1",
|
||||
"circlet": f"UI_RelicIcon_{self.id}_3",
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def honey_name_map(self) -> dict[str, str]:
|
||||
first_id = int(re.findall(r'\d+', HONEY_DATA['artifact'][str(self.id)][-1])[0])
|
||||
return {
|
||||
"icon": f"i_n{first_id + 30}",
|
||||
"flower": f"i_n{first_id + 30}",
|
||||
"plume": f"i_n{first_id + 10}",
|
||||
"sands": f"i_n{first_id + 40}",
|
||||
"goblet": f"i_n{first_id}",
|
||||
"circlet": f"i_n{first_id + 20}",
|
||||
}
|
||||
|
||||
|
||||
class _NamecardAssets(_AssetsService):
|
||||
enka: EnkaCharacterAsset | None
|
||||
|
||||
navbar: ICON_TYPE
|
||||
"""好友名片背景"""
|
||||
|
||||
profile: ICON_TYPE
|
||||
"""个人资料名片背景"""
|
||||
|
||||
@cached_property
|
||||
def honey_id(self) -> str:
|
||||
return HONEY_DATA['namecard'][str(self.id)][0]
|
||||
|
||||
@cached_property
|
||||
def game_name(self) -> str:
|
||||
return NAMECARD_DATA[str(self.id)]['icon']
|
||||
|
||||
def __call__(self, target: int) -> "_NamecardAssets":
|
||||
result = _NamecardAssets(self.client)
|
||||
result.id = target
|
||||
result.enka = EnkaAssets().namecards(target)
|
||||
return result
|
||||
|
||||
async def _get_from_ambr(self, item: str) -> Path | None:
|
||||
if item == 'profile':
|
||||
url = AMBR_HOST.join(f"assets/UI/namecard/{self.game_name_map[item]}.png.png")
|
||||
return await self._download(url, self.path.joinpath(f"{item}.png"))
|
||||
|
||||
async def _get_from_enka(self, item: str) -> Path | None:
|
||||
path = self.path.joinpath(f"{item}.png")
|
||||
url = getattr(self.enka, {'profile': 'banner'}.get(item, item), None)
|
||||
if url is not None:
|
||||
return await self._download(url.url, path)
|
||||
|
||||
@cached_property
|
||||
def game_name_map(self) -> dict[str, str]:
|
||||
return {
|
||||
'icon': self.game_name,
|
||||
'navbar': NAMECARD_DATA[str(self.id)]['navbar'],
|
||||
'profile': NAMECARD_DATA[str(self.id)]['profile']
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def honey_name_map(self) -> dict[str, str]:
|
||||
return {
|
||||
'icon': self.honey_id,
|
||||
'navbar': f"{self.honey_id}_back",
|
||||
'profile': f"{self.honey_id}_profile",
|
||||
}
|
||||
|
||||
|
||||
class AssetsService(Service):
|
||||
"""asset服务
|
||||
|
||||
用于储存和管理 asset :
|
||||
当对应的 asset (如某角色图标)不存在时,该服务会先查找本地。
|
||||
若本地不存在,则从网络上下载;若存在,则返回其路径
|
||||
"""
|
||||
|
||||
avatar: _AvatarAssets
|
||||
"""角色"""
|
||||
|
||||
weapon: _WeaponAssets
|
||||
"""武器"""
|
||||
|
||||
material: _MaterialAssets
|
||||
"""素材"""
|
||||
|
||||
artifact: _ArtifactAssets
|
||||
"""圣遗物"""
|
||||
|
||||
namecard: _NamecardAssets
|
||||
"""名片"""
|
||||
|
||||
def __init__(self):
|
||||
for attr, assets_type_name in filter(
|
||||
lambda x: (not x[0].startswith('_')) and x[1].endswith('Assets'),
|
||||
self.__annotations__.items()
|
||||
):
|
||||
setattr(self, attr, globals()[assets_type_name]())
|
||||
|
||||
async def start(self): # pylint: disable=R0201
|
||||
logger.info("正在刷新元数据")
|
||||
await update_metadata_from_github(False)
|
||||
await update_metadata_from_ambr(False)
|
||||
await update_honey_metadata(False)
|
||||
logger.info("刷新元数据成功")
|
||||
|
||||
|
||||
AssetsServiceType = TypeVar('AssetsServiceType', bound=_AssetsService)
|
42
metadata/genshin.py
Normal file
42
metadata/genshin.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""此文件用于储存 honey impact 中的部分基础数据"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ujson as json
|
||||
|
||||
from utils.const import PROJECT_ROOT
|
||||
from utils.log import logger
|
||||
from utils.typedefs import JSONType, StrOrInt
|
||||
|
||||
__all__ = [
|
||||
'HONEY_DATA',
|
||||
'AVATAR_DATA', 'WEAPON_DATA', 'MATERIAL_DATA', 'ARTIFACT_DATA', 'NAMECARD_DATA',
|
||||
'honey_id_to_game_id'
|
||||
]
|
||||
|
||||
data_dir = PROJECT_ROOT.joinpath('metadata/data/')
|
||||
|
||||
|
||||
def _get_content(file_name: str) -> JSONType:
|
||||
path = data_dir.joinpath(file_name).with_suffix('.json')
|
||||
if not path.exists():
|
||||
logger.error(
|
||||
"暂未找到名为 \"{file_name}.json\" 的 metadata , 请先使用 [yellow bold]/refresh_metadata[/] 命令下载",
|
||||
extra={'markup': True}
|
||||
)
|
||||
return {}
|
||||
with open(path, encoding='utf-8') as file:
|
||||
return json.load(file)
|
||||
|
||||
|
||||
HONEY_DATA: dict[str, dict[StrOrInt, list[str | int]]] = _get_content('honey')
|
||||
|
||||
AVATAR_DATA: dict[str, dict[str, int | str | list[int]]] = _get_content('avatar')
|
||||
WEAPON_DATA: dict[str, dict[str, int | str]] = _get_content('weapon')
|
||||
MATERIAL_DATA: dict[str, dict[str, int | str]] = _get_content('material')
|
||||
ARTIFACT_DATA: dict[str, dict[str, int | str | list[int] | dict[str, str]]] = _get_content('reliquary')
|
||||
NAMECARD_DATA: dict[str, dict[str, int | str]] = _get_content('namecard')
|
||||
|
||||
|
||||
def honey_id_to_game_id(honey_id: str, item_type: str) -> str | None:
|
||||
return next((key for key, value in HONEY_DATA[item_type].items() if value[0] == honey_id), None)
|
@ -1,464 +0,0 @@
|
||||
"""此文件用于储存 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']
|
||||
}
|
188
metadata/scripts/honey.py
Normal file
188
metadata/scripts/honey.py
Normal file
@ -0,0 +1,188 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import ujson as json
|
||||
from aiofiles import open as async_open
|
||||
from httpx import AsyncClient, HTTPError, Response
|
||||
|
||||
from modules.wiki.base import HONEY_HOST
|
||||
from utils.const import PROJECT_ROOT
|
||||
from utils.log import logger
|
||||
from utils.typedefs import StrOrInt
|
||||
|
||||
__all__ = [
|
||||
'get_avatar_data', 'get_artifact_data', 'get_material_data', 'get_namecard_data', 'get_weapon_data',
|
||||
'update_honey_metadata',
|
||||
]
|
||||
|
||||
DATA_TYPE = Dict[StrOrInt, List[str]]
|
||||
FULL_DATA_TYPE = Dict[str, DATA_TYPE]
|
||||
|
||||
client = AsyncClient()
|
||||
|
||||
|
||||
async def request(url: str, retry: int = 5) -> Optional[Response]:
|
||||
for time in range(retry):
|
||||
try:
|
||||
return await client.get(url)
|
||||
except HTTPError:
|
||||
if time != retry - 1:
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
return None
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
async def get_avatar_data() -> DATA_TYPE:
|
||||
result = {}
|
||||
url = "https://genshin.honeyhunterworld.com/fam_chars/?lang=CHS"
|
||||
response = await request(url)
|
||||
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0]
|
||||
json_data = json.loads(chaos_data) # 转为 json
|
||||
for data in json_data:
|
||||
cid = int("10000" + re.findall(r'\d+', data[1])[0])
|
||||
honey_id = re.findall(r"/(.*?)/", data[1])[0]
|
||||
name = re.findall(r'>(.*)<', data[1])[0]
|
||||
rarity = int(re.findall(r">(\d)<", data[2])[0])
|
||||
result[cid] = [honey_id, name, rarity]
|
||||
return result
|
||||
|
||||
|
||||
async def get_weapon_data() -> DATA_TYPE:
|
||||
from modules.wiki.other import WeaponType
|
||||
|
||||
result = {}
|
||||
urls = [HONEY_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__]
|
||||
for url in urls:
|
||||
response = await request(url)
|
||||
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0]
|
||||
json_data = json.loads(chaos_data) # 转为 json
|
||||
for data in json_data:
|
||||
name = re.findall(r'>(.*)<', data[1])[0]
|
||||
if name in ['「一心传」名刀', '石英大剑', '琥珀玥', '黑檀弓']: # 跳过特殊的武器
|
||||
continue
|
||||
wid = int(re.findall(r'\d+', data[1])[0])
|
||||
honey_id = re.findall(r"/(.*?)/", data[1])[0]
|
||||
rarity = int(re.findall(r">(\d)<", data[2])[0])
|
||||
result[wid] = [honey_id, name, rarity]
|
||||
return result
|
||||
|
||||
|
||||
async def get_material_data() -> DATA_TYPE:
|
||||
result = {}
|
||||
|
||||
weapon = [HONEY_HOST.join(f'fam_wep_{i}/?lang=CHS') for i in ['primary', 'secondary', 'common']]
|
||||
talent = [HONEY_HOST.join(f'fam_talent_{i}/?lang=CHS') for i in ['book', 'boss', 'common', 'reward']]
|
||||
namecard = [HONEY_HOST.join("fam_nameplate/?lang=CHS")]
|
||||
urls = weapon + talent + namecard
|
||||
|
||||
response = await request("https://api.ambr.top/v2/chs/material")
|
||||
ambr_data = json.loads(response.text)['data']['items']
|
||||
|
||||
for url in urls:
|
||||
response = await request(url)
|
||||
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0]
|
||||
json_data = json.loads(chaos_data) # 转为 json
|
||||
for data in json_data:
|
||||
honey_id = re.findall(r'/(.*?)/', data[1])[0]
|
||||
name = re.findall(r'>(.*)<', data[1])[0]
|
||||
rarity = int(re.findall(r">(\d)<", data[2])[0])
|
||||
mid = None
|
||||
for mid, item in ambr_data.items():
|
||||
if name == item['name']:
|
||||
break
|
||||
mid = int(mid) or int(re.findall(r'\d+', data[1])[0])
|
||||
result[mid] = [honey_id, name, rarity]
|
||||
return result
|
||||
|
||||
|
||||
async def get_artifact_data() -> DATA_TYPE:
|
||||
async def get_first_id(_link) -> str:
|
||||
_response = await request(_link)
|
||||
_chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', _response.text)[0]
|
||||
_json_data = json.loads(_chaos_data)
|
||||
return re.findall(r"/(.*?)/", _json_data[-1][1])[0]
|
||||
|
||||
result = {}
|
||||
url = "https://genshin.honeyhunterworld.com/fam_art_set/?lang=CHS"
|
||||
|
||||
response = await request("https://api.ambr.top/v2/chs/reliquary")
|
||||
ambr_data = json.loads(response.text)['data']['items']
|
||||
|
||||
response = await request(url)
|
||||
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0]
|
||||
json_data = json.loads(chaos_data) # 转为 json
|
||||
for data in json_data:
|
||||
honey_id = re.findall(r'/(.*?)/', data[1])[0]
|
||||
name = re.findall(r"alt=\"(.*?)\"", data[0])[0]
|
||||
link = HONEY_HOST.join(re.findall(r'href="(.*?)"', data[0])[0])
|
||||
first_id = await get_first_id(link)
|
||||
aid = None
|
||||
for aid, item in ambr_data.items():
|
||||
if name == item['name']:
|
||||
break
|
||||
aid = aid or re.findall(r'\d+', data[1])[0]
|
||||
result[aid] = [honey_id, name, first_id]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_namecard_data() -> DATA_TYPE:
|
||||
from metadata.genshin import NAMECARD_DATA
|
||||
|
||||
if not NAMECARD_DATA:
|
||||
# noinspection PyProtectedMember
|
||||
from metadata.genshin import _get_content
|
||||
from metadata.scripts.metadatas import update_metadata_from_github
|
||||
await update_metadata_from_github()
|
||||
# noinspection PyPep8Naming
|
||||
NAMECARD_DATA = _get_content('namecard')
|
||||
url = HONEY_HOST.join("fam_nameplate/?lang=CHS")
|
||||
result = {}
|
||||
|
||||
response = await request(url)
|
||||
chaos_data = re.findall(r'sortable_data\.push\((.*)\);\s*sortable_cur_page', response.text)[0]
|
||||
json_data = json.loads(chaos_data)
|
||||
for data in json_data:
|
||||
honey_id = re.findall(r'/(.*?)/', data[1])[0]
|
||||
name = re.findall(r"alt=\"(.*?)\"", data[0])[0]
|
||||
try:
|
||||
nid = [key for key, value in NAMECARD_DATA.items() if value['name'] == name][0]
|
||||
except IndexError: # 暂不支持 beta 的名片
|
||||
continue
|
||||
rarity = int(re.findall(r">(\d)<", data[2])[0])
|
||||
result[nid] = [honey_id, name, rarity]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def update_honey_metadata(overwrite: bool = True) -> FULL_DATA_TYPE | None:
|
||||
path = PROJECT_ROOT.joinpath('metadata/data/honey.json')
|
||||
if not overwrite and path.exists():
|
||||
return
|
||||
avatar_data = await get_avatar_data()
|
||||
logger.success("Avatar data is done.")
|
||||
weapon_data = await get_weapon_data()
|
||||
logger.success("Weapon data is done.")
|
||||
material_data = await get_material_data()
|
||||
logger.success("Material data is done.")
|
||||
artifact_data = await get_artifact_data()
|
||||
logger.success("Artifact data is done.")
|
||||
namecard_data = await get_namecard_data()
|
||||
logger.success("Namecard data is done.")
|
||||
|
||||
result = {
|
||||
'avatar': avatar_data,
|
||||
'weapon': weapon_data,
|
||||
'material': material_data,
|
||||
'artifact': artifact_data,
|
||||
'namecard': namecard_data,
|
||||
}
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
async with async_open(path, mode='w', encoding='utf-8') as file:
|
||||
await file.write(json.dumps(result, ensure_ascii=False))
|
||||
return result
|
65
metadata/scripts/metadatas.py
Normal file
65
metadata/scripts/metadatas.py
Normal file
@ -0,0 +1,65 @@
|
||||
import ujson as json
|
||||
from aiofiles import open as async_open
|
||||
from httpx import AsyncClient, URL
|
||||
|
||||
from utils.const import AMBR_HOST, PROJECT_ROOT
|
||||
|
||||
__all__ = ['update_metadata_from_ambr', 'update_metadata_from_github']
|
||||
|
||||
client = AsyncClient()
|
||||
|
||||
|
||||
async def update_metadata_from_ambr(overwrite: bool = True):
|
||||
result = []
|
||||
targets = ['material', 'weapon', 'avatar', 'reliquary']
|
||||
for target in targets:
|
||||
path = PROJECT_ROOT.joinpath(f'metadata/data/{target}.json')
|
||||
if not overwrite and path.exists():
|
||||
continue
|
||||
url = AMBR_HOST.join(f"v2/chs/{target}")
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
response = await client.get(url)
|
||||
json_data = json.loads(response.text)['data']['items']
|
||||
async with async_open(path, mode='w', encoding='utf-8') as file:
|
||||
data = json.dumps(json_data, ensure_ascii=False)
|
||||
await file.write(data)
|
||||
result.append(json_data)
|
||||
return result
|
||||
|
||||
|
||||
async def update_metadata_from_github(overwrite: bool = True):
|
||||
path = PROJECT_ROOT.joinpath('metadata/data/namecard.json')
|
||||
if not overwrite and path.exists():
|
||||
return
|
||||
|
||||
host = URL("https://raw.fastgit.org/Dimbreath/GenshinData/master/")
|
||||
|
||||
text_map_url = host.join("TextMap/TextMapCHS.json")
|
||||
material_url = host.join("ExcelBinOutput/MaterialExcelConfigData.json")
|
||||
|
||||
text_map_json_data = json.loads((await client.get(text_map_url)).text)
|
||||
material_json_data = json.loads((await client.get(material_url)).text)
|
||||
|
||||
data = {}
|
||||
for namecard_data in filter(lambda x: x.get('materialType', None) == 'MATERIAL_NAMECARD', material_json_data):
|
||||
name = text_map_json_data[str(namecard_data['nameTextMapHash'])]
|
||||
icon = namecard_data['icon']
|
||||
navbar = namecard_data['picPath'][0]
|
||||
banner = namecard_data['picPath'][1]
|
||||
rank = namecard_data['rankLevel']
|
||||
description = text_map_json_data[str(namecard_data['descTextMapHash'])].replace('\\n', '\n')
|
||||
data.update({
|
||||
str(namecard_data['id']): {
|
||||
"id": namecard_data['id'],
|
||||
"name": name,
|
||||
"rank": rank,
|
||||
"icon": icon,
|
||||
"navbar": navbar,
|
||||
"profile": banner,
|
||||
"description": description,
|
||||
}
|
||||
})
|
||||
async with async_open(path, mode='w', encoding='utf-8') as file:
|
||||
data = json.dumps(data, ensure_ascii=False)
|
||||
await file.write(data)
|
||||
return data
|
@ -1,8 +1,10 @@
|
||||
from typing import Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from metadata.genshin import WEAPON_DATA
|
||||
|
||||
__all__ = [
|
||||
'roles', 'weapons',
|
||||
'roleToId', 'roleToName', 'weaponToName',
|
||||
'roleToId', 'roleToName', 'weaponToName', 'weaponToId'
|
||||
]
|
||||
|
||||
# noinspection SpellCheckingInspection
|
||||
@ -89,7 +91,8 @@ roles = {
|
||||
10000067: ['柯莱', 'Collei', 'collei', '柯来', '科莱', '科来', '小天使', '须弥安柏', '须弥飞行冠军', '见习巡林员',
|
||||
'克莱', '草安伯'],
|
||||
10000068: ['多莉', 'Dori', 'dori', '多利', '多力', '多丽', '奸商'],
|
||||
10000069: ['提纳里', 'Tighnari', 'tighnari', '小提', '提那里', '缇娜里', '提哪里', '驴', '柯莱老师', '柯莱师傅', '巡林官',
|
||||
10000069: ['提纳里', 'Tighnari', 'tighnari', '小提', '提那里', '缇娜里', '提哪里', '驴', '柯莱老师', '柯莱师傅',
|
||||
'巡林官',
|
||||
'提那里'],
|
||||
10000070: ['妮露', 'Nilou', 'nilou', '尼露', '尼禄'],
|
||||
10000071: ['赛诺', 'Cyno', 'cyno', '赛洛'],
|
||||
@ -109,7 +112,7 @@ weapons = {
|
||||
"松籁响起之时": ["松籁", "乐团大剑", "松剑"],
|
||||
"苍古自由之誓": ["苍古", "乐团剑"],
|
||||
|
||||
"渔获": ["鱼叉"],
|
||||
"「渔获」": ["鱼叉", "渔叉"],
|
||||
"衔珠海皇": ["海皇", "咸鱼剑", "咸鱼大剑"],
|
||||
|
||||
"匣里日月": ["日月"],
|
||||
@ -185,7 +188,7 @@ def roleToName(shortname: str) -> str:
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
def roleToId(name: str) -> Optional[int]:
|
||||
def roleToId(name: str) -> int | None:
|
||||
"""获取角色ID"""
|
||||
return next((key for key, value in roles.items() for n in value if n == name), None)
|
||||
|
||||
@ -194,3 +197,9 @@ def roleToId(name: str) -> Optional[int]:
|
||||
def weaponToName(shortname: str) -> str:
|
||||
"""讲武器昵称转为正式名"""
|
||||
return next((key for key, value in weapons.items() if shortname == key or shortname in value), shortname)
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
def weaponToId(name: str) -> int | None:
|
||||
"""获取武器ID"""
|
||||
return next((int(key) for key, value in WEAPON_DATA.items() if weaponToName(name) in value['name']), None)
|
||||
|
@ -2,7 +2,7 @@ import time
|
||||
from typing import List, Optional
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel, validator, parse_obj_as
|
||||
from pydantic import BaseModel, parse_obj_as, validator
|
||||
|
||||
|
||||
class Member(BaseModel):
|
||||
|
@ -16,9 +16,9 @@ from pydantic import (
|
||||
)
|
||||
from typing_extensions import Self
|
||||
|
||||
__all__ = ['Model', 'WikiModel', 'SCRAPE_HOST']
|
||||
__all__ = ['Model', 'WikiModel', 'HONEY_HOST']
|
||||
|
||||
SCRAPE_HOST = URL("https://genshin.honeyhunterworld.com/")
|
||||
HONEY_HOST = URL("https://genshin.honeyhunterworld.com/")
|
||||
|
||||
|
||||
class Model(PydanticBaseModel):
|
||||
@ -130,10 +130,7 @@ class WikiModel(Model):
|
||||
返回对应的 WikiModel
|
||||
"""
|
||||
url = await cls.get_url_by_name(name)
|
||||
if url is None:
|
||||
return None
|
||||
else:
|
||||
return await cls._scrape(url)
|
||||
return None if url is None else await cls._scrape(url)
|
||||
|
||||
@classmethod
|
||||
async def get_full_data(cls) -> List[Self]:
|
||||
@ -186,7 +183,7 @@ class WikiModel(Model):
|
||||
Returns:
|
||||
返回对应的 url
|
||||
"""
|
||||
return SCRAPE_HOST.join(f"{id_}/?lang=CHS")
|
||||
return HONEY_HOST.join(f"{id_}/?lang=CHS")
|
||||
|
||||
@classmethod
|
||||
async def _name_list_generator(cls, *, with_url: bool = False) -> AsyncIterator[Union[str, Tuple[str, URL]]]:
|
||||
@ -210,7 +207,7 @@ class WikiModel(Model):
|
||||
for data in json_data: # 遍历 json
|
||||
data_name = re.findall(r'>(.*)<', data[1])[0] # 获取 Model 的名称
|
||||
if with_url: # 如果需要返回对应的 url
|
||||
data_url = SCRAPE_HOST.join(re.findall(r'\"(.*?)\"', data[0])[0])
|
||||
data_url = HONEY_HOST.join(re.findall(r'\"(.*?)\"', data[0])[0])
|
||||
await queue.put((data_name, data_url))
|
||||
else:
|
||||
await queue.put(data_name)
|
||||
|
@ -4,7 +4,7 @@ from typing import List, Optional
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
|
||||
from modules.wiki.base import Model, SCRAPE_HOST
|
||||
from modules.wiki.base import Model, HONEY_HOST
|
||||
from modules.wiki.base import WikiModel
|
||||
from modules.wiki.other import Association, Element, WeaponType
|
||||
|
||||
@ -94,7 +94,7 @@ class Character(WikiModel):
|
||||
|
||||
@classmethod
|
||||
def scrape_urls(cls) -> List[URL]:
|
||||
return [SCRAPE_HOST.join("fam_chars/?lang=CHS")]
|
||||
return [HONEY_HOST.join("fam_chars/?lang=CHS")]
|
||||
|
||||
@classmethod
|
||||
async def _parse_soup(cls, soup: BeautifulSoup) -> 'Character':
|
||||
@ -170,8 +170,8 @@ class Character(WikiModel):
|
||||
@property
|
||||
def icon(self) -> CharacterIcon:
|
||||
return CharacterIcon(
|
||||
icon=str(SCRAPE_HOST.join(f'/img/{self.id}_icon.webp')),
|
||||
side=str(SCRAPE_HOST.join(f'/img/{self.id}_side_icon.webp')),
|
||||
gacha=str(SCRAPE_HOST.join(f'/img/{self.id}_gacha_card.webp')),
|
||||
splash=str(SCRAPE_HOST.join(f'/img/{self.id}_gacha_splash.webp'))
|
||||
icon=str(HONEY_HOST.join(f'/img/{self.id}_icon.webp')),
|
||||
side=str(HONEY_HOST.join(f'/img/{self.id}_side_icon.webp')),
|
||||
gacha=str(HONEY_HOST.join(f'/img/{self.id}_gacha_card.webp')),
|
||||
splash=str(HONEY_HOST.join(f'/img/{self.id}_gacha_splash.webp'))
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ from typing import List, Optional, Tuple, Union
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
|
||||
from modules.wiki.base import SCRAPE_HOST, WikiModel
|
||||
from modules.wiki.base import HONEY_HOST, WikiModel
|
||||
|
||||
__all__ = ['Material']
|
||||
|
||||
@ -28,8 +28,8 @@ class Material(WikiModel):
|
||||
|
||||
@staticmethod
|
||||
def scrape_urls() -> List[URL]:
|
||||
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']]
|
||||
weapon = [HONEY_HOST.join(f'fam_wep_{i}/?lang=CHS') for i in ['primary', 'secondary', 'common']]
|
||||
talent = [HONEY_HOST.join(f'fam_talent_{i}/?lang=CHS') for i in ['book', 'boss', 'common', 'reward']]
|
||||
return weapon + talent
|
||||
|
||||
@classmethod
|
||||
@ -77,4 +77,4 @@ class Material(WikiModel):
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return str(SCRAPE_HOST.join(f'/img/{self.id}.webp'))
|
||||
return str(HONEY_HOST.join(f'/img/{self.id}.webp'))
|
||||
|
@ -1,85 +0,0 @@
|
||||
{
|
||||
"504": {
|
||||
"city": "Mondstadt",
|
||||
"name": "高塔孤王",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_504.png"
|
||||
},
|
||||
"524": {
|
||||
"city": "Mondstadt",
|
||||
"name": "凛风奔狼",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_524.png"
|
||||
},
|
||||
"544": {
|
||||
"city": "Mondstadt",
|
||||
"name": "狮牙斗士",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_544.png"
|
||||
},
|
||||
"514": {
|
||||
"city": "Liyue",
|
||||
"name": "孤云寒林",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"Key": "Weapon_Guyun",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_514.png"
|
||||
},
|
||||
"534": {
|
||||
"city": "Liyue",
|
||||
"name": "雾海云间",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_534.png"
|
||||
},
|
||||
"554": {
|
||||
"city": "Liyue",
|
||||
"name": "漆黑陨铁",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_554.png"
|
||||
},
|
||||
"564": {
|
||||
"city": "Inazuma",
|
||||
"name": "远海夷地",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"Key": "Weapon_DistantSea",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_564.png"
|
||||
},
|
||||
"574": {
|
||||
"city": "Inazuma",
|
||||
"name": "鸣神御灵",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_574.png"
|
||||
},
|
||||
"584": {
|
||||
"city": "Inazuma",
|
||||
"name": "今昔剧画",
|
||||
"star": {
|
||||
"value": 5,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||
},
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_584.png"
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
{
|
||||
"63": {
|
||||
"name": "号角",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_Horn",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_63.png"
|
||||
},
|
||||
"73": {
|
||||
"name": "地脉",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_LeyLine",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_73.png"
|
||||
},
|
||||
"83": {
|
||||
"name": "混沌",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_Chaos",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_83.png"
|
||||
},
|
||||
"93": {
|
||||
"name": "雾虚",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_MistGrass",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_93.png"
|
||||
},
|
||||
"103": {
|
||||
"name": "祭刀",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_SacrificialKnife",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_103.png"
|
||||
},
|
||||
"143": {
|
||||
"name": "骨片",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_BoneShard",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_143.png"
|
||||
},
|
||||
"153": {
|
||||
"name": "刻像",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_Statuette",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_153.png"
|
||||
},
|
||||
"173": {
|
||||
"name": "混沌2",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_Chaos2",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_173.png"
|
||||
},
|
||||
"176": {
|
||||
"name": "隐兽",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_Concealed",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_176.png"
|
||||
},
|
||||
"183": {
|
||||
"name": "棱镜",
|
||||
"star": {
|
||||
"value": 4,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||
},
|
||||
"key": "Elite_Prism",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_183.png"
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
{
|
||||
"23": {
|
||||
"name": "史莱姆",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"key": "Monster_Slime",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_23.png"
|
||||
},
|
||||
"33": {
|
||||
"name": "面具",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"Key": "Monster_Mask",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_33.png"
|
||||
},
|
||||
"43": {
|
||||
"name": "绘卷",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"Key": "Monster_Scroll",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_43.png"
|
||||
},
|
||||
"53": {
|
||||
"name": "箭簇",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"Key": "Monster_Scroll",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_43.png"
|
||||
},
|
||||
"113": {
|
||||
"name": "徽记",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"Key": "Monster_Insignia",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_113.png"
|
||||
},
|
||||
"123": {
|
||||
"name": "鸦印",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"Key": "Monster_RavenInsignia",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_123.png"
|
||||
},
|
||||
"133": {
|
||||
"name": "花蜜",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"Key": "Monster_Nectar",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_133.png"
|
||||
},
|
||||
"163": {
|
||||
"name": "刀镡",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"Key": "Monster_Handguard",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_163.png"
|
||||
},
|
||||
"187": {
|
||||
"name": "浮游",
|
||||
"star": {
|
||||
"value": 3,
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||
},
|
||||
"Key": "Monster_Spectral",
|
||||
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_187.png"
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ from typing import Optional
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from modules.wiki.base import SCRAPE_HOST
|
||||
from modules.wiki.base import HONEY_HOST
|
||||
|
||||
__all__ = [
|
||||
'Element',
|
||||
@ -26,11 +26,11 @@ class Element(Enum):
|
||||
|
||||
|
||||
_WEAPON_ICON_MAP = {
|
||||
'Sword': SCRAPE_HOST.join('img/s_23101.png'),
|
||||
'Claymore': SCRAPE_HOST.join('img/s_163101.png'),
|
||||
'Polearm': SCRAPE_HOST.join('img/s_233101.png'),
|
||||
'Catalyst': SCRAPE_HOST.join('img/s_43101.png'),
|
||||
'Bow': SCRAPE_HOST.join('img/s_213101.png'),
|
||||
'Sword': HONEY_HOST.join('img/s_23101.png'),
|
||||
'Claymore': HONEY_HOST.join('img/s_163101.png'),
|
||||
'Polearm': HONEY_HOST.join('img/s_233101.png'),
|
||||
'Catalyst': HONEY_HOST.join('img/s_43101.png'),
|
||||
'Bow': HONEY_HOST.join('img/s_213101.png'),
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Union
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
|
||||
from modules.wiki.base import Model, SCRAPE_HOST, WikiModel
|
||||
from modules.wiki.base import Model, HONEY_HOST, WikiModel
|
||||
from modules.wiki.other import AttributeType, WeaponType
|
||||
|
||||
__all__ = ['Weapon', 'WeaponAffix', 'WeaponAttribute']
|
||||
@ -45,7 +45,7 @@ class Weapon(WikiModel):
|
||||
"""武器
|
||||
|
||||
Attributes:
|
||||
type: 武器类型
|
||||
weapon_type: 武器类型
|
||||
attack: 基础攻击力
|
||||
attribute:
|
||||
affix: 武器技能
|
||||
@ -66,7 +66,7 @@ class Weapon(WikiModel):
|
||||
|
||||
@staticmethod
|
||||
def scrape_urls() -> List[URL]:
|
||||
return [SCRAPE_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__]
|
||||
return [HONEY_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__]
|
||||
|
||||
@classmethod
|
||||
async def _parse_soup(cls, soup: BeautifulSoup) -> 'Weapon':
|
||||
@ -99,10 +99,7 @@ class Weapon(WikiModel):
|
||||
affix = WeaponAffix(name=get_table_text(7), description=[
|
||||
i.find_all('td')[1].text for i in tables[3].find_all('tr')[1:]
|
||||
])
|
||||
if len(tables) < 11:
|
||||
description = get_table_text(-1)
|
||||
else:
|
||||
description = get_table_text(9)
|
||||
description = get_table_text(-1) if len(tables) < 11 else get_table_text(9)
|
||||
if story_table := find_table('quotes'):
|
||||
story = story_table[0].text.strip()
|
||||
else:
|
||||
@ -137,7 +134,7 @@ class Weapon(WikiModel):
|
||||
@property
|
||||
def icon(self) -> WeaponIcon:
|
||||
return WeaponIcon(
|
||||
icon=str(SCRAPE_HOST.join(f'/img/{self.id}.webp')),
|
||||
awakened=str(SCRAPE_HOST.join(f'/img/{self.id}_awaken_icon.webp')),
|
||||
gacha=str(SCRAPE_HOST.join(f'/img/{self.id}_gacha_icon.webp')),
|
||||
icon=str(HONEY_HOST.join(f'/img/{self.id}.webp')),
|
||||
awakened=str(HONEY_HOST.join(f'/img/{self.id}_awaken_icon.webp')),
|
||||
gacha=str(HONEY_HOST.join(f'/img/{self.id}_gacha_icon.webp')),
|
||||
)
|
||||
|
@ -5,6 +5,7 @@ from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
|
||||
|
||||
from core.base.assets import AssetsService
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.cookies.services import CookiesService
|
||||
@ -14,7 +15,7 @@ from core.user import UserService
|
||||
from core.user.error import UserNotFoundError
|
||||
from utils.decorators.error import error_callable
|
||||
from utils.decorators.restricts import restricts
|
||||
from utils.helpers import get_genshin_client, url_to_file, get_public_genshin_client
|
||||
from utils.helpers import get_genshin_client, get_public_genshin_client
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@ -31,11 +32,17 @@ class NoMostKills(Exception):
|
||||
class Abyss(Plugin, BasePlugin):
|
||||
"""深渊数据查询"""
|
||||
|
||||
def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None):
|
||||
def __init__(
|
||||
self,
|
||||
user_service: UserService = None,
|
||||
cookies_service: CookiesService = None,
|
||||
template_service: TemplateService = None,
|
||||
assets_service: AssetsService = None
|
||||
):
|
||||
self.template_service = template_service
|
||||
self.cookies_service = cookies_service
|
||||
self.user_service = user_service
|
||||
self.assets_service = assets_service
|
||||
|
||||
@staticmethod
|
||||
def _get_role_star_bg(value: int):
|
||||
@ -65,23 +72,23 @@ class Abyss(Plugin, BasePlugin):
|
||||
"total_stars": spiral_abyss_info.total_stars,
|
||||
"most_played_list": [],
|
||||
"most_kills": {
|
||||
"icon": await url_to_file(ranks.most_kills[0].side_icon),
|
||||
"icon": await self.assets_service.avatar(ranks.most_kills[0].id).side(),
|
||||
"value": ranks.most_kills[0].value,
|
||||
},
|
||||
"strongest_strike": {
|
||||
"icon": await url_to_file(ranks.strongest_strike[0].side_icon),
|
||||
"icon": await self.assets_service.avatar(ranks.strongest_strike[0].id).side(),
|
||||
"value": ranks.strongest_strike[0].value
|
||||
},
|
||||
"most_damage_taken": {
|
||||
"icon": await url_to_file(ranks.most_damage_taken[0].side_icon),
|
||||
"icon": await self.assets_service.avatar(ranks.most_damage_taken[0].id).side(),
|
||||
"value": ranks.most_damage_taken[0].value
|
||||
},
|
||||
"most_bursts_used": {
|
||||
"icon": await url_to_file(ranks.most_bursts_used[0].side_icon),
|
||||
"icon": await self.assets_service.avatar(ranks.most_bursts_used[0].id).side(),
|
||||
"value": ranks.most_bursts_used[0].value
|
||||
},
|
||||
"most_skills_used": {
|
||||
"icon": await url_to_file(ranks.most_skills_used[0].side_icon),
|
||||
"icon": await self.assets_service.avatar(ranks.most_skills_used[0].id).side(),
|
||||
"value": ranks.most_skills_used[0].value
|
||||
}
|
||||
}
|
||||
@ -89,7 +96,7 @@ class Abyss(Plugin, BasePlugin):
|
||||
most_played_list = ranks.most_played
|
||||
for most_played in most_played_list:
|
||||
temp = {
|
||||
"icon": await url_to_file(most_played.icon),
|
||||
"icon": await self.assets_service.avatar(most_played.id).icon(),
|
||||
"value": most_played.value,
|
||||
"background": self._get_role_star_bg(most_played.rarity)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ from telegram import Update, User
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
|
||||
|
||||
from core.assets import AssetsService
|
||||
from core.base.assets import AssetsService
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.cookies.error import CookiesNotFoundError
|
||||
from core.plugin import Plugin, handler
|
||||
@ -67,7 +67,7 @@ class AbyssTeam(Plugin, BasePlugin):
|
||||
}
|
||||
for i in team_data.rateListUp[0].formation:
|
||||
temp = {
|
||||
"icon": (await self.assets_service.character(roleToId(i.name)).icon()).as_uri(),
|
||||
"icon": (await self.assets_service.avatar(roleToId(i.name)).icon()).as_uri(),
|
||||
"name": i.name,
|
||||
"background": self._get_role_star_bg(i.star),
|
||||
"hava": i.name in user_data,
|
||||
@ -75,7 +75,7 @@ class AbyssTeam(Plugin, BasePlugin):
|
||||
abyss_team_data["up"].append(temp)
|
||||
for i in team_data.rateListDown[0].formation:
|
||||
temp = {
|
||||
"icon": (await self.assets_service.character(roleToId(i.name)).icon()).as_uri(),
|
||||
"icon": (await self.assets_service.avatar(roleToId(i.name)).icon()).as_uri(),
|
||||
"name": i.name,
|
||||
"background": self._get_role_star_bg(i.star),
|
||||
"hava": i.name in user_data,
|
||||
|
@ -1,8 +1,10 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
import re
|
||||
from asyncio import Lock
|
||||
from ctypes import c_double
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from multiprocessing import Value
|
||||
from pathlib import Path
|
||||
from ssl import SSLZeroReturnError
|
||||
@ -20,13 +22,13 @@ from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.error import RetryAfter, TimedOut
|
||||
from telegram.ext import CallbackContext
|
||||
|
||||
from core.assets import AssetsService
|
||||
from core.base.assets import AssetsService, AssetsServiceType
|
||||
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 metadata.genshin import AVATAR_DATA, HONEY_DATA
|
||||
from utils.bot import get_all_args
|
||||
from utils.decorators.admins import bot_admins_rights_check
|
||||
from utils.decorators.error import error_callable
|
||||
@ -34,6 +36,8 @@ from utils.decorators.restricts import restricts
|
||||
from utils.helpers import get_genshin_client
|
||||
from utils.log import logger
|
||||
|
||||
INTERVAL = 1
|
||||
|
||||
DATA_TYPE = Dict[str, List[List[str]]]
|
||||
DATA_FILE_PATH = Path(__file__).joinpath('../daily.json').resolve()
|
||||
DOMAINS = ['忘却之峡', '太山府', '菫色之庭', '昏识塔', '塞西莉亚苗圃', '震雷连山密宫', '砂流之庭', '有顶塔']
|
||||
@ -118,30 +122,32 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
async def _get_data_from_user(self, user: User) -> Tuple[Optional[Client], Dict[str, List[Any]]]:
|
||||
"""获取已经绑定的账号的角色、武器信息"""
|
||||
client = None
|
||||
user_data = {'character': [], 'weapon': []}
|
||||
user_data = {'avatar': [], 'weapon': []}
|
||||
try:
|
||||
logger.debug("尝试获取已绑定的原神账号")
|
||||
client = await get_genshin_client(user.id)
|
||||
logger.debug(f"获取成功, UID: {client.uid}")
|
||||
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]
|
||||
if character.name == '旅行者': # 跳过主角
|
||||
continue
|
||||
cid = AVATAR_DATA[str(character.id)]['id']
|
||||
weapon = character.weapon
|
||||
user_data['character'].append(
|
||||
user_data['avatar'].append(
|
||||
ItemData(
|
||||
id=cid, name=character.name, rarity=character.rarity, level=character.level,
|
||||
constellation=character.constellation,
|
||||
icon=(await self.assets_service.character(cid).icon()).as_uri()
|
||||
icon=(await self.assets_service.avatar(cid).icon()).as_uri()
|
||||
)
|
||||
)
|
||||
user_data['weapon'].append(
|
||||
ItemData(
|
||||
id=(wid := f"i_n{weapon.id}"), name=weapon.name, level=weapon.level, rarity=weapon.rarity,
|
||||
id=str(weapon.id), name=weapon.name, level=weapon.level, rarity=weapon.rarity,
|
||||
refinement=weapon.refinement,
|
||||
icon=(await getattr( # 判定武器的突破次数是否大于 2 ;若是, 则将图标替换为 awakened (觉醒) 的图标
|
||||
self.assets_service.weapon(wid), 'icon' if weapon.ascension < 2 else 'awakened'
|
||||
self.assets_service.weapon(weapon.id), 'icon' if weapon.ascension < 2 else 'awaken'
|
||||
)()).as_uri(),
|
||||
c_path=(await self.assets_service.character(cid).side()).as_uri()
|
||||
c_path=(await self.assets_service.avatar(cid).side()).as_uri()
|
||||
)
|
||||
)
|
||||
except (UserNotFoundError, CookiesNotFoundError):
|
||||
@ -190,13 +196,13 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
await update.message.reply_chat_action(ChatAction.TYPING)
|
||||
|
||||
# 获取已经缓存的秘境素材信息
|
||||
local_data = {'character': [], 'weapon': []}
|
||||
local_data = {'avatar': [], 'weapon': []}
|
||||
if not self.data: # 若没有缓存每日素材表的数据
|
||||
logger.info("正在获取每日素材缓存")
|
||||
await self._refresh_data()
|
||||
self.data = 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' # 获取秘境的培养素材的类型:是天赋书还是武器突破材料
|
||||
type_ = 'avatar' if DOMAINS.index(domain) < 4 else 'weapon' # 获取秘境的培养素材的类型:是天赋书还是武器突破材料
|
||||
# 将读取到的数据存入 local_data 中
|
||||
local_data[type_].append({'name': area, 'materials': sche[weekday][0], 'items': sche[weekday][1]})
|
||||
|
||||
@ -205,36 +211,36 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
|
||||
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']:
|
||||
for type_ in ['avatar', 'weapon']:
|
||||
areas = []
|
||||
for area_data in local_data[type_]: # 遍历每个区域的信息:蒙德、璃月、稻妻、须弥
|
||||
items = []
|
||||
for id_ in area_data['items']: # 遍历所有该区域下,当天(weekday)可以培养的角色、武器
|
||||
added = False
|
||||
for i in user_data[type_]: # 从已经获取的角色数据中查找对应角色、武器
|
||||
if id_ == i.id:
|
||||
if id_ == str(i.id):
|
||||
if i.rarity > 3: # 跳过 3 星及以下的武器
|
||||
items.append(i)
|
||||
added = True
|
||||
if added:
|
||||
continue
|
||||
item = HONEY_ID_MAP[type_][id_]
|
||||
if item[1] < 4: # 跳过 3 星及以下的武器
|
||||
item = HONEY_DATA[type_][id_]
|
||||
if item[2] < 4: # 跳过 3 星及以下的武器
|
||||
continue
|
||||
items.append(ItemData( # 添加角色数据中未找到的
|
||||
id=id_, name=item[0], rarity=item[1],
|
||||
icon=(await getattr(self.assets_service, f'{type_}')(id_).icon()).as_uri()
|
||||
id=id_, name=item[1], rarity=item[2],
|
||||
icon=(await getattr(self.assets_service, type_)(id_).icon()).as_uri()
|
||||
))
|
||||
materials = []
|
||||
for mid in area_data['materials']: # 添加这个区域当天(weekday)的培养素材
|
||||
path = (await self.assets_service.material(mid).icon()).as_uri()
|
||||
material = HONEY_ID_MAP['material'][mid]
|
||||
materials.append(ItemData(id=mid, icon=path, name=material[0], rarity=material[1]))
|
||||
material = HONEY_DATA['material'][mid]
|
||||
materials.append(ItemData(id=mid, icon=path, name=material[1], rarity=material[2]))
|
||||
areas.append(AreaData(
|
||||
name=area_data['name'], materials=materials, items=sort_item(items),
|
||||
material_name=get_material_serial_name(map(lambda x: x.name, materials))
|
||||
))
|
||||
setattr(render_data, type_, areas)
|
||||
setattr(render_data, {'avatar': 'character'}.get(type_, type_), areas)
|
||||
|
||||
await update.message.reply_chat_action(ChatAction.TYPING)
|
||||
render_tasks = [
|
||||
@ -270,7 +276,12 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
@handler.command('refresh_daily_material', block=False)
|
||||
@bot_admins_rights_check
|
||||
async def refresh(self, update: Update, context: CallbackContext):
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
|
||||
logger.info(
|
||||
f"用户 {user.full_name}[{user.id}] 刷新[bold]每日素材[/]缓存命令", extra={'markup': True}
|
||||
)
|
||||
if self.locks[0].locked():
|
||||
notice = await message.reply_text("派蒙还在抄每日素材表呢,我有在好好工作哦~")
|
||||
self._add_delete_message_job(context, notice.chat_id, notice.message_id, 10)
|
||||
@ -290,12 +301,19 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
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>",
|
||||
time = await self._download_icon(notice)
|
||||
|
||||
async def job(_, n):
|
||||
await n.edit_text(
|
||||
n.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)
|
||||
await asyncio.sleep(INTERVAL)
|
||||
await notice.delete()
|
||||
|
||||
context.application.job_queue.run_once(
|
||||
partial(job, n=notice), when=time + INTERVAL, name='notice_msg_final_job'
|
||||
)
|
||||
|
||||
async def _refresh_data(self, retry: int = 5) -> DATA_TYPE:
|
||||
"""刷新来自 honey impact 的每日素材表"""
|
||||
@ -313,11 +331,20 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
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')]
|
||||
result[key][day][0] = []
|
||||
for a in div.find_all('a'):
|
||||
honey_id = re.findall(r"/(.*)?/", a['href'])[0]
|
||||
mid: str = [
|
||||
i[0]
|
||||
for i in HONEY_DATA['material'].items()
|
||||
if i[1][0] == honey_id
|
||||
][0]
|
||||
result[key][day][0].append(mid)
|
||||
else: # 如果是角色或武器
|
||||
id_ = re.findall(r"/(.*)?/", tag['href'])[0]
|
||||
if tag.text.strip() == '旅行者': # 忽略主角
|
||||
continue
|
||||
id_ = ("" if id_.startswith('i_n') else "10000") + re.findall(r'\d+', id_)[0]
|
||||
for day in map(int, tag.find('div')['data-days']): # 获取该角色/武器的可培养天
|
||||
result[key][day][1].append(id_)
|
||||
for stage, schedules in result.items():
|
||||
@ -339,51 +366,53 @@ class DailyMaterial(Plugin, BasePlugin):
|
||||
# noinspection PyTypeChecker
|
||||
return result
|
||||
|
||||
async def _download_icon(self, message: Optional[Message] = None):
|
||||
async def _download_icon(self, message: Optional[Message] = None) -> float:
|
||||
"""下载素材图标"""
|
||||
asset_list = []
|
||||
|
||||
from time import time as time_
|
||||
lock = asyncio.Lock()
|
||||
interval = 2
|
||||
the_time = Value(c_double, time_() - interval)
|
||||
|
||||
async def task(_id, _item, _type):
|
||||
logger.debug(f"正在开始下载 \"{_item[0]}\" 的图标素材")
|
||||
the_time = Value(c_double, time_() - INTERVAL)
|
||||
|
||||
async def edit_message(text):
|
||||
"""修改提示消息"""
|
||||
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)
|
||||
the_time.value = time_()
|
||||
except (TimedOut, RetryAfter): # 修改消息失败
|
||||
pass
|
||||
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>"
|
||||
if (
|
||||
message is not None
|
||||
and
|
||||
time_() >= (the_time.value + INTERVAL)
|
||||
):
|
||||
with contextlib.suppress(TimedOut, RetryAfter):
|
||||
await message.edit_text(
|
||||
'\n'.join(message.text_html.split('\n')[:2] + [text]),
|
||||
parse_mode=ParseMode.HTML
|
||||
)
|
||||
try:
|
||||
await message.edit_text(text, parse_mode=ParseMode.HTML)
|
||||
the_time.value = time_()
|
||||
except (TimedOut, RetryAfter):
|
||||
pass
|
||||
|
||||
for type_, items in HONEY_ID_MAP.items(): # 遍历每个对象
|
||||
async def task(item_id, name, item_type):
|
||||
logger.debug(f"正在开始下载 \"{name}\" 的图标素材")
|
||||
await edit_message(f"正在搬运 <b>{name}</b> 的图标素材。。。")
|
||||
asset: AssetsServiceType = getattr(self.assets_service, item_type)(item_id) # 获取素材对象
|
||||
asset_list.append(asset.honey_id)
|
||||
# 找到该素材对象的所有图标类型
|
||||
# 并根据图标类型找到下载对应图标的函数
|
||||
for icon_type in asset.icon_types:
|
||||
await getattr(asset, icon_type)(True) # 执行下载函数
|
||||
logger.debug(f"\"{name}\" 的图标素材下载成功")
|
||||
await edit_message(f"正在搬运 <b>{name}</b> 的图标素材。。。<b>成功!</b>")
|
||||
|
||||
for TYPE, ITEMS in HONEY_DATA.items(): # 遍历每个对象
|
||||
task_list = []
|
||||
for id_, item in items.items():
|
||||
task_list.append(asyncio.create_task(task(id_, item, type_)))
|
||||
new_items = []
|
||||
for ID, DATA in ITEMS.items():
|
||||
if (ITEM := [ID, DATA[1], TYPE]) not in new_items:
|
||||
new_items.append(ITEM)
|
||||
task_list.append(asyncio.create_task(task(*ITEM)))
|
||||
await asyncio.gather(*task_list) # 等待所有任务执行完成
|
||||
|
||||
logger.info("图标素材下载完成")
|
||||
return the_time.value
|
||||
|
||||
|
||||
class ItemData(BaseModel):
|
||||
|
29
plugins/genshin/refresh_metadata.py
Normal file
29
plugins/genshin/refresh_metadata.py
Normal file
@ -0,0 +1,29 @@
|
||||
from telegram import Update
|
||||
|
||||
from core.plugin import Plugin, handler
|
||||
from metadata.scripts.honey import update_honey_metadata
|
||||
from metadata.scripts.metadatas import update_metadata_from_ambr, update_metadata_from_github
|
||||
from utils.decorators.admins import bot_admins_rights_check
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class MetadataPlugin(Plugin):
|
||||
|
||||
@handler.command('refresh_metadata')
|
||||
@bot_admins_rights_check
|
||||
async def refresh(self, update: Update, _) -> None:
|
||||
user = update.effective_user
|
||||
message = update.effective_message
|
||||
|
||||
logger.info(
|
||||
f"用户 {user.full_name}[{user.id}] 刷新[bold]metadata[/]缓存命令", extra={'markup': True}
|
||||
)
|
||||
|
||||
msg = await message.reply_text("正在刷新元数据,请耐心等待...")
|
||||
logger.info("正在从 github 上获取元数据")
|
||||
await update_metadata_from_github()
|
||||
logger.info("正在从 ambr 上获取元数据")
|
||||
await update_metadata_from_ambr()
|
||||
logger.info("正在从 honey 上获取元数据")
|
||||
await update_honey_metadata()
|
||||
await msg.edit_text("正在刷新元数据,请耐心等待...\n完成!")
|
@ -2,11 +2,12 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
|
||||
|
||||
from core.assets import AssetsService
|
||||
from core.base.assets import AssetsService
|
||||
from core.baseplugin import BasePlugin
|
||||
from core.plugin import Plugin, handler
|
||||
from core.template import TemplateService
|
||||
from core.wiki.services import WikiService
|
||||
from metadata.genshin import honey_id_to_game_id
|
||||
from metadata.shortname import weaponToName
|
||||
from modules.wiki.weapon import Weapon
|
||||
from utils.bot import get_all_args
|
||||
@ -27,11 +28,11 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
self,
|
||||
template_service: TemplateService = None,
|
||||
wiki_service: WikiService = None,
|
||||
assert_service: AssetsService = None
|
||||
assets_service: AssetsService = None
|
||||
):
|
||||
self.wiki_service = wiki_service
|
||||
self.template_service = template_service
|
||||
self.assert_service = assert_service
|
||||
self.assets_service = assets_service
|
||||
|
||||
@handler(CommandHandler, command="weapon", block=False)
|
||||
@handler(MessageHandler, filters=filters.Regex("^武器查询(.*)"), block=False)
|
||||
@ -78,11 +79,13 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
"weapon_info_type_img": await url_to_file(_weapon_data.weapon_type.icon_url()),
|
||||
"progression_secondary_stat_value": bonus,
|
||||
"progression_secondary_stat_name": _weapon_data.attribute.type.value,
|
||||
"weapon_info_source_img": (await self.assert_service.weapon(_weapon_data.id).icon()).as_uri(),
|
||||
"weapon_info_source_img": (
|
||||
await self.assets_service.weapon(honey_id_to_game_id(_weapon_data.id, 'weapon')).icon()
|
||||
).as_uri(),
|
||||
"weapon_info_max_level": _weapon_data.stats[-1].level,
|
||||
"progression_base_atk": round(_weapon_data.stats[-1].ATK),
|
||||
"weapon_info_source_list": [
|
||||
(await self.assert_service.material(mid).icon()).as_uri()
|
||||
(await self.assets_service.material(honey_id_to_game_id(mid, 'material')).icon()).as_uri()
|
||||
for mid in _weapon_data.ascension[-3:]
|
||||
],
|
||||
"special_ability_name": _weapon_data.affix.name,
|
||||
@ -94,11 +97,13 @@ class WeaponPlugin(Plugin, BasePlugin):
|
||||
"weapon_info_type_img": await url_to_file(_weapon_data.weapon_type.icon_url()),
|
||||
"progression_secondary_stat_value": ' ',
|
||||
"progression_secondary_stat_name": '无其它属性加成',
|
||||
"weapon_info_source_img": (await self.assert_service.weapon(_weapon_data.id).icon()).as_uri(),
|
||||
"weapon_info_source_img": (
|
||||
await self.assets_service.weapon(honey_id_to_game_id(_weapon_data.id, 'weapon')).icon()
|
||||
).as_uri(),
|
||||
"weapon_info_max_level": _weapon_data.stats[-1].level,
|
||||
"progression_base_atk": round(_weapon_data.stats[-1].ATK),
|
||||
"weapon_info_source_list": [
|
||||
(await self.assert_service.material(mid).icon()).as_uri()
|
||||
(await self.assets_service.material(honey_id_to_game_id(mid, 'material')).icon()).as_uri()
|
||||
for mid in _weapon_data.ascension[-3:]
|
||||
],
|
||||
"special_ability_name": '',
|
||||
|
@ -19,19 +19,34 @@
|
||||
}
|
||||
|
||||
.character-side-icon {
|
||||
width: 32px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 1px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
div:has(> img.character-side-icon) {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
div:has(> img.character-side-icon) > div {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="mx-auto flex flex-col h-full bg-no-repeat bg-cover" id="container">
|
||||
<div class="title text-2xl my-4 text-yellow-500 mx-auto">深境螺旋</div>
|
||||
<div class="base-info flex flex-row px-20 py-1 my-1 text-white bg-white bg-opacity-10">
|
||||
<div class="uid text-center mx-auto">UID {{uid}}</div>
|
||||
<div class="text-center mx-auto">最深抵达 {{max_floor}}</div>
|
||||
<div class="text-center mx-auto">战斗次数 {{total_battles}}</div>
|
||||
<div class="text-center mx-auto">获得星级 {{total_stars}}</div>
|
||||
<div class="uid text-center mx-auto">UID {{ uid }}</div>
|
||||
<div class="text-center mx-auto">最深抵达 {{ max_floor }}</div>
|
||||
<div class="text-center mx-auto">战斗次数 {{ total_battles }}</div>
|
||||
<div class="text-center mx-auto">获得星级 {{ total_stars }}</div>
|
||||
</div>
|
||||
<div class="base-info flex flex-col px-20 py-1 text-black my-1">
|
||||
<div class="text-center mr-auto text-yellow-500">出战次数</div>
|
||||
@ -39,9 +54,9 @@
|
||||
{% for most_played in most_played_list %}
|
||||
<div class="rounded-lg mx-2 overflow-hidden" style="background-color: rgb(233, 229, 220)">
|
||||
<div class="character-icon rounded-br-3xl bg-cover overflow-hidden"
|
||||
style="background: url({{most_played.background}});background-size: cover;">
|
||||
<img src="{{most_played.icon}}" alt=""></div>
|
||||
<div class="text-center">{{most_played.value}}次</div>
|
||||
style="background: url({{ most_played.background }});background-size: cover;">
|
||||
<img src="{{ most_played.icon }}" alt=""></div>
|
||||
<div class="text-center">{{ most_played.value }}次</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -53,28 +68,28 @@
|
||||
<div>
|
||||
<div class="base-info flex flex-row px-20 py-1 my-1 text-white bg-black bg-opacity-10 ">
|
||||
<div class="text-center flex flex-row flex-1 mr-6">
|
||||
<div class="my-auto">最多击破数:{{most_kills.value}}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{most_kills.icon}}" alt="">
|
||||
<div class="my-auto">最多击破数:{{ most_kills.value }}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{ most_kills.icon }}" alt="">
|
||||
</div>
|
||||
<div class="text-center flex flex-row flex-1 mr-6">
|
||||
<div class="my-auto">最强一击:{{strongest_strike.value}}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{strongest_strike.icon}}" alt="">
|
||||
<div class="my-auto">最强一击:{{ strongest_strike.value }}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{ strongest_strike.icon }}" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="base-info flex flex-row px-20 py-1 my-1 text-white">
|
||||
<div class="text-center flex flex-row flex-1 mr-6">
|
||||
<div class="my-auto">承受最多伤害:{{most_damage_taken.value}}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{most_damage_taken.icon}}" alt="">
|
||||
<div class="my-auto">承受最多伤害:{{ most_damage_taken.value }}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{ most_damage_taken.icon }}" alt="">
|
||||
</div>
|
||||
<div class="text-center flex flex-row flex-1 mr-6">
|
||||
<div class="my-auto">元素爆发数:{{most_bursts_used.value}}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{most_bursts_used.icon}}" alt="">
|
||||
<div class="my-auto">元素爆发数:{{ most_bursts_used.value }}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{ most_bursts_used.icon }}" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="base-info flex flex-row px-20 py-1 my-1 text-white bg-black bg-opacity-10 ">
|
||||
<div class="text-center flex flex-row flex-1 mr-6">
|
||||
<div class="my-auto">元素战技释放次数:{{most_skills_used.value}}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{most_skills_used.icon}}" alt="">
|
||||
<div class="my-auto">元素战技释放次数:{{ most_skills_used.value }}</div>
|
||||
<img class="character-side-icon ml-auto" src="{{ most_skills_used.icon }}" alt="">
|
||||
</div>
|
||||
<div class="text-center flex flex-row flex-1 mr-6"></div>
|
||||
</div>
|
||||
|
@ -18,10 +18,24 @@
|
||||
}
|
||||
|
||||
.character-side-icon {
|
||||
width: 32px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 1px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
div:has(> img.character-side-icon) {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
div:has(> img.character-side-icon) > div {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -70,7 +84,7 @@
|
||||
<div class="base-info flex flex-row px-20 py-1 my-1 text-white bg-black bg-opacity-10 ">
|
||||
<div class="text-center flex flex-row flex-1 mr-6">
|
||||
<div class="my-auto">最多击破数:21</div>
|
||||
<img class="character-side-icon ml-auto" src="./../../img/example/256x256.png" alt="">
|
||||
<img class="character-side-icon" src="./../../img/example/256x256.png" alt="">
|
||||
</div>
|
||||
<div class="text-center flex flex-row flex-1 mr-6">
|
||||
<div class="my-auto">最强一击:21</div>
|
||||
|
@ -28,7 +28,7 @@
|
||||
<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_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/3.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/4.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -58,7 +58,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/5.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -75,17 +75,17 @@
|
||||
<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="一斗"/>
|
||||
<img src="../../assets/avatar/10000058/icon.png" alt="八重神子"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>一斗</div>
|
||||
<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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -95,7 +95,7 @@
|
||||
<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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -104,7 +104,7 @@
|
||||
<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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -112,7 +112,7 @@
|
||||
</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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -120,7 +120,7 @@
|
||||
</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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -140,7 +140,7 @@
|
||||
<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_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -149,7 +149,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/3.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -159,7 +159,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/4.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -170,7 +170,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/5.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -187,7 +187,7 @@
|
||||
<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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000058/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -197,7 +197,7 @@
|
||||
<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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -207,7 +207,7 @@
|
||||
<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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -216,7 +216,7 @@
|
||||
<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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -224,7 +224,7 @@
|
||||
</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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -232,7 +232,7 @@
|
||||
</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="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/icon.png" alt="神里绫华"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>神里绫华</div>
|
||||
@ -256,7 +256,7 @@
|
||||
<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_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -265,7 +265,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/3.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -275,7 +275,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/4.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -286,7 +286,7 @@
|
||||
</div>
|
||||
<div class="material">
|
||||
<div class="material-icon" style="background-image: url(./bg/rarity/half/5.png)">
|
||||
<img alt="" src="../../assets/material/i_21/icon.webp"/>
|
||||
<img alt="" src="../../assets/material/114003/icon.png"/>
|
||||
</div>
|
||||
<div class="material-star">
|
||||
<img alt="star" src="./bg/rarity/star.webp"/>
|
||||
@ -301,12 +301,12 @@
|
||||
<div class="area-content">
|
||||
<div class="item weapon item-not-owned">
|
||||
<div class="role">
|
||||
<img src="../../assets/character/shenhe_063/side.webp" alt="申鹤"/>
|
||||
<img src="../../assets/avatar/10000063/side.png" 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="天空之刃"/>
|
||||
<img src="../../assets/weapon/14501/awaken.png" alt="天空之刃"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>天空之刃</div>
|
||||
@ -314,12 +314,12 @@
|
||||
</div>
|
||||
<div class="item weapon">
|
||||
<div class="role">
|
||||
<img src="../../assets/character/mona_041/side.webp" alt="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000041/side.png" 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="天空之刃"/>
|
||||
<img src="../../assets/weapon/14502/awaken.png" alt="天空之刃"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>天空之刃</div>
|
||||
@ -327,12 +327,12 @@
|
||||
</div>
|
||||
<div class="item weapon">
|
||||
<div class="role">
|
||||
<img src="../../assets/character/collei_067/side.webp" alt="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000067/side.png" 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="天空之刃"/>
|
||||
<img src="../../assets/weapon/14502/awaken.png" alt="天空之刃"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>天空之刃</div>
|
||||
@ -340,12 +340,12 @@
|
||||
</div>
|
||||
<div class="item weapon">
|
||||
<div class="role">
|
||||
<img src="../../assets/character/ayaka_002/side.webp" alt="神里绫华"/>
|
||||
<img src="../../assets/avatar/10000002/side.png" 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="天空之刃"/>
|
||||
<img src="../../assets/weapon/14502/awaken.png" alt="天空之刃"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>天空之刃</div>
|
||||
@ -353,7 +353,7 @@
|
||||
</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="天空之刃"/>
|
||||
<img src="../../assets/weapon/14502/awaken.png" alt="天空之刃"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>天空之刃</div>
|
||||
@ -361,7 +361,7 @@
|
||||
</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="天空之刃"/>
|
||||
<img src="../../assets/weapon/14502/awaken.png" alt="天空之刃"/>
|
||||
</div>
|
||||
<div class="item-name">
|
||||
<div>天空之刃</div>
|
||||
|
@ -180,7 +180,7 @@ body {
|
||||
}
|
||||
|
||||
.material-icon > img {
|
||||
height: calc(100% - 10px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.material-star {
|
||||
@ -211,21 +211,23 @@ body {
|
||||
box-shadow: 3px 3px 10px var(--shadow);
|
||||
margin: 10px 12px;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.item-not-owned:before {
|
||||
.item-not-owned::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
height: 187px;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
z-index: 2;
|
||||
z-index: 1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: inherit;
|
||||
height: 152px;
|
||||
height: 150px;
|
||||
background-size: 104% auto;
|
||||
background-position: center;
|
||||
overflow: hidden;
|
||||
@ -313,9 +315,9 @@ body {
|
||||
}
|
||||
|
||||
.weapon > .role > img {
|
||||
height: 50px;
|
||||
height: 58px;
|
||||
position: absolute;
|
||||
left: 58%;
|
||||
left: 20px;
|
||||
transform: translateX(-50%);
|
||||
bottom: 0;
|
||||
}
|
@ -1,9 +1,12 @@
|
||||
"""常量"""
|
||||
from pathlib import Path
|
||||
|
||||
from httpx import URL
|
||||
|
||||
__all__ = [
|
||||
'PROJECT_ROOT', 'PLUGIN_DIR', 'RESOURCE_DIR',
|
||||
'NOT_SET',
|
||||
'HONEY_HOST', 'ENKA_HOST', 'AMBR_HOST', 'CELESTIA_HOST',
|
||||
]
|
||||
|
||||
# 项目根目录
|
||||
@ -14,3 +17,8 @@ PLUGIN_DIR = PROJECT_ROOT / 'plugins'
|
||||
RESOURCE_DIR = PROJECT_ROOT / 'resources'
|
||||
|
||||
NOT_SET = object()
|
||||
|
||||
HONEY_HOST = URL("https://genshin.honeyhunterworld.com/")
|
||||
ENKA_HOST = URL("https://enka.network/")
|
||||
AMBR_HOST = URL("https://api.ambr.top/")
|
||||
CELESTIA_HOST = URL("https://www.projectcelestia.com/")
|
||||
|
@ -1,13 +1,14 @@
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple, Union, cast
|
||||
from typing import Optional, Tuple, TypeVar, Union, cast
|
||||
|
||||
import aiofiles
|
||||
import genshin
|
||||
import httpx
|
||||
from genshin import Client, types
|
||||
from httpx import UnsupportedProtocol
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
from core.bot import bot
|
||||
from core.cookies.services import CookiesService, PublicCookiesService
|
||||
@ -17,6 +18,9 @@ from utils.error import UrlResourcesNotFoundError
|
||||
from utils.log import logger
|
||||
from utils.models.base import RegionEnum
|
||||
|
||||
T = TypeVar('T')
|
||||
P = ParamSpec('P')
|
||||
|
||||
USER_AGENT: str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " \
|
||||
"Chrome/90.0.4430.72 Safari/537.36"
|
||||
REQUEST_HEADERS: dict = {'User-Agent': USER_AGENT}
|
||||
|
@ -5,16 +5,19 @@ from typing import Any, Dict, Optional, Tuple, Type, Union
|
||||
from httpx import URL
|
||||
|
||||
__all__ = [
|
||||
'StrOrPath', 'StrOrURL',
|
||||
'StrOrPath', 'StrOrURL', 'StrOrInt',
|
||||
'SysExcInfoType', 'ExceptionInfoType',
|
||||
'JSONDict',
|
||||
'JSONDict', 'JSONType'
|
||||
]
|
||||
|
||||
StrOrPath = Union[str, Path]
|
||||
StrOrURL = Union[str, URL]
|
||||
StrOrInt = Union[str, int]
|
||||
|
||||
SysExcInfoType = Union[
|
||||
Tuple[Type[BaseException], BaseException, Optional[TracebackType]],
|
||||
Tuple[None, None, None]
|
||||
]
|
||||
ExceptionInfoType = Union[bool, SysExcInfoType, BaseException]
|
||||
JSONDict = Dict[str, Any]
|
||||
JSONType = Union[JSONDict, list]
|
||||
|
Loading…
Reference in New Issue
Block a user