diff --git a/core/dependence/assets.py b/core/dependence/assets.py index c1c625e..42b0497 100644 --- a/core/dependence/assets.py +++ b/core/dependence/assets.py @@ -1,47 +1,25 @@ -"""用于下载和管理角色、武器、材料等的图标""" -from __future__ import annotations - import asyncio -import re -from abc import ABC, abstractmethod -from functools import cached_property, lru_cache, partial -from multiprocessing import RLock as Lock from pathlib import Path from ssl import SSLZeroReturnError -from typing import AsyncIterator, Awaitable, Callable, ClassVar, Dict, Optional, TYPE_CHECKING, TypeVar, Union +from typing import Optional, List, Dict 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, HTTPStatusError, TransportError, URL -from typing_extensions import Self +from httpx import AsyncClient, HTTPError from core.base_service import BaseService -from core.config import config -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 utils.const import AMBR_HOST, ENKA_HOST, PROJECT_ROOT +from modules.wiki.base import WikiModel +from modules.wiki.models.avatar_config import AvatarIcon +from modules.wiki.models.light_cone_config import LightConeIcon +from utils.const import PROJECT_ROOT from utils.log import logger -from utils.typedefs import StrOrInt, StrOrURL - -if TYPE_CHECKING: - from httpx import Response - from multiprocessing.synchronize import RLock - -__all__ = ("AssetsServiceType", "AssetsService", "AssetsServiceError", "AssetsCouldNotFound", "DEFAULT_EnkaAssets") - -ICON_TYPE = Union[Callable[[bool], Awaitable[Optional[Path]]], Callable[..., Awaitable[Optional[Path]]]] -NAME_MAP_TYPE = Dict[str, StrOrURL] +from utils.typedefs import StrOrURL, StrOrInt ASSETS_PATH = PROJECT_ROOT.joinpath("resources/assets") ASSETS_PATH.mkdir(exist_ok=True, parents=True) -HONEY_HOST = "" -DATA_MAP = {"avatar": AVATAR_DATA, "weapon": WEAPON_DATA, "material": MATERIAL_DATA} - -DEFAULT_EnkaAssets = EnkaAssets(lang="chs") +DATA_MAP = { + "avatar": WikiModel.BASE_URL + "avatar_icons.json", + "light_cone": WikiModel.BASE_URL + "light_cone_icons.json", +} class AssetsServiceError(Exception): @@ -55,84 +33,16 @@ class AssetsCouldNotFound(AssetsServiceError): super().__init__(f"{message}: target={message}") -class _AssetsService(ABC): - _lock: ClassVar["RLock"] = Lock() - _dir: ClassVar[Path] - icon_types: ClassVar[list[str]] - - _client: Optional[AsyncClient] = None - _links: dict[str, str] = {} - - 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 +class _AssetsService: + client: Optional[AsyncClient] = None def __init__(self, client: Optional[AsyncClient] = None) -> None: - self._client = client + 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 _request(self, url: str, interval: float = 0.2) -> "Response": - error = None - for _ in range(5): - try: - return await self.client.get(url, follow_redirects=False) - except (TransportError, SSLZeroReturnError) as e: - error = e - await asyncio.sleep(interval) - continue - if error is not None: - raise error - - async def _download(self, url: StrOrURL, path: Path, retry: int = 5) -> Path | None: + async def _download(self, url: StrOrURL, path: Path, retry: int = 5) -> Optional[Path]: """从 url 下载图标至 path""" logger.debug("正在从 %s 下载图标至 %s", url, path) headers = None - if config.enka_network_api_agent is not None and URL(url).host == "enka.network": - headers = {"user-agent": config.enka_network_api_agent} for time in range(retry): try: response = await self.client.get(url, follow_redirects=False, headers=headers) @@ -150,356 +60,144 @@ class _AssetsService(ABC): await file.write(response.content) # 保存图标 return path.resolve() - async def _get_from_ambr(self, item: str) -> AsyncIterator[str | None]: # pylint: disable=W0613,R0201 - """从 ambr.top 上获取目标链接""" - yield None - - async def _get_from_enka(self, item: str) -> AsyncIterator[str | None]: # pylint: disable=W0613,R0201 - """从 enke.network 上获取目标链接""" - yield None - - async def _get_from_honey(self, item: str) -> AsyncIterator[str | None]: - """从 honey 上获取目标链接""" - if (honey_name := self.honey_name_map.get(item, None)) is not None: - yield HONEY_HOST.join(f"img/{honey_name}.png") - yield HONEY_HOST.join(f"img/{honey_name}.webp") - - async def _download_url_generator(self, item: str) -> AsyncIterator[str]: - # 获取当前 `AssetsService` 的所有爬虫 - for func in map(lambda x: getattr(self, x), sorted(filter(lambda x: x.startswith("_get_from_"), dir(self)))): - async for url in func(item): - if url is not None: - try: - response = await self._request(url := str(url)) - response.raise_for_status() - yield url - except HTTPStatusError: - continue - - async def _get_download_url(self, item: str) -> str | None: - """获取图标的下载链接""" - async for url in self._download_url_generator(item): - if url is not None: - return url - - 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: # 如果需要下载的图标存在且不覆盖( overwrite ) - return path.resolve() - if path is not None and path.exists(): - if overwrite: # 如果覆盖 - await async_remove(path) # 删除已存在的图标 - else: - return path - # 依次从使用当前 assets class 中的爬虫下载图标,顺序为爬虫名的字母顺序 - async for url in self._download_url_generator(item): - if url is not None: - path = self.path.joinpath(f"{item}{Path(url).suffix}") - if (result := await self._download(url, path)) is not None: - return result - - @lru_cache - async def get_link(self, item: str) -> str | None: - """获取相应图标链接""" - return await self._get_download_url(item) - - def __getattr__(self, item: str): - """魔法""" - if item in self.icon_types: - return partial(self._get_img, item=item) - object.__getattribute__(self, item) - return None - - @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 + path: Path + data: List[AvatarIcon] + name_map: Dict[str, AvatarIcon] + id_map: Dict[int, AvatarIcon] - 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): + def __init__(self, client: Optional[AsyncClient] = None) -> None: super().__init__(client) - self._enka_api = enka or DEFAULT_EnkaAssets + self.path = ASSETS_PATH.joinpath("avatar") + self.path.mkdir(exist_ok=True, parents=True) - 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("找不到对应的角色", temp) - result.id = target - result._enka_api = self._enka_api - return result + async def initialize(self): + logger.info("正在初始化角色素材图标") + html = await self.client.get(DATA_MAP["avatar"]) + self.data = [AvatarIcon(**data) for data in html.json()] + self.name_map = {icon.name: icon for icon in self.data} + self.id_map = {icon.id: icon for icon in self.data} + tasks = [] + for icon in self.data: + base_path = self.path / f"{icon.id}" + base_path.mkdir(exist_ok=True, parents=True) + gacha_path = base_path / "gacha.webp" + icon_path = base_path / "icon.webp" + normal_path = base_path / "normal.webp" + if not gacha_path.exists(): + tasks.append(self._download(icon.gacha, gacha_path)) + if not icon_path.exists(): + tasks.append(self._download(icon.icon_, icon_path)) + if not normal_path.exists(): + tasks.append(self._download(icon.normal, normal_path)) + if len(tasks) >= 100: + await asyncio.gather(*tasks) + tasks = [] + if tasks: + await asyncio.gather(*tasks) + logger.info("角色素材图标初始化完成") - async def _get_from_ambr(self, item: str) -> AsyncIterator[str | None]: - if item in {"icon", "side", "gacha"}: - yield str(AMBR_HOST.join(f"assets/UI/{self.game_name_map[item]}.png")) + def get_path(self, icon: AvatarIcon, name: str) -> Path: + path = self.path / f"{icon.id}" + path.mkdir(exist_ok=True, parents=True) + return path / f"{name}.webp" - async def _get_from_enka(self, item: str) -> AsyncIterator[str | None]: - if (item_id := self.game_name_map.get(item)) is not None: - yield str(ENKA_HOST.join(f"ui/{item_id}.png")) + def get_by_id(self, id_: int) -> Optional[AvatarIcon]: + return self.id_map.get(id_, None) - @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", - } + def get_by_name(self, name: str) -> Optional[AvatarIcon]: + return self.name_map.get(name, None) - @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}", - } + def get_target(self, target: StrOrInt) -> Optional[AvatarIcon]: + if isinstance(target, int): + return self.get_by_id(target) + elif isinstance(target, str): + return self.get_by_name(target) + return None + + def gacha(self, target: StrOrInt) -> Path: + icon = self.get_target(target) + if icon is None: + raise AssetsCouldNotFound("角色素材图标不存在", target) + return self.get_path(icon, "gacha") + + def icon(self, target: StrOrInt) -> Path: + icon = self.get_target(target) + if icon is None: + raise AssetsCouldNotFound("角色素材图标不存在", target) + return self.get_path(icon, "icon") + + def normal(self, target: StrOrInt) -> Path: + icon = self.get_target(target) + if icon is None: + raise AssetsCouldNotFound("角色素材图标不存在", target) + return self.get_path(icon, "normal") -class _WeaponAssets(_AssetsService): - awaken: ICON_TYPE - """突破后图标""" +class _LightConeAssets(_AssetsService): + path: Path + data: List[LightConeIcon] + name_map: Dict[str, LightConeIcon] + id_map: Dict[int, LightConeIcon] - gacha: ICON_TYPE - """抽卡立绘""" + def __init__(self, client: Optional[AsyncClient] = None) -> None: + super().__init__(client) + self.path = ASSETS_PATH.joinpath("light_cone") + self.path.mkdir(exist_ok=True, parents=True) - @cached_property - def game_name(self) -> str: - return re.findall(r"UI_EquipIcon_(.*)", WEAPON_DATA[str(self.id)]["icon"])[0] + async def initialize(self): + logger.info("正在初始化光锥素材图标") + html = await self.client.get(DATA_MAP["light_cone"]) + self.data = [LightConeIcon(**data) for data in html.json()] + self.name_map = {icon.name: icon for icon in self.data} + self.id_map = {icon.id: icon for icon in self.data} + tasks = [] + for icon in self.data: + base_path = self.path / f"{icon.id}" + base_path.mkdir(exist_ok=True, parents=True) + gacha_path = base_path / "gacha.webp" + icon_path = base_path / "icon.webp" + if not gacha_path.exists(): + tasks.append(self._download(icon.gacha, gacha_path)) + if not icon_path.exists(): + tasks.append(self._download(icon.icon_, icon_path)) + if len(tasks) >= 100: + await asyncio.gather(*tasks) + tasks = [] + if tasks: + await asyncio.gather(*tasks) + logger.info("光锥素材图标初始化完成") - @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}", - } + def get_path(self, icon: LightConeIcon, name: str) -> Path: + path = self.path / f"{icon.id}" + path.mkdir(exist_ok=True, parents=True) + return path / f"{name}.webp" - @cached_property - def honey_id(self) -> str: - return f"i_n{self.id}" + def get_by_id(self, id_: int) -> Optional[LightConeIcon]: + return self.id_map.get(id_, None) - 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("找不到对应的武器", temp) - result.id = target - return result + def get_by_name(self, name: str) -> Optional[LightConeIcon]: + return self.name_map.get(name, None) - async def _get_from_ambr(self, item: str) -> AsyncIterator[str | None]: - if item == "icon": - yield str(AMBR_HOST.join(f"assets/UI/{self.game_name_map.get(item)}.png")) + def get_target(self, target: StrOrInt) -> Optional[LightConeIcon]: + if isinstance(target, int): + return self.get_by_id(target) + elif isinstance(target, str): + return self.get_by_name(target) + return None - async def _get_from_enka(self, item: str) -> AsyncIterator[str | None]: - if item in self.game_name_map: - yield str(ENKA_HOST.join(f"ui/{self.game_name_map.get(item)}.png")) + def gacha(self, target: StrOrInt) -> Path: + icon = self.get_target(target) + if icon is None: + raise AssetsCouldNotFound("光锥素材图标不存在", target) + return self.get_path(icon, "gacha") - @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("找不到对应的素材", temp) - result.id = target - return result - - async def _get_from_ambr(self, item: str) -> AsyncIterator[str | None]: - if item == "icon": - yield str(AMBR_HOST.join(f"assets/UI/{self.game_name_map.get(item)}.png")) - - async def _get_from_honey(self, item: str) -> AsyncIterator[str | None]: - yield HONEY_HOST.join(f"/img/{self.honey_name_map.get(item)}.png") - yield HONEY_HOST.join(f"/img/{self.honey_name_map.get(item)}.webp") - - -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) -> AsyncIterator[str | None]: - if item in self.game_name_map: - yield str(ENKA_HOST.join(f"ui/{self.game_name_map.get(item)}.png")) - - async def _get_from_ambr(self, item: str) -> AsyncIterator[str | None]: - if item in self.game_name_map: - yield str(AMBR_HOST.join(f"assets/UI/reliquary/{self.game_name_map[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"] - - @lru_cache - def _get_id_from_avatar_id(self, avatar_id: Union[int, str]) -> int: - avatar_icon_name = AVATAR_DATA[str(avatar_id)]["icon"].replace("AvatarIcon", "NameCardIcon") - for namecard_id, namecard_data in NAMECARD_DATA.items(): - if namecard_data["icon"] == avatar_icon_name: - return int(namecard_id) - raise ValueError(avatar_id) - - def __call__(self, target: int) -> "_NamecardAssets": - result = _NamecardAssets(self.client) - if target > 10000000: - target = self._get_id_from_avatar_id(target) - result.id = target - result.enka = DEFAULT_EnkaAssets.namecards(target) - return result - - async def _get_from_ambr(self, item: str) -> AsyncIterator[str | None]: - if item == "profile": - yield AMBR_HOST.join(f"assets/UI/namecard/{self.game_name_map[item]}.png.png") - - async def _get_from_enka(self, item: str) -> AsyncIterator[str | None]: - if (url := getattr(self.enka, {"profile": "banner"}.get(item, item), None)) is not None: - yield url.url - - @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", - } + def icon(self, target: StrOrInt) -> Path: + icon = self.get_target(target) + if icon is None: + raise AssetsCouldNotFound("光锥素材图标不存在", target) + return self.get_path(icon, "icon") class AssetsService(BaseService.Dependence): @@ -510,26 +208,19 @@ class AssetsService(BaseService.Dependence): 若本地不存在,则从网络上下载;若存在,则返回其路径 """ + client: Optional[AsyncClient] = None + avatar: _AvatarAssets """角色""" - weapon: _WeaponAssets - """武器""" - - material: _MaterialAssets - """素材""" - - artifact: _ArtifactAssets - """圣遗物""" - - namecard: _NamecardAssets - """名片""" + light_cone: _LightConeAssets + """光锥""" 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]()) + self.client = AsyncClient(timeout=60.0) + self.avatar = _AvatarAssets(self.client) + self.light_cone = _LightConeAssets(self.client) - -AssetsServiceType = TypeVar("AssetsServiceType", bound=_AssetsService) + async def initialize(self): # pylint: disable=W0221 + await self.avatar.initialize() + await self.light_cone.initialize() diff --git a/core/dependence/assets.pyi b/core/dependence/assets.pyi deleted file mode 100644 index ba1b250..0000000 --- a/core/dependence/assets.pyi +++ /dev/null @@ -1,167 +0,0 @@ -from __future__ import annotations - -from abc import ABC, abstractmethod -from functools import partial -from pathlib import Path -from typing import Awaitable, Callable, ClassVar, TypeVar - -from enkanetwork import Assets as EnkaAssets -from enkanetwork.model.assets import CharacterAsset as EnkaCharacterAsset -from httpx import AsyncClient -from typing_extensions import Self - -from core.base_service import BaseService -from utils.typedefs import StrOrInt - -__all__ = ("AssetsServiceType", "AssetsService", "AssetsServiceError", "AssetsCouldNotFound", "DEFAULT_EnkaAssets") - -ICON_TYPE = Callable[[bool], Awaitable[Path | None]] | Callable[..., Awaitable[Path | None]] -DEFAULT_EnkaAssets: EnkaAssets -_GET_TYPE = partial | list[str] | int | str | ICON_TYPE | Path | AsyncClient | None | Self | dict[str, str] - -class AssetsServiceError(Exception): ... - -class AssetsCouldNotFound(AssetsServiceError): - message: str - target: str - def __init__(self, message: str, target: str): ... - -class _AssetsService(ABC): - icon_types: ClassVar[list[str]] - id: int - type: str - - icon: ICON_TYPE - """图标""" - - @abstractmethod - @property - def game_name(self) -> str: - """游戏数据中的名称""" - @property - def honey_id(self) -> str: - """当前资源在 Honey Impact 所对应的 ID""" - @property - def path(self) -> Path: - """当前资源的文件夹""" - @property - def client(self) -> AsyncClient: - """当前的 http client""" - def __init__(self, client: AsyncClient | None = None) -> None: ... - def __call__(self, target: int) -> Self: - """用于生成与 target 对应的 assets""" - def __getattr__(self, item: str) -> _GET_TYPE: - """魔法""" - async def get_link(self, item: str) -> str | None: - """获取相应图标链接""" - @abstractmethod - @property - def game_name_map(self) -> dict[str, str]: - """游戏中的图标名""" - @abstractmethod - @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 - """抽卡卡片""" - - @property - def honey_name_map(self) -> dict[str, str]: ... - @property - def game_name_map(self) -> dict[str, str]: ... - @property - def enka(self) -> EnkaCharacterAsset | None: ... - def __init__(self, client: AsyncClient | None = None, enka: EnkaAssets | None = None) -> None: ... - def __call__(self, target: StrOrInt) -> Self: ... - def __getitem__(self, item: str) -> _GET_TYPE | EnkaCharacterAsset: ... - def game_name(self) -> str: ... - -class _WeaponAssets(_AssetsService): - awaken: ICON_TYPE - """突破后图标""" - - gacha: ICON_TYPE - """抽卡立绘""" - - @property - def honey_name_map(self) -> dict[str, str]: ... - @property - def game_name_map(self) -> dict[str, str]: ... - def __call__(self, target: StrOrInt) -> Self: ... - def game_name(self) -> str: ... - -class _MaterialAssets(_AssetsService): - @property - def honey_name_map(self) -> dict[str, str]: ... - @property - def game_name_map(self) -> dict[str, str]: ... - def __call__(self, target: StrOrInt) -> Self: ... - def game_name(self) -> str: ... - -class _ArtifactAssets(_AssetsService): - flower: ICON_TYPE - """生之花""" - - plume: ICON_TYPE - """死之羽""" - - sands: ICON_TYPE - """时之沙""" - - goblet: ICON_TYPE - """空之杯""" - - circlet: ICON_TYPE - """理之冠""" - - @property - def honey_name_map(self) -> dict[str, str]: ... - @property - def game_name_map(self) -> dict[str, str]: ... - def game_name(self) -> str: ... - -class _NamecardAssets(_AssetsService): - enka: EnkaCharacterAsset | None - - navbar: ICON_TYPE - """好友名片背景""" - - profile: ICON_TYPE - """个人资料名片背景""" - - @property - def honey_name_map(self) -> dict[str, str]: ... - @property - def game_name_map(self) -> dict[str, str]: ... - def game_name(self) -> str: ... - -class AssetsService(BaseService.Dependence): - avatar: _AvatarAssets - """角色""" - - weapon: _WeaponAssets - """武器""" - - material: _MaterialAssets - """素材""" - - artifact: _ArtifactAssets - """圣遗物""" - - namecard: _NamecardAssets - """名片""" - -AssetsServiceType = TypeVar("AssetsServiceType", bound=_AssetsService) diff --git a/core/services/wiki/services.py b/core/services/wiki/services.py index be4f0e4..c623763 100644 --- a/core/services/wiki/services.py +++ b/core/services/wiki/services.py @@ -23,12 +23,15 @@ class WikiService(BaseService): async def initialize(self) -> None: logger.info("正在加载 Wiki 数据") - await self.character.read() - await self.material.read() - await self.monster.read() - await self.relic.read() - await self.light_cone.read() - await self.raider.read() + try: + await self.character.read() + await self.material.read() + await self.monster.read() + await self.relic.read() + await self.light_cone.read() + await self.raider.read() + except Exception as e: + logger.error("加载 Wiki 数据失败", exc_info=e) logger.info("加载 Wiki 数据完成") async def refresh_wiki(self) -> NoReturn: diff --git a/metadata/pool/pool.py b/metadata/pool/pool.py index 8b09eea..f64423e 100644 --- a/metadata/pool/pool.py +++ b/metadata/pool/pool.py @@ -1,13 +1,16 @@ -from metadata.pool.pool_200 import POOL_200 -from metadata.pool.pool_301 import POOL_301 -from metadata.pool.pool_302 import POOL_302 +from metadata.pool.pool_1 import POOL_1 +from metadata.pool.pool_2 import POOL_2 +from metadata.pool.pool_11 import POOL_11 +from metadata.pool.pool_12 import POOL_12 def get_pool_by_id(pool_type): - if pool_type == 200: - return POOL_200 - if pool_type == 301: - return POOL_301 - if pool_type == 302: - return POOL_302 + if pool_type == 1: + return POOL_1 + elif pool_type == 2: + return POOL_2 + if pool_type == 11: + return POOL_11 + if pool_type == 12: + return POOL_12 return None diff --git a/metadata/pool/pool_1.py b/metadata/pool/pool_1.py new file mode 100644 index 0000000..0595a15 --- /dev/null +++ b/metadata/pool/pool_1.py @@ -0,0 +1 @@ +POOL_1 = [{"five": ["常驻池"], "four": [], "from": "2023-04-26 06:00:00", "name": "常驻池", "to": "2050-09-15 17:59:59"}] diff --git a/metadata/pool/pool_11.py b/metadata/pool/pool_11.py new file mode 100644 index 0000000..fcab44d --- /dev/null +++ b/metadata/pool/pool_11.py @@ -0,0 +1,9 @@ +POOL_11 = [ + { + "five": ["希尔"], + "four": ["娜塔莎", "佩拉", "虎克"], + "from": "2023-04-26 06:00:00", + "name": "蝶立锋锷", + "to": "2023-05-17 17:59:59", + }, +] diff --git a/metadata/pool/pool_12.py b/metadata/pool/pool_12.py new file mode 100644 index 0000000..28b4f42 --- /dev/null +++ b/metadata/pool/pool_12.py @@ -0,0 +1,9 @@ +POOL_12 = [ + { + "five": ["于夜色中"], + "four": ["一场术后对话", "晚安与睡颜", "鼹鼠党欢迎你"], + "from": "2023-04-26 06:00:00", + "name": "流光定影", + "to": "2023-05-17 17:59:59", + }, +] diff --git a/metadata/pool/pool_2.py b/metadata/pool/pool_2.py new file mode 100644 index 0000000..3d959aa --- /dev/null +++ b/metadata/pool/pool_2.py @@ -0,0 +1 @@ +POOL_2 = [{"five": ["新手池"], "four": [], "from": "2023-04-26 06:00:00", "name": "新手池", "to": "2050-09-15 17:59:59"}] diff --git a/metadata/pool/pool_200.py b/metadata/pool/pool_200.py deleted file mode 100644 index 3bc8339..0000000 --- a/metadata/pool/pool_200.py +++ /dev/null @@ -1 +0,0 @@ -POOL_200 = [{"five": ["常驻池"], "four": [], "from": "2020-09-15 06:00:00", "name": "常驻池", "to": "2050-09-15 17:59:59"}] diff --git a/metadata/pool/pool_301.py b/metadata/pool/pool_301.py deleted file mode 100644 index c5e1a9f..0000000 --- a/metadata/pool/pool_301.py +++ /dev/null @@ -1,324 +0,0 @@ -POOL_301 = [ - { - "five": ["纳西妲", "妮露"], - "four": ["久岐忍", "多莉", "莱依拉"], - "from": "2023-04-12 06:00:00", - "name": "月草的赐慧|翩舞歈莲", - "to": "2023-05-02 17:59:59", - }, - { - "five": ["申鹤", "神里绫华"], - "four": ["米卡", "砂糖", "迪奥娜"], - "from": "2023-03-21 18:00:00", - "name": "孤辰茕怀|白鹭霜华", - "to": "2023-04-11 14:59:59", - }, - { - "five": ["迪希雅", "赛诺"], - "four": ["班尼特", "芭芭拉", "柯莱"], - "from": "2023-03-01 06:00:00", - "name": "烈阳烁金|雳裁冥昭", - "to": "2023-03-21 17:59:59", - }, - { - "five": ["胡桃", "夜兰"], - "four": ["行秋", "凝光", "北斗"], - "from": "2023-02-07 18:00:00", - "name": "赤团开时|素霓伣天", - "to": "2023-02-28 14:59:59", - }, - { - "five": ["艾尔海森", "魈"], - "four": ["瑶瑶", "云堇", "辛焱"], - "from": "2023-01-18 06:00:00", - "name": "敕诫枢谋|烟火之邀", - "to": "2023-02-07 17:59:59", - }, - { - "five": ["雷电将军", "神里绫人"], - "four": ["罗莎莉亚", "早柚", "九条裟罗"], - "from": "2022-12-27 18:00:00", - "name": "影寂天下人|苍流踏花", - "to": "2023-01-17 14:59:59", - }, - { - "five": ["流浪者", "荒泷一斗"], - "four": ["珐露珊", "五郎", "烟绯"], - "from": "2022-12-07 06:00:00", - "name": "余火变相|鬼门斗宴", - "to": "2022-12-27 17:59:59", - }, - { - "five": ["八重神子", "达达利亚"], - "four": ["莱依拉", "托马", "鹿野院平藏"], - "from": "2022-11-18 18:00:00", - "name": "华紫樱绯|暂别冬都", - "to": "2022-12-06 14:59:59", - }, - { - "five": ["纳西妲", "宵宫"], - "four": ["雷泽", "诺艾尔", "班尼特"], - "from": "2022-11-2 06:00:00", - "name": "月草的赐慧|焰色天河", - "to": "2022-11-18 17:59:59", - }, - { - "five": ["妮露", "阿贝多"], - "four": ["北斗", "芭芭拉", "香菱"], - "from": "2022-10-14 18:00:00", - "name": "翩舞歈莲|深秘之息", - "to": "2022-11-01 14:59:59", - }, - { - "five": ["赛诺", "温迪"], - "four": ["久岐忍", "早柚", "坎蒂丝"], - "from": "2022-09-28 06:00:00", - "name": "雳裁冥昭|杯装之诗", - "to": "2022-10-14 17:59:59", - }, - { - "five": ["甘雨", "心海"], - "four": ["行秋", "砂糖", "多莉"], - "from": "2022-09-09 18:00:00", - "name": "浮生孰来|浮岳虹珠", - "to": "2022-09-27 14:59:59", - }, - { - "five": ["提纳里", "钟离"], - "four": ["云堇", "辛焱", "班尼特"], - "from": "2022-08-24 06:00:00", - "name": "巡御蘙荟|陵薮市朝", - "to": "2022-09-09 17:59:59", - }, - { - "five": ["宵宫"], - "four": ["云堇", "辛焱", "班尼特"], - "from": "2022-08-02 18:00:00", - "name": "焰色天河", - "to": "2022-08-23 14:59:59", - }, - { - "five": ["枫原万叶", "可莉"], - "four": ["凝光", "鹿野院平藏", "托马"], - "from": "2022-07-13 06:00:00", - "name": "红叶逐荒波", - "to": "2022-08-02 17:59:59", - }, - { - "five": ["荒泷一斗"], - "four": ["烟绯", "芭芭拉", "诺艾尔"], - "from": "2022-06-21 18:00:00", - "name": "鬼门斗宴", - "to": "2022-07-12 14:59:59", - }, - { - "five": ["夜兰", "魈"], - "four": ["烟绯", "芭芭拉", "诺艾尔"], - "from": "2022-05-31 06:00:00", - "name": "素霓伣天|烟火之邀", - "to": "2022-06-21 17:59:59", - }, - { - "five": ["神里绫华"], - "four": ["罗莎莉亚", "早柚", "雷泽"], - "from": "2022-04-19 17:59:59", - "name": "白鹭之庭", - "to": "2022-05-31 05:59:59", - }, - { - "five": ["神里绫人", "温迪"], - "four": ["香菱", "砂糖", "云堇"], - "from": "2022-03-30 06:00:00", - "name": "苍流踏花|杯装之诗", - "to": "2022-04-19 17:59:59", - }, - { - "five": ["雷电将军", "珊瑚宫心海"], - "four": ["辛焱", "九条裟罗", "班尼特"], - "from": "2022-03-08 18:00:00", - "name": "影寂天下人|浮岳虹珠", - "to": "2022-03-29 14:59:59", - }, - { - "five": ["八重神子"], - "four": ["菲谢尔", "迪奥娜", "托马"], - "from": "2022-02-16 06:00:00", - "name": "华紫樱绯", - "to": "2022-03-08 17:59:59", - }, - { - "five": ["甘雨", "钟离"], - "four": ["行秋", "北斗", "烟绯"], - "from": "2022-01-25 18:00:00", - "name": "浮生孰来|陵薮市朝", - "to": "2022-02-15 14:59:59", - }, - { - "five": ["申鹤", "魈"], - "four": ["云堇", "凝光", "重云"], - "from": "2022-01-05 06:00:00", - "name": "出尘入世|烟火之邀", - "to": "2022-01-25 17:59:59", - }, - { - "five": ["荒泷一斗"], - "four": ["五郎", "芭芭拉", "香菱"], - "from": "2021-12-14 18:00:00", - "name": "鬼门斗宴", - "to": "2022-01-04 14:59:59", - }, - { - "five": ["阿贝多", "优菈"], - "four": ["班尼特", "诺艾尔", "罗莎莉亚"], - "from": "2021-11-24 06:00:00", - "name": "深秘之息|浪涌之瞬", - "to": "2021-12-14 17:59:59", - }, - { - "five": ["胡桃"], - "four": ["托马", "迪奥娜", "早柚"], - "from": "2021-11-02 18:00:00", - "name": "赤团开时", - "to": "2021-11-23 14:59:59", - }, - { - "five": ["达达利亚"], - "four": ["凝光", "重云", "烟绯"], - "from": "2021-10-13 06:00:00", - "name": "暂别冬都", - "to": "2021-11-02 17:59:59", - }, - { - "five": ["珊瑚宫心海"], - "four": ["罗莎莉亚", "北斗", "行秋"], - "from": "2021-09-21 18:00:00", - "name": "浮岳虹珠", - "to": "2021-10-12 14:59:59", - }, - { - "five": ["雷电将军"], - "four": ["九条裟罗", "香菱", "砂糖"], - "from": "2021-09-01 06:00:00", - "name": "影寂天下人", - "to": "2021-09-21 17:59:59", - }, - { - "five": ["宵宫"], - "four": ["早柚", "迪奥娜", "辛焱"], - "from": "2021-08-10 18:00:00", - "name": "焰色天河", - "to": "2021-08-31 14:59:59", - }, - { - "five": ["神里绫华"], - "four": ["凝光", "重云", "烟绯"], - "from": "2021-07-21 06:00:00", - "name": "白鹭之庭", - "to": "2021-08-10 17:59:59", - }, - { - "five": ["枫原万叶"], - "four": ["罗莎莉亚", "班尼特", "雷泽"], - "from": "2021-06-29 18:00:00", - "name": "红叶逐荒波", - "to": "2021-07-20 14:59:59", - }, - { - "five": ["可莉"], - "four": ["芭芭拉", "砂糖", "菲谢尔"], - "from": "2021-06-09 06:00:00", - "name": "逃跑的太阳", - "to": "2021-06-29 17:59:59", - }, - { - "five": ["优菈"], - "four": ["辛焱", "行秋", "北斗"], - "from": "2021-05-18 18:00:00", - "name": "浪沫的旋舞", - "to": "2021-06-08 14:59:59", - }, - { - "five": ["钟离"], - "four": ["烟绯", "诺艾尔", "迪奥娜"], - "from": "2021-04-28 06:00:00", - "name": "陵薮市朝", - "to": "2021-05-18 17:59:59", - }, - { - "five": ["达达利亚"], - "four": ["罗莎莉亚", "芭芭拉", "菲谢尔"], - "from": "2021-04-06 18:00:00", - "name": "暂别冬都", - "to": "2021-04-27 14:59:59", - }, - { - "five": ["温迪"], - "four": ["砂糖", "雷泽", "诺艾尔"], - "from": "2021-03-17 06:00:00", - "name": "杯装之诗", - "to": "2021-04-06 15:59:59", - }, - { - "five": ["胡桃"], - "four": ["行秋", "香菱", "重云"], - "from": "2021-03-02 18:00:00", - "name": "赤团开时", - "to": "2021-03-16 14:59:59", - }, - { - "five": ["刻晴"], - "four": ["凝光", "班尼特", "芭芭拉"], - "from": "2021-02-17 18:00:00", - "name": "鱼龙灯昼", - "to": "2021-03-02 15:59:59", - }, - { - "five": ["魈"], - "four": ["迪奥娜", "北斗", "辛焱"], - "from": "2021-02-03 06:00:00", - "name": "烟火之邀", - "to": "2021-02-17 15:59:59", - }, - { - "five": ["甘雨"], - "four": ["香菱", "行秋", "诺艾尔"], - "from": "2021-01-12 18:00:00", - "name": "浮生孰来", - "to": "2021-02-02 14:59:59", - }, - { - "five": ["阿贝多"], - "four": ["菲谢尔", "砂糖", "班尼特"], - "from": "2020-12-23 06:00:00", - "name": "深秘之息", - "to": "2021-01-12 15:59:59", - }, - { - "five": ["钟离"], - "four": ["辛焱", "雷泽", "重云"], - "from": "2020-12-01 18:00:00", - "name": "陵薮市朝", - "to": "2020-12-22 14:59:59", - }, - { - "five": ["达达利亚"], - "four": ["迪奥娜", "北斗", "凝光"], - "from": "2020-11-11 06:00:00", - "name": "暂别冬都", - "to": "2020-12-01 15:59:59", - }, - { - "five": ["可莉"], - "four": ["行秋", "诺艾尔", "砂糖"], - "from": "2020-10-20 18:00:00", - "name": "闪焰的驻足", - "to": "2020-11-10 14:59:59", - }, - { - "five": ["温迪"], - "four": ["芭芭拉", "菲谢尔", "香菱"], - "from": "2020-9-28 06:00:00", - "name": "杯装之诗", - "to": "2020-10-18 17:59:59", - }, -] diff --git a/metadata/pool/pool_302.py b/metadata/pool/pool_302.py deleted file mode 100644 index 04a7ef2..0000000 --- a/metadata/pool/pool_302.py +++ /dev/null @@ -1,317 +0,0 @@ -POOL_302 = [ - { - "five": ["千夜浮梦", "圣显之钥"], - "four": ["西福斯的月光", "西风大剑", "匣里灭辰", "祭礼残章", "绝弦"], - "from": "2023-04-12 06:00:00", - "name": "神铸赋形", - "to": "2023-05-02 17:59:59", - }, - { - "five": ["息灾", "雾切之回光"], - "four": ["暗巷的酒与诗", "祭礼剑", "钟剑", "西风长枪", "西风猎弓"], - "from": "2023-03-21 18:00:00", - "name": "神铸赋形", - "to": "2023-04-11 14:59:59", - }, - { - "five": ["苇海信标", "赤沙之杖"], - "four": ["暗巷闪光", "暗巷猎手", "祭礼大剑", "匣里灭辰", "昭心"], - "from": "2023-03-01 06:00:00", - "name": "神铸赋形", - "to": "2023-03-21 17:59:59", - }, - { - "five": ["护摩之杖", "若水"], - "four": ["千岩古剑", "西风剑", "匣里灭辰", "西风秘典", "弓藏"], - "from": "2023-02-07 18:00:00", - "name": "神铸赋形", - "to": "2023-02-28 14:59:59", - }, - { - "five": ["裁叶萃光", "和璞鸢"], - "four": ["千岩长枪", "笛剑", "雨裁", "流浪乐章", "祭礼弓"], - "from": "2023-01-18 06:00:00", - "name": "神铸赋形", - "to": "2023-02-07 17:59:59", - }, - { - "five": ["薙草之稻光", "波乱月白经津"], - "four": ["恶王丸", "曚云之月", "匣里龙吟", "西风长枪", "祭礼残章"], - "from": "2022-12-27 18:00:00", - "name": "神铸赋形", - "to": "2023-01-17 14:59:59", - }, - { - "five": ["图莱杜拉的回忆", "赤角石溃杵"], - "four": ["祭礼剑", "西风大剑", "断浪长鳍", "昭心", "西风猎弓"], - "from": "2022-12-07 06:00:00", - "name": "神铸赋形", - "to": "2022-12-27 17:59:59", - }, - { - "five": ["神乐之真意", "冬极白星"], - "four": ["西风剑", "钟剑", "匣里灭辰", "西风秘典", "绝弦"], - "from": "2022-11-18 18:00:00", - "name": "神铸赋形", - "to": "2022-12-06 14:59:59", - }, - { - "five": ["千夜浮梦", "飞雷之弦振"], - "four": ["笛剑", "祭礼大剑", "西风长枪", "流浪乐章", "弓藏"], - "from": "2022-11-2 06:00:00", - "name": "神铸赋形", - "to": "2022-11-18 17:59:59", - }, - { - "five": ["圣显之钥", "磐岩结绿"], - "four": ["西福斯的月光", "雨裁", "匣里灭辰", "流浪的晚星", "祭礼弓"], - "from": "2022-10-14 18:00:00", - "name": "神铸赋形", - "to": "2022-11-01 14:59:59", - }, - { - "five": ["赤沙之杖", "终末嗟叹之诗"], - "four": ["匣里龙吟", "玛海菈的水色", "西风长枪", "祭礼残章", "西风猎弓"], - "from": "2022-09-28 06:00:00", - "name": "神铸赋形", - "to": "2022-10-14 17:59:59", - }, - { - "five": ["阿莫斯之弓", "不灭月华"], - "four": ["祭礼剑", "西风大剑", "匣里灭辰", "昭心", "弓藏"], - "from": "2022-09-09 18:00:00", - "name": "神铸赋形", - "to": "2022-09-27 14:59:59", - }, - { - "five": ["猎人之径", "贯虹之槊"], - "four": ["西风剑", "钟剑", "西风长枪", "西风秘典", "绝弦"], - "from": "2022-08-24 06:00:00", - "name": "神铸赋形", - "to": "2022-09-09 17:59:59", - }, - { - "five": ["飞雷之弦振", "斫峰之刃"], - "four": ["暗巷的酒与诗", "暗巷猎手", "笛剑", "祭礼大剑", "匣里灭辰"], - "from": "2022-08-02 18:00:00", - "name": "神铸赋形", - "to": "2022-08-23 14:59:59", - }, - { - "five": ["苍古自由之誓", "四风原典"], - "four": ["千岩古剑", "匣里龙吟", "匣里灭辰", "祭礼残章", "绝弦"], - "from": "2022-07-13 06:00:00", - "name": "神铸赋形", - "to": "2022-08-02 17:59:59", - }, - { - "five": ["赤角石溃杵", "尘世之锁"], - "four": ["千岩古剑", "匣里龙吟", "匣里灭辰", "祭礼残章", "绝弦"], - "from": "2022-06-21 18:00:00", - "name": "神铸赋形", - "to": "2022-07-12 14:59:59", - }, - { - "five": ["若水", "和璞鸢"], - "four": ["千岩长枪", "祭礼剑", "西风大剑", "昭心", "祭礼弓"], - "from": "2022-05-31 06:00:00", - "name": "神铸赋形", - "to": "2022-06-21 17:59:59", - }, - { - "five": ["雾切之回光", "无工之剑"], - "four": ["西风剑", "钟剑", "西风长枪", "西风秘典", "西风猎弓"], - "from": "2022-04-19 17:59:59", - "name": "神铸赋形", - "to": "2022-05-31 05:59:59", - }, - { - "five": ["波乱月白经津", "终末嗟叹之诗"], - "four": ["弓藏", "笛剑", "流浪乐章", "匣里灭辰", "祭礼大剑"], - "from": "2022-03-30 06:00:00", - "name": "神铸赋形", - "to": "2022-04-19 17:59:59", - }, - { - "five": ["薙草之稻光", "不灭月华"], - "four": ["恶王丸", "曚云之月", "匣里龙吟", "西风长枪", "祭礼残章"], - "from": "2022-03-08 18:00:00", - "name": "神铸赋形", - "to": "2022-03-29 14:59:59", - }, - { - "five": ["神乐之真意", "磐岩结绿"], - "four": ["祭礼剑", "雨裁", "断浪长鳍", "昭心", "绝弦"], - "from": "2022-02-16 06:00:00", - "name": "神铸赋形", - "to": "2022-03-08 17:59:59", - }, - { - "five": ["贯虹之槊", "阿莫斯之弓"], - "four": ["西风剑", "千岩古剑", "匣里灭辰", "西风秘典", "祭礼弓"], - "from": "2022-01-25 18:00:00", - "name": "神铸赋形", - "to": "2022-02-15 14:59:59", - }, - { - "five": ["息灾", "和璞鸢"], - "four": ["笛剑", "西风大剑", "千岩长枪", "流浪乐章", "西风猎弓"], - "from": "2022-01-05 06:00:00", - "name": "神铸赋形", - "to": "2022-01-25 17:59:59", - }, - { - "five": ["赤角石溃杵", "天空之翼"], - "four": ["暗巷闪光", "钟剑", "西风长枪", "祭礼残章", "幽夜华尔兹"], - "from": "2021-12-14 18:00:00", - "name": "神铸赋形", - "to": "2022-01-04 14:59:59", - }, - { - "five": ["苍古自由之誓", "松籁响起之时"], - "four": ["匣里龙吟", "祭礼大剑", "匣里灭辰", "暗巷的酒与诗", "暗巷猎手"], - "from": "2021-11-24 06:00:00", - "name": "神铸赋形", - "to": "2021-12-14 17:59:59", - }, - { - "five": ["护摩之杖", "终末嗟叹之诗"], - "four": ["祭礼剑", "雨裁", "断浪长鳍", "流浪乐章", "曚云之月"], - "from": "2021-11-02 18:00:00", - "name": "神铸赋形", - "to": "2021-11-23 14:59:59", - }, - { - "five": ["冬极白星", "尘世之锁"], - "four": ["西风剑", "恶王丸", "西风长枪", "昭心", "弓藏"], - "from": "2021-10-13 06:00:00", - "name": "神铸赋形", - "to": "2021-11-02 17:59:59", - }, - { - "five": ["不灭月华", "磐岩结绿"], - "four": ["笛剑", "西风大剑", "匣里灭辰", "西风秘典", "绝弦"], - "from": "2021-09-21 18:00:00", - "name": "神铸赋形", - "to": "2021-10-12 14:59:59", - }, - { - "five": ["薙草之稻光", "无工之剑"], - "four": ["匣里龙吟", "钟剑", "西风长枪", "流浪乐章", "祭礼弓"], - "from": "2021-09-01 06:00:00", - "name": "神铸赋形", - "to": "2021-09-21 17:59:59", - }, - { - "five": ["飞雷之弦振", "天空之刃"], - "four": ["祭礼剑", "雨裁", "匣里灭辰", "祭礼残章", "西风猎弓"], - "from": "2021-08-10 18:00:00", - "name": "神铸赋形", - "to": "2021-08-31 14:59:59", - }, - { - "five": ["雾切之回光", "天空之脊"], - "four": ["西风剑", "祭礼大剑", "西风长枪", "西风秘典", "绝弦"], - "from": "2021-07-21 06:00:00", - "name": "神铸赋形", - "to": "2021-08-10 17:59:59", - }, - { - "five": ["苍古自由之誓", "天空之卷"], - "four": ["暗巷闪光", "西风大剑", "匣里灭辰", "暗巷的酒与诗", "暗巷猎手"], - "from": "2021-06-29 18:00:00", - "name": "神铸赋形", - "to": "2021-07-20 14:59:59", - }, - { - "five": ["天空之傲", "四风原典"], - "four": ["匣里龙吟", "钟剑", "西风长枪", "流浪乐章", "幽夜华尔兹"], - "from": "2021-06-09 06:00:00", - "name": "神铸赋形", - "to": "2021-06-29 17:59:59", - }, - { - "five": ["松籁响起之时", "风鹰剑"], - "four": ["祭礼剑", "雨裁", "匣里灭辰", "祭礼残章", "弓藏"], - "from": "2021-05-18 18:00:00", - "name": "神铸赋形", - "to": "2021-06-08 14:59:59", - }, - { - "five": ["斫峰之刃", "尘世之锁"], - "four": ["笛剑", "千岩古剑", "祭礼弓", "昭心", "千岩长枪"], - "from": "2021-04-28 06:00:00", - "name": "神铸赋形", - "to": "2021-05-18 17:59:59", - }, - { - "five": ["天空之翼", "四风原典"], - "four": ["西风剑", "祭礼大剑", "暗巷猎手", "西风秘典", "西风长枪"], - "from": "2021-04-06 18:00:00", - "name": "神铸赋形", - "to": "2021-04-27 14:59:59", - }, - { - "five": ["终末嗟叹之诗", "天空之刃"], - "four": ["暗巷闪光", "西风大剑", "西风猎弓", "暗巷的酒与诗", "匣里灭辰"], - "from": "2021-03-17 06:00:00", - "name": "神铸赋形", - "to": "2021-04-06 15:59:59", - }, - { - "five": ["护摩之杖", "狼的末路"], - "four": ["匣里龙吟", "千岩古剑", "祭礼弓", "流浪乐章", "千岩长枪"], - "from": "2021-02-23 18:00:00", - "name": "神铸赋形", - "to": "2021-03-16 14:59:59", - }, - { - "five": ["磐岩结绿", "和璞鸢"], - "four": ["笛剑", "祭礼大剑", "弓藏", "昭心", "西风长枪"], - "from": "2021-02-03 06:00:00", - "name": "神铸赋形", - "to": "2021-02-23 15:59:59", - }, - { - "five": ["阿莫斯之弓", "天空之傲"], - "four": ["祭礼剑", "钟剑", "匣里灭辰", "昭心", "西风猎弓"], - "from": "2021-01-12 18:00:00", - "name": "神铸赋形", - "to": "2021-02-02 14:59:59", - }, - { - "five": ["斫峰之刃", "天空之卷"], - "four": ["西风剑", "西风大剑", "西风长枪", "祭礼残章", "绝弦"], - "from": "2020-12-23 06:00:00", - "name": "神铸赋形", - "to": "2021-01-12 15:59:59", - }, - { - "five": ["贯虹之槊", "无工之剑"], - "four": ["匣里龙吟", "钟剑", "西风秘典", "西风猎弓", "匣里灭辰"], - "from": "2020-12-01 18:00:00", - "name": "神铸赋形", - "to": "2020-12-22 14:59:59", - }, - { - "five": ["天空之翼", "尘世之锁"], - "four": ["笛剑", "雨裁", "昭心", "弓藏", "西风长枪"], - "from": "2020-11-11 06:00:00", - "name": "神铸赋形", - "to": "2020-12-01 15:59:59", - }, - { - "five": ["四风原典", "狼的末路"], - "four": ["祭礼剑", "祭礼大剑", "祭礼残章", "祭礼弓", "匣里灭辰"], - "from": "2020-10-20 18:00:00", - "name": "神铸赋形", - "to": "2020-11-10 14:59:59", - }, - { - "five": ["风鹰剑", "阿莫斯之弓"], - "four": ["祭礼剑", "祭礼大剑", "祭礼残章", "祭礼弓", "匣里灭辰"], - "from": "2020-09-28 06:00:00", - "name": "神铸赋形", - "to": "2020-10-18 17:59:59", - }, -] diff --git a/metadata/shortname.py b/metadata/shortname.py index 2c76e98..76bd163 100644 --- a/metadata/shortname.py +++ b/metadata/shortname.py @@ -5,700 +5,107 @@ from typing import List from metadata.genshin import WEAPON_DATA -__all__ = ["roles", "weapons", "roleToId", "roleToName", "weaponToName", "weaponToId", "not_real_roles", "roleToTag"] +__all__ = ["roles", "light_cones", "roleToId", "roleToName", "lightConeToName", "lightConeToId", "not_real_roles", "roleToTag"] # noinspection SpellCheckingInspection roles = { - 20000000: [ - "旅行者", - "主角", - "卑鄙的外乡人", - "荣誉骑士", - "爷", - "履刑者", - "人之子", - "命定之人", - "荣誉骑士", - "小可爱", # 丽莎 - "小家伙", # 八重神子 - "金发异乡人", - "大黄金钓鱼手", # 派蒙 - "黄毛阿姨", - "黄毛叔叔", - "大黄倭瓜那菈", - ], - 10000002: ["神里绫华", "ayaka", "kamisato ayaka", "神里", "绫华", "神里凌华", "凌华", "白鹭公主", "神里大小姐", "冰骗骗花", "龟龟"], - 10000003: ["琴", "jean", "团长", "代理团长", "琴团长", "蒲公英骑士", "蒙德砍王", "骑士团的魂"], - 10000005: ["空", "aether", "男主", "男主角", "龙哥", "空哥", "王子"], - 10000006: ["丽莎", "lisa", "图书管理员", "图书馆管理员", "蔷薇魔女"], - 10000007: ["荧", "lumine", "女主", "女主角", "莹", "萤", "黄毛阿姨", "荧妹", "公主殿下"], - 10000014: ["芭芭拉", "barbara", "巴巴拉", "拉粑粑", "拉巴巴", "内鬼", "加湿器", "闪耀偶像", "偶像", "蒙德辣王"], - 10000015: ["凯亚", "kaeya", "盖亚", "凯子哥", "凯鸭", "矿工", "矿工头子", "骑兵队长", "凯子", "凝冰渡海真君", "花脸猫"], - 10000016: [ - "迪卢克", - "diluc", - "卢姥爷", - "姥爷", - "卢老爷", - "卢锅巴", - "正义人", - "正e人", - "正E人", - "卢本伟", - "暗夜英雄", - "卢卢伯爵", - "落魄了", - "落魄了家人们", - "哦哦哦", - "前夫哥", - "在此烧鸟真君", - "E键三连真君", - ], - 10000020: [ - "雷泽", - "razor", - "狼少年", - "狼崽子", - "狼崽", - "卢皮卡", - "小狼", - "小狼狼", - "小狼狗", - "小赛诺", - "替身使者", - "须佐狼乎", - "蒙德砍王", - "炸矿之星", - ], - 10000021: [ - "安柏", - "amber", - "安伯", - "兔兔伯爵", - "飞行冠军", - "侦查骑士", - "侦察骑士", - "点火姬", - "点火机", - "打火机", - "打火姬", - "燃炬焚棘真君", - "初代目提瓦特第一火弓", - ], - 10000022: [ - "温迪", - "venti", - "barbatos", - "温蒂", - "风神", - "卖唱的", - "巴巴托斯", - "巴巴脱丝", - "芭芭托斯", - "芭芭脱丝", - "干点正事", - "不干正事", - "吟游诗人", - "诶嘿", - "唉嘿", - "摸鱼", - "最弱最丢人的七神", - "卖唱的大哥哥", - "巴巴托斯大人", - "欸嘿聚怪真君", - "荻花洲的吹笛人", - "直升机", - ], - 10000023: [ - "香菱", - "xiangling", - "香玲", - "锅巴", - "厨师", - "万民堂厨师", - "香师傅", - "哪吒", - "锅巴发射器", - "无敌风火轮真君", - "舌尖上的璃月", - "提瓦特枪王", - ], - 10000024: ["北斗", "beidou", "大姐头", "大姐", "无冕的龙王", "稻妻人形继电石"], - 10000025: ["行秋", "xingqiu", "秋秋人", "秋妹妹", "书呆子", "水神", "飞云商会二少爷", "秋秋人", "6星水神", "枕玉老师"], - 10000026: [ - "魈", - "xiao", - "杏仁豆腐", - "打桩机", - "插秧", - "三眼五显仙人", - "三眼五显真人", - "降魔大圣", - "护法夜叉", - "快乐风男", - "无聊", - "靖妖傩舞", - "矮子仙人", - "三点五尺仙人", - "跳跳虎", - "护法夜叉大将", - "金鹏大将", - "这里无能真君", - "抬头不见低头见真君", - "跳跳虎", - "随叫随到真君", - "成天冷着脸的帅气小哥", - ], - 10000027: ["凝光", "ningguang", "富婆", "天权", "天权星", "寻山见矿真君"], - 10000029: [ - "可莉", - "klee", - "嘟嘟可", - "火花骑士", - "蹦蹦炸弹", - "炸鱼", - "放火烧山", - "放火烧山真君", - "蒙德最强战力", - "逃跑的太阳", - "啦啦啦", - "哒哒哒", - "炸弹人", - "禁闭室", - "艾莉丝的女儿", - "阿贝多的义妹", - "火化骑士", - "炸鱼禁闭真君", - "蒙德小坦克", - "骑士团团宠", - ], - 10000030: [ - "钟离", - "zhongli", - "morax", - "摩拉克斯", - "岩王爷", - "岩神", - "钟师傅", - "天动万象", - "岩王帝君", - "未来可期", - "帝君", - "契约之神", - "社会废人", - "未来可期真君", - "废人养成器", - "听书人", - ], - 10000031: ["菲谢尔", "fischl", "皇女", "小艾米", "小艾咪", "奥兹", "断罪皇女", "中二病", "中二少女", "中二皇女", "奥兹发射器"], - 10000032: ["班尼特", "bennett", "点赞哥", "点赞", "倒霉少年", "倒霉蛋", "霹雳闪雷真君", "班神", "班爷", "倒霉", "火神", "六星真神"], - 10000033: [ - "达达利亚", - "tartaglia", - "childe", - "ajax", - "达达鸭", - "达达利鸭", - "公子", - "玩具销售员", - "玩具推销员", - "钱包", - "鸭鸭", - "愚人众末席", - "至冬国驻璃月港玩具推销员主管", - "钟离的钱包", - "近战弓兵", - "在蒙德认识的冒险家", - "永别冬都", - "汤达人", - "大貉妖处理专家", - ], - 10000034: ["诺艾尔", "noelle", "女仆", "高达", "岩王帝姬", "山吹", "冰萤术士", "岩王帝姬"], - 10000035: ["七七", "qiqi", "僵尸", "肚饿真君", "度厄真君", "77", "起死回骸童子", "救苦度厄真君", "椰羊创始人", "不卜庐砍王", "不卜庐剑圣"], - 10000036: ["重云", "chongyun", "纯阳之体", "冰棍", "驱邪世家", "大外甥"], - 10000037: ["甘雨", "ganyu", "椰羊", "椰奶", "鸡腿猎人", "咕噜咕噜滚下山真君", "肝雨", "走路上山真君"], - 10000038: [ - "阿贝多", - "albedo", - "可莉哥哥", - "升降机", - "升降台", - "电梯", - "白垩之子", - "贝爷", - "白垩", - "阿贝少", - "花呗多", - "阿贝夕", - "abd", - "阿师傅", - "小王子", - "调查小队队长", - "西风骑士团首席炼金术师", - "白垩老师", - "电梯人", - "蒙德岩神", - "平平无奇", - "蒙德NPC", - ], - 10000039: ["迪奥娜", "diona", "迪欧娜", "dio", "dio娜", "冰猫", "猫猫", "猫娘", "喵喵", "调酒师"], - 10000041: [ - "莫娜", - "mona", - "穷鬼", - "穷光蛋", - "穷", - "莫纳", - "占星术士", - "占星师", - "讨龙真君", - "半部讨龙真君", - "阿斯托洛吉斯·莫娜·梅姬斯图斯", - "astrologist mona megistus", - "梅姬斯图斯", - "梅姬斯图斯卿", - "梅姬", - "半部讨龙真君", - ], - 10000042: [ - "刻晴", - "keqing", - "刻情", - "氪晴", - "刻师傅", - "刻师父", - "牛杂", - "牛杂师傅", - "斩尽牛杂", - "免疫", - "免疫免疫", - "屁斜剑法", - "玉衡星", - "阿晴", - "啊晴", - "得不到的女人", - "金丝虾球真君", - "璃月雷神", - "刻猫猫", - ], - 10000043: ["砂糖", "sucrose", "雷莹术士", "雷萤术士", "雷荧术士"], - 10000044: ["辛焱", "xinyan", "辛炎", "黑妹", "摇滚"], - 10000045: [ - "罗莎莉亚", - "rosaria", - "罗莎莉娅", - "白色史莱姆", - "白史莱姆", - "修女", - "罗莎利亚", - "罗莎利娅", - "罗沙莉亚", - "罗沙莉娅", - "罗沙利亚", - "罗沙利娅", - "萝莎莉亚", - "萝莎莉娅", - "萝莎利亚", - "萝莎利娅", - "萝沙莉亚", - "萝沙莉娅", - "萝沙利亚", - "萝沙利娅", - ], - 10000046: [ - "胡桃", - "hutao", - "hu tao", - "胡淘", - "往生堂堂主", - "火化", - "抬棺的", - "蝴蝶", - "核桃", - "堂主", - "胡堂主", - "雪霁梅香", - "赤团开时", - "黑无常", - "嘘嘘鬼王", - "琪亚娜", - "薪炎之律者", - ], - 10000047: ["枫原万叶", "kazuha", "kaedehara kazuha", "万叶", "叶天帝", "天帝", "人型气象观测台", "浪人武士"], - 10000048: ["烟绯", "yanfei", "烟老师", "律师", "罗翔", "璃月港的知名律法咨询师", "璃月罗翔", "铁人三项真君"], - 10000049: [ - "宵宫", - "yoimiya", - "霄宫", - "烟花", - "肖宫", - "肖工", - "绷带女孩", - "夏祭的女王", - "地对鸽导弹", - "打火姬二代目", - "长野原加特林", - "花见坂军火商", - ], - 10000050: ["托马", "thoma", "家政官", "太郎丸", "地头蛇", "男仆", "男妈妈"], - 10000051: ["优菈", "eula", "优拉", "尤拉", "尤菈", "浪花骑士", "记仇", "喷嚏记仇真君"], - 10000052: [ - "雷电将军", - "shougun", - "raiden shogun", - "raiden", - "ei", - "raiden ei", - "baal", - "雷神", - "将军", - "雷军", - "巴尔", - "阿影", - "影", - "巴尔泽布", - "煮饭婆", - "奶香一刀", - "无想一刀", - "宅女", - "大御所大人", - "鸣神", - "永恒之神", - "姐控", - "不会做饭真君", - "宅女程序员", - "奶香一刀真君", - "雷电芽衣", - "又哭又闹真君", - "御建鸣神主尊大御所大人", - ], - 10000053: ["早柚", "sayu", "小狸猫", "狸猫", "咕噜咕噜赶路真君", "柚岩龙蜥", "善于潜行的矮子", "专业人士"], - 10000054: [ - "珊瑚宫心海", - "kokomi", - "sangonomiya kokomi", - "心海", - "军师", - "珊瑚宫", - "书记", - "观赏鱼", - "水母", - "鱼", - "现人神巫女", - "宅家派节能军师", - "藤原千花", - "能量管理大师", - "五星观赏鱼", - "海天后", - "深海舌鲆鱼小姐", - ], - 10000055: ["五郎", "gorou", "柴犬", "土狗", "希娜", "希娜小姐", "海祇岛的小狗大将", "修勾", "五郎大将的朋友", "小狗勾"], - 10000056: [ - "九条裟罗", - "sara", - "kujou sara", - "九条", - "九条沙罗", - "裟罗", - "条家的养子", - "雷系班尼特", - "雷神单推头子", - "珊瑚宫心海的冤家", - "荒泷一斗的冤家", - "外置暴伤", - "维密天使", - ], - 10000057: [ - "荒泷一斗", - "itto", - "arataki itto", - "荒龙一斗", - "荒泷天下第一斗", - "一斗", - "一抖", - "荒泷", - "1斗", - "牛牛", - "斗子哥", - "牛子哥", - "牛子", - "孩子王", - "斗虫", - "巧乐兹", - "放牛的", - "岩丘丘萨满", - "伐伐伐伐伐木工", - "希娜小姐的榜一大哥", - ], - 10000058: [ - "八重神子", - "miko", - "yae miko", - "八重", - "神子", - "狐狸", - "想得美哦", - "巫女", - "屑狐狸", - "骚狐狸", - "八重宫司", - "婶子", - "小八", - "白辰血脉的后裔", - "兼具智慧和美貌的八重神子大人", - "稻妻老八", - "雷丘丘萨满", - "八重樱", - "嗑瓜子", - "小奥兹", - "玲珑油豆腐小姐", - ], - 10000059: [ - "鹿野院平藏", - "heizou", - "shikanoin heizou", - "heizo", - "鹿野苑", - "鹿野院", - "平藏", - "鹿野苑平藏", - "鹿野", - "小鹿", - "天领奉行侦探", - "鹿野奈奈的表弟", - "风拳前锋军", - "拳师", - "名侦探柯南", - "捕快展昭", - ], - 10000060: ["夜兰", "yelan", "夜阑", "叶澜", "腋兰", "夜天后", "自称就职于总务司的神秘人士", "岩上茶室老板", "夜上海", "胸怀大痣"], - 10000062: ["埃洛伊", "aloy", "异界的救世主"], - 10000063: ["申鹤", "shenhe", "神鹤", "小姨", "小姨子", "审鹤", "仙家弟子", "驱邪世家旁", "药材杀手"], - 10000064: ["云堇", "yunjin", "yun jin", "云瑾", "云先生", "云锦", "神女劈观", "岩北斗", "五更琉璃"], - 10000065: [ - "久岐忍", - "kuki", - "kuki shinobu", - "shinobu", - "97忍", - "小忍", - "久歧忍", - "97", - "茄忍", - "阿忍", - "忍姐", - "鬼之副手", - "不是忍者的忍者", - "医疗忍者", - "考证专家", - ], - 10000066: [ - "神里绫人", - "ayato", - "kamisato ayato", - "绫人", - "神里凌人", - "凌人", - "0人", - "神人", - "零人", - "大舅哥", - "神里绫华的兄长", - "荒泷一斗的虫友", - "奥托", - "奥托·阿波卡利斯", - "奥托主教", - "藏镜仕男", - "袖藏奶茶真君", - "真正的甘雨", - "可莉的爷爷", - ], - 10000067: [ - "柯莱", - "collei", - "柯来", - "科莱", - "科来", - "小天使", - "须弥安柏", - "须弥飞行冠军", - "见习巡林员", - "克莱", - "草安伯", - "道成林见习巡林员", - "提纳里的学徒", - "安柏的挚友", - "兰那罗奶奶", - ], - 10000068: ["多莉", "dori", "多利", "多力", "多丽", "奸商", "须弥百货商人", "歌玛哈巴依老爷", "艾尔卡萨扎莱宫之主"], - 10000069: [ - "提纳里", - "tighnari", - "小提", - "提那里", - "缇娜里", - "提哪里", - "驴", - "柯莱老师", - "柯莱师傅", - "巡林官", - "提那里", - "耳朵很好摸", - "道成林巡林官", - "柯莱的师父", - ], - 10000070: ["妮露", "nilou", "尼露", "祖拜尔剧场之星", "红牛"], - 10000071: ["赛诺", "cyno", "赛洛", "大风纪官", "大风机关", "胡狼头大人", "夹击妹抖", "游戏王", "冷笑话爱好者", "牌佬", "沙漠死神", "胡狼"], - 10000072: ["坎蒂丝", "candace", "坎迪斯", "水北斗", "赤王后裔", "阿如村守护者"], - 10000073: [ - "纳西妲", - "nahida", - "buer", - "草王", - "草神", - "小吉祥草王", - "草萝莉", - "艹萝莉", - "羽毛球", - "布耶尔", - "纳西坦", - "摩诃善法大吉祥智慧主", - "智慧之神", - "草木之主", - "草神大人", - ], - 10000074: ["莱依拉", "layla", "拉一拉", "莱伊拉", "莫娜的同行", "西琳", "黑塔"], - 10000075: [ - "流浪者", - "wanderer", - "散兵", - "伞兵", - "伞兵一号", - "雷电国崩", - "国崩", - "卢本伟", - "雷电大炮", - "雷大炮", - "大炮", - "sb", - "斯卡拉姆齐", - "倾奇者", - "黑主", - "崩崩小圆帽", - "七叶寂照秘密主", - "七彩阳光秘密主", - "正机之神", - "伪神", - ], - 10000076: ["珐露珊", "faruzan", "法露珊", "珐妹", "初音", "初音未来", "miku", "发露姗", "发姐", "法姐", "百岁珊", "百岁山", "童姥", "知论派名宿"], - 10000077: ["瑶瑶", "yaoyao", "遥遥", "遥遥无期", "香菱师妹", "萝卜", "四星草奶"], - 10000078: ["艾尔海森", "alhaitham", "爱尔海森", "艾尔海参", "艾尔", "海森", "海参", "海神", "埃尔海森", "草刻晴", "书记官", "代理大贤者"], - 10000079: ["迪希雅", "dehya", "狮女", "狮子", "腕豪", "女拳"], - 10000080: ["米卡", "mika", "镜音连", "咪卡", "小米"], - 10000081: ["白术", "baizhuer"], - 10000082: ["卡维", "kaveh"], + 1001: ['三月七'], + 1002: ['丹恒'], + 1003: ['姬子'], + 1004: ['瓦尔特'], + 1005: ['卡芙卡'], + 1006: ['银狼'], + 1008: ['阿兰'], + 1009: ['艾丝妲'], + 1013: ['黑塔'], + 1101: ['布洛妮娅'], + 1102: ['希儿'], + 1103: ['希露瓦'], + 1104: ['杰帕德'], + 1105: ['娜塔莎'], + 1106: ['佩拉'], + 1107: ['克拉拉'], + 1108: ['桑博'], + 1109: ['虎克'], + 1201: ['青雀'], + 1202: ['停云'], + 1203: ['罗刹'], + 1204: ['景元'], + 1206: ['素裳'], + 1209: ['彦卿'], + 1211: ['白露'], + 8004: ['开拓者'], } -not_real_roles = [10000081, 10000082] -weapons = { - # 1.x - "决斗之枪": ["决斗枪", "决斗", "月卡枪"], - "螭骨剑": ["螭骨", "丈育剑", "离骨剑", "月卡大剑"], - "黑剑": ["月卡剑"], - "苍翠猎弓": ["绿弓", "月卡弓"], - "匣里日月": ["日月"], - "匣里灭辰": ["灭辰"], - "匣里龙吟": ["龙吟"], - "流月针": ["针"], - "流浪乐章": ["赌狗书", "赌狗乐章", "赌狗"], - "昭心": ["糟心"], - "讨龙英杰谭": ["讨龙"], - "神射手之誓": ["脚气弓", "神射手"], - "黑缨枪": ["史莱姆枪"], - "黑岩刺枪": ["黑岩枪"], - "黑岩战弓": ["黑岩弓"], - "天空之刃": ["天空剑"], - "天空之傲": ["天空大剑"], - "天空之脊": ["天空枪", "薄荷枪", "薄荷"], - "天空之卷": ["天空书", "厕纸"], - "天空之翼": ["天空弓"], - "四风原典": ["四风", "可莉专武"], - "阿莫斯之弓": ["阿莫斯", "ams", "痛苦弓", "甘雨专武"], - "狼的末路": ["狼末"], - "和璞鸢": ["鸟枪", "绿枪", "魈专武"], - "风鹰剑": ["风鹰"], - "试作斩岩": ["斩岩"], - "试作星镰": ["星镰"], - "试作金珀": ["金珀"], - "试作古华": ["古华"], - "试作澹月": ["澹月"], - "万国诸海图谱": ["万国", "万国诸海"], - "尘世之锁": ["尘世锁", "尘世", "盾书", "锁"], - "无工之剑": ["蜈蚣", "蜈蚣大剑", "无工大剑", "盾大剑", "无工"], - "贯虹之槊": ["贯虹", "岩枪", "盾枪", "钟离专武"], - "斫峰之刃": ["斫峰", "盾剑"], - "腐殖之剑": ["腐殖", "腐殖剑"], - "雪葬的星银": ["雪葬", "星银", "雪葬星银", "雪山大剑"], - "磐岩结绿": ["绿箭", "绿剑"], - "护摩之杖": ["护摩", "护摩枪", "护膜", "胡桃专武"], - "千岩长枪": ["千岩枪"], - "千岩古剑": ["千岩剑", "千岩大剑"], - "西风长枪": ["西风枪"], - "西风猎弓": ["西风弓"], - "西风秘典": ["西风书"], - "暗巷闪光": ["暗巷剑", "暗巷小剑", "暗巷"], - "暗巷猎手": ["暗巷弓"], - "暗巷的酒与诗": ["暗巷法器", "暗巷书"], - "风花之颂": ["风花弓"], - "终末嗟叹之诗": ["终末", "终末弓", "叹气弓", "乐团弓", "温迪专武"], - "松籁响起之时": ["松籁", "乐团大剑", "松剑", "优菈专武"], - "苍古自由之誓": ["苍古", "乐团剑", "枫原万叶专武"], - "幽夜华尔兹": ["幽夜", "幽夜弓", "华尔兹", "皇女弓"], - "嘟嘟可故事集": ["嘟嘟可"], - # 2.x - "天目影打刀": ["天目刀", "天目"], - "桂木斩长正": ["桂木", "斩长正"], - "喜多院十文字": ["喜多院", "十文字"], - "破魔之弓": ["破魔弓", "破魔"], - "白辰之环": ["白辰", "白辰环"], - "雾切之回光": ["雾切", "神里绫华专武"], - "飞雷之弦振": ["飞雷", "飞雷弓", "宵宫专武"], - "薙草之稻光": ["薙草", "稻光", "薙草稻光", "马尾枪", "马尾", "薙刀", "雷电将军专武"], - "不灭月华": ["月华", "珊瑚宫心海专武"], - "「渔获」": ["鱼叉", "渔叉", "渔获"], - "衔珠海皇": ["海皇", "咸鱼剑", "咸鱼大剑"], - "冬极白星": ["冬极", "达达利亚专武"], - "曚云之月": ["曚云弓", "曚云"], - "恶王丸": ["断浪大剑"], - "断浪长鳍": ["断浪", "断浪长枪", "断浪枪"], - "辰砂之纺锤": ["辰砂", "辰砂纺锤", "纺锤", "阿贝多专武"], - "赤角石溃杵": ["赤角", "石溃杵", "荒泷一斗专武", "巧乐兹"], - "息灾": ["申鹤专武"], - "神乐之真意": ["神乐", "真意", "八重神子专武"], - "证誓之明瞳": ["证誓", "明瞳", "证誓明瞳"], - "波乱月白经津": ["波乱", "月白", "波乱月白", "经津", "波波津", "神里绫人专武", "钵钵鸡"], - "若水": ["麒麟弓", "夜兰专武"], - "笼钓瓶一心": ["万叶刀", "一心传名刀", "妖刀"], - # 3.x - "猎人之径": ["草弓", "提纳里专武"], - "竭泽": ["鱼弓"], - "原木刀": ["须弥锻造单手剑"], - "森林王器": ["须弥锻造大剑", "原木大剑"], - "贯月矢": ["须弥锻造长枪", "原木枪"], - "盈满之实": ["须弥锻造法器"], - "王下近侍": ["须弥锻造弓", "原木弓"], - "赤沙之杖": ["赤沙", "赛诺专武", "船桨"], - "圣显之钥": ["圣显之钥", "圣显", "不灭剑华", "妮露专武", "板砖"], - "风信之锋": ["风信", "风信锋"], - "西福斯的月光": ["西福斯", "月光", "月光小剑", "月光剑"], - "玛海菈的水色": ["玛海菈", "水色"], - "流浪的晚星": ["晚星"], - "千夜浮梦": ["千夜", "神灯", "茶壶", "夜壶"], - "图莱杜拉的回忆": ["图莱杜拉", "铃铛", "流浪者专武"], - "东花坊时雨": ["东花坊", "时雨", "伞"], - "裁叶萃光": ["萃光", "韭菜刀", "裁叶", "菜叶"], - "饰铁之花": ["饰铁", "铁花"], - "苇海信标": ["苇海", "信标"], - "碧落之珑": ["碧落", "白术专武"], +not_real_roles = [] +light_cones = { + 20000: ['锋镝'], + 20001: ['物穰'], + 20002: ['天倾'], + 20003: ['琥珀'], + 20004: ['幽邃'], + 20005: ['齐颂'], + 20006: ['智库'], + 20007: ['离弦'], + 20008: ['嘉果'], + 20009: ['乐圮'], + 20010: ['戍御'], + 20011: ['渊环'], + 20012: ['轮契'], + 20013: ['灵钥'], + 20014: ['相抗'], + 20015: ['蕃息'], + 20016: ['俱殁'], + 20017: ['开疆'], + 20018: ['匿影'], + 20019: ['调和'], + 20020: ['睿见'], + 21000: ['一场术后对话'], + 21001: ['晚安与睡颜'], + 21002: ['余生的第一天'], + 21003: ['唯有沉默'], + 21004: ['记忆中的模样'], + 21005: ['鼹鼠党欢迎你'], + 21006: ['「我」的诞生'], + 21007: ['同一种心情'], + 21008: ['猎物的视线'], + 21009: ['朗道的选择'], + 21010: ['论剑'], + 21011: ['与行星相会'], + 21012: ['秘密誓心'], + 21013: ['别让世界静下来'], + 21014: ['此时恰好'], + 21015: ['决心如汗珠般闪耀'], + 21016: ['宇宙市场趋势'], + 21017: ['点个关注吧!'], + 21018: ['舞!舞!舞!'], + 21019: ['在蓝天下'], + 21020: ['天才们的休憩'], + 21021: ['等价交换'], + 21022: ['延长记号'], + 21023: ['我们是地火'], + 21024: ['春水初生'], + 21025: ['过往未来'], + 21026: ['汪!散步时间!'], + 21027: ['早餐的仪式感'], + 21028: ['暖夜不会漫长'], + 21029: ['后会有期'], + 21030: ['这就是我啦!'], + 21031: ['重返幽冥'], + 21032: ['镂月裁云之意'], + 21033: ['无处可逃'], + 21034: ['今日亦是和平的一日'], + 23000: ['银河铁道之夜'], + 23001: ['于夜色中'], + 23002: ['无可取代的东西'], + 23003: ['但战斗还未结束'], + 23004: ['以世界之名'], + 23005: ['制胜的瞬间'], + 23010: ['拂晓之前'], + 23012: ['如泥酣眠'], + 23013: ['时节不居'], + 24000: ['记一位星神的陨落'], + 24001: ['星海巡航'], + 24002: ['记忆的质料'] } @@ -720,16 +127,18 @@ def roleToId(name: str) -> int | None: # noinspection PyPep8Naming @functools.lru_cache() -def weaponToName(shortname: str) -> str: - """将武器昵称转为正式名""" - return next((key for key, value in weapons.items() if shortname == key or shortname in value), shortname) +def lightConeToName(shortname: str) -> str: + """将光锥昵称转为正式名""" + shortname = str.casefold(shortname) # 忽略大小写 + return next((value[0] for value in light_cones.values() for name in value if name == shortname), shortname) # noinspection PyPep8Naming @functools.lru_cache() -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) +def lightConeToId(name: str) -> int | None: + """获取光锥ID""" + name = str.casefold(name) + return next((key for key, value in light_cones.items() for n in value if n == name), None) # noinspection PyPep8Naming diff --git a/modules/gacha_log/const.py b/modules/gacha_log/const.py index 6783b8c..31ca98b 100644 --- a/modules/gacha_log/const.py +++ b/modules/gacha_log/const.py @@ -1,13 +1,11 @@ -from genshin.models import BannerType +from genshin.models import StarRailBannerType -PAIMONMOE_VERSION = 3 -UIGF_VERSION = "v2.2" +UIWF_VERSION = "v1.0" GACHA_TYPE_LIST = { - BannerType.NOVICE: "新手祈愿", - BannerType.PERMANENT: "常驻祈愿", - BannerType.WEAPON: "武器祈愿", - BannerType.CHARACTER1: "角色祈愿", - BannerType.CHARACTER2: "角色祈愿", + StarRailBannerType.NOVICE: "新手跃迁", + StarRailBannerType.PERMANENT: "常驻跃迁", + StarRailBannerType.CHARACTER: "角色跃迁", + StarRailBannerType.WEAPON: "光锥跃迁", } diff --git a/modules/gacha_log/log.py b/modules/gacha_log/log.py index 2bf7d93..b6bad2a 100644 --- a/modules/gacha_log/log.py +++ b/modules/gacha_log/log.py @@ -3,19 +3,16 @@ import contextlib import datetime import json from concurrent.futures import ThreadPoolExecutor -from os import PathLike from pathlib import Path -from typing import Dict, IO, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple import aiofiles from genshin import AuthkeyTimeout, Client, InvalidAuthkey -from genshin.models import BannerType -from openpyxl import load_workbook +from genshin.models import StarRailBannerType from core.dependence.assets import AssetsService from metadata.pool.pool import get_pool_by_id -from metadata.shortname import roleToId, weaponToId -from modules.gacha_log.const import GACHA_TYPE_LIST, PAIMONMOE_VERSION +from modules.gacha_log.const import GACHA_TYPE_LIST from modules.gacha_log.error import ( GachaLogAccountNotFound, GachaLogAuthkeyTimeout, @@ -24,7 +21,6 @@ from modules.gacha_log.error import ( GachaLogInvalidAuthkey, GachaLogMixedProvider, GachaLogNotFound, - PaimonMoeGachaLogFileError, ) from modules.gacha_log.models import ( FiveStarItem, @@ -32,16 +28,14 @@ from modules.gacha_log.models import ( GachaItem, GachaLogInfo, ImportType, - ItemType, Pool, - UIGFGachaType, - UIGFInfo, - UIGFItem, - UIGFModel, + UIWFInfo, + UIWFItem, + UIWFModel, ) from utils.const import PROJECT_ROOT -GACHA_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "gacha_log") +GACHA_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "warp_log") GACHA_LOG_PATH.mkdir(parents=True, exist_ok=True) @@ -64,11 +58,11 @@ class GachaLog: async def load_history_info( self, user_id: str, uid: str, only_status: bool = False ) -> Tuple[Optional[GachaLogInfo], bool]: - """读取历史抽卡记录数据 + """读取历史跃迁记录数据 :param user_id: 用户id :param uid: 原神uid :param only_status: 是否只读取状态 - :return: 抽卡记录数据 + :return: 跃迁记录数据 """ file_path = self.gacha_log_path / f"{user_id}-{uid}.json" if only_status: @@ -81,7 +75,7 @@ class GachaLog: return GachaLogInfo(user_id=user_id, uid=uid, update_time=datetime.datetime.now()), False async def remove_history_info(self, user_id: str, uid: str) -> bool: - """删除历史抽卡记录数据 + """删除历史跃迁记录数据 :param user_id: 用户id :param uid: 原神uid :return: 是否删除成功 @@ -102,10 +96,10 @@ class GachaLog: return False async def save_gacha_log_info(self, user_id: str, uid: str, info: GachaLogInfo): - """保存抽卡记录数据 + """保存跃迁记录数据 :param user_id: 用户id :param uid: 原神uid - :param info: 抽卡记录数据 + :param info: 跃迁记录数据 """ save_path = self.gacha_log_path / f"{user_id}-{uid}.json" save_path_bak = self.gacha_log_path / f"{user_id}-{uid}.json.bak" @@ -118,21 +112,21 @@ class GachaLog: # 写入数据 await self.save_json(save_path, info.json()) - async def gacha_log_to_uigf(self, user_id: str, uid: str) -> Optional[Path]: - """抽卡日记转换为 UIGF 格式 + async def gacha_log_to_uiwf(self, user_id: str, uid: str) -> Optional[Path]: + """跃迁日记转换为 UIWF 格式 :param user_id: 用户ID :param uid: 游戏UID - :return: 转换是否成功、转换信息、UIGF文件目录 + :return: 转换是否成功、转换信息、UIWF文件目录 """ data, state = await self.load_history_info(user_id, uid) if not state: raise GachaLogNotFound - save_path = self.gacha_log_path / f"{user_id}-{uid}-uigf.json" - info = UIGFModel(info=UIGFInfo(uid=uid, export_app=ImportType.PaiGram.value, export_app_version="v3"), list=[]) + save_path = self.gacha_log_path / f"{user_id}-{uid}-uiwf.json" + info = UIWFModel(info=UIWFInfo(uid=uid, export_app=ImportType.PaiGram.value, export_app_version="v3"), list=[]) for items in data.item_list.values(): for item in items: info.list.append( - UIGFItem( + UIWFItem( id=item.id, name=item.name, gacha_type=item.gacha_type, @@ -153,9 +147,9 @@ class GachaLog: four_star = len([i for i in data if i.rank_type == "4"]) if total > 50: if total <= five_star * 15: - raise GachaLogFileError("检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。") + raise GachaLogFileError("检测到您将要导入的跃迁记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。") if four_star < five_star: - raise GachaLogFileError("检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。") + raise GachaLogFileError("检测到您将要导入的跃迁记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。") return True except Exception as exc: # pylint: disable=W0703 raise GachaLogFileError from exc @@ -164,7 +158,7 @@ class GachaLog: def import_data_backend(all_items: List[GachaItem], gacha_log: GachaLogInfo, temp_id_data: Dict) -> int: new_num = 0 for item_info in all_items: - pool_name = GACHA_TYPE_LIST[BannerType(int(item_info.gacha_type))] + pool_name = GACHA_TYPE_LIST[StarRailBannerType(int(item_info.gacha_type))] if item_info.id not in temp_id_data[pool_name]: gacha_log.item_list[pool_name].append(item_info) temp_id_data[pool_name].append(item_info.id) @@ -187,11 +181,6 @@ class GachaLog: all_items = [GachaItem(**i) for i in data["list"]] await self.verify_data(all_items) gacha_log, status = await self.load_history_info(str(user_id), uid) - if import_type == ImportType.PAIMONMOE: - if status and gacha_log.get_import_type != ImportType.PAIMONMOE: - raise GachaLogMixedProvider - elif status and gacha_log.get_import_type == ImportType.PAIMONMOE: - raise GachaLogMixedProvider # 将唯一 id 放入临时数据中,加快查找速度 temp_id_data = { pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items() @@ -216,10 +205,11 @@ class GachaLog: except GachaLogMixedProvider as e: raise GachaLogMixedProvider from e except Exception as exc: + breakpoint() raise GachaLogException from exc async def get_gacha_log_data(self, user_id: int, client: Client, authkey: str) -> int: - """使用authkey获取抽卡记录数据,并合并旧数据 + """使用authkey获取跃迁记录数据,并合并旧数据 :param user_id: 用户id :param client: genshin client :param authkey: authkey @@ -227,8 +217,6 @@ class GachaLog: """ new_num = 0 gacha_log, _ = await self.load_history_info(str(user_id), str(client.uid)) - if gacha_log.get_import_type == ImportType.PAIMONMOE: - raise GachaLogMixedProvider # 将唯一 id 放入临时数据中,加快查找速度 temp_id_data = {pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()} try: @@ -261,30 +249,20 @@ class GachaLog: for i in gacha_log.item_list.values(): i.sort(key=lambda x: (x.time, x.id)) gacha_log.update_time = datetime.datetime.now() - gacha_log.import_type = ImportType.UIGF.value + gacha_log.import_type = ImportType.UIWF.value await self.save_gacha_log_info(str(user_id), str(client.uid), gacha_log) return new_num @staticmethod def check_avatar_up(name: str, gacha_time: datetime.datetime) -> bool: - if name in {"莫娜", "七七", "迪卢克", "琴"}: + if name in {"姬子", "瓦尔特", "布洛妮娅", "杰帕德", "克拉拉", "彦卿", "白露"}: return False - if name == "刻晴": - start_time = datetime.datetime.strptime("2021-02-17 18:00:00", "%Y-%m-%d %H:%M:%S") - end_time = datetime.datetime.strptime("2021-03-02 15:59:59", "%Y-%m-%d %H:%M:%S") - if not start_time < gacha_time < end_time: - return False - elif name == "提纳里": - start_time = datetime.datetime.strptime("2022-08-24 06:00:00", "%Y-%m-%d %H:%M:%S") - end_time = datetime.datetime.strptime("2022-09-09 17:59:59", "%Y-%m-%d %H:%M:%S") - if not start_time < gacha_time < end_time: - return False return True - async def get_all_5_star_items(self, data: List[GachaItem], assets: AssetsService, pool_name: str = "角色祈愿"): + async def get_all_5_star_items(self, data: List[GachaItem], assets: AssetsService, pool_name: str = "角色跃迁"): """ 获取所有5星角色 - :param data: 抽卡记录 + :param data: 跃迁记录 :param assets: 资源服务 :param pool_name: 池子名称 :return: 5星角色列表 @@ -294,21 +272,27 @@ class GachaLog: for item in data: count += 1 if item.rank_type == "5": - if item.item_type == "角色" and pool_name in {"角色祈愿", "常驻祈愿"}: + if item.item_type == "角色" and pool_name in {"角色跃迁", "常驻跃迁", "新手跃迁"}: + if pool_name == "新手跃迁": + isUp, isBig = True, False + elif pool_name == "角色跃迁": + isUp, isBig = self.check_avatar_up(item.name, item.time), (not result[-1].isUp) if result else False + else: + isUp, isBig = False, False data = { "name": item.name, - "icon": (await assets.avatar(roleToId(item.name)).icon()).as_uri(), + "icon": assets.avatar.icon(item.name).as_uri(), "count": count, "type": "角色", - "isUp": self.check_avatar_up(item.name, item.time) if pool_name == "角色祈愿" else False, - "isBig": (not result[-1].isUp) if result and pool_name == "角色祈愿" else False, + "isUp": isUp, + "isBig": isBig, "time": item.time, } result.append(FiveStarItem.construct(**data)) - elif item.item_type == "武器" and pool_name in {"武器祈愿", "常驻祈愿"}: + elif item.item_type == "光锥" and pool_name in {"光锥跃迁", "常驻跃迁"}: data = { "name": item.name, - "icon": (await assets.weapon(weaponToId(item.name)).icon()).as_uri(), + "icon": assets.light_cone.icon(item.name).as_uri(), "count": count, "type": "武器", "isUp": False, @@ -324,7 +308,7 @@ class GachaLog: async def get_all_4_star_items(data: List[GachaItem], assets: AssetsService): """ 获取 no_fout_star - :param data: 抽卡记录 + :param data: 跃迁记录 :param assets: 资源服务 :return: no_fout_star """ @@ -336,18 +320,18 @@ class GachaLog: if item.item_type == "角色": data = { "name": item.name, - "icon": (await assets.avatar(roleToId(item.name)).icon()).as_uri(), + "icon": assets.avatar.icon(item.name).as_uri(), "count": count, "type": "角色", "time": item.time, } result.append(FourStarItem.construct(**data)) - elif item.item_type == "武器": + elif item.item_type == "光锥": data = { "name": item.name, - "icon": (await assets.weapon(weaponToId(item.name)).icon()).as_uri(), + "icon": assets.light_cone.icon(item.name).as_uri(), "count": count, - "type": "武器", + "type": "光锥", "time": item.time, } result.append(FourStarItem.construct(**data)) @@ -387,7 +371,7 @@ class GachaLog: {"num": no_four_star, "unit": "抽", "lable": "未出四星"}, {"num": five_star_const, "unit": "个", "lable": "五星常驻"}, {"num": up_avg, "unit": "抽", "lable": "UP平均"}, - {"num": up_cost, "unit": "", "lable": "UP花费原石"}, + {"num": up_cost, "unit": "", "lable": "UP花费星琼"}, ], ] @@ -400,7 +384,7 @@ class GachaLog: # 五星平均 five_star_avg = round((total - no_five_star) / five_star, 2) if five_star != 0 else 0 # 五星武器 - five_star_weapon = len([i for i in all_five if i.type == "武器"]) + five_star_weapon = len([i for i in all_five if i.type == "光锥"]) # 总共四星 four_star = len(all_four) # 四星平均 @@ -414,7 +398,7 @@ class GachaLog: {"num": no_five_star, "unit": "抽", "lable": "未出五星"}, {"num": five_star, "unit": "个", "lable": "五星"}, {"num": five_star_avg, "unit": "抽", "lable": "五星平均"}, - {"num": five_star_weapon, "unit": "个", "lable": "五星武器"}, + {"num": five_star_weapon, "unit": "个", "lable": "五星光锥"}, ], [ {"num": no_four_star, "unit": "抽", "lable": "未出四星"}, @@ -433,7 +417,7 @@ class GachaLog: # 五星平均 five_star_avg = round((total - no_five_star) / five_star, 2) if five_star != 0 else 0 # 四星武器 - four_star_weapon = len([i for i in all_four if i.type == "武器"]) + four_star_weapon = len([i for i in all_four if i.type == "光锥"]) # 总共四星 four_star = len(all_four) # 四星平均 @@ -447,7 +431,7 @@ class GachaLog: {"num": no_five_star, "unit": "抽", "lable": "未出五星"}, {"num": five_star, "unit": "个", "lable": "五星"}, {"num": five_star_avg, "unit": "抽", "lable": "五星平均"}, - {"num": four_star_weapon, "unit": "个", "lable": "四星武器"}, + {"num": four_star_weapon, "unit": "个", "lable": "四星光锥"}, ], [ {"num": no_four_star, "unit": "抽", "lable": "未出四星"}, @@ -460,7 +444,7 @@ class GachaLog: @staticmethod def count_fortune(pool_name: str, summon_data, weapon: bool = False): """ - 角色 武器 + 角色 光锥 欧 50以下 45以下 吉 50-60 45-55 中 60-70 55-65 @@ -482,9 +466,9 @@ class GachaLog: return f"{pool_name} · 非" return pool_name - async def get_analysis(self, user_id: int, client: Client, pool: BannerType, assets: AssetsService): + async def get_analysis(self, user_id: int, client: Client, pool: StarRailBannerType, assets: AssetsService): """ - 获取抽卡记录分析数据 + 获取跃迁记录分析数据 :param user_id: 用户id :param client: genshin client :param pool: 池子类型 @@ -502,13 +486,13 @@ class GachaLog: all_five, no_five_star = await self.get_all_5_star_items(data, assets, pool_name) all_four, no_four_star = await self.get_all_4_star_items(data, assets) summon_data = None - if pool == BannerType.CHARACTER1: + if pool in [StarRailBannerType.CHARACTER, StarRailBannerType.NOVICE]: summon_data = self.get_301_pool_data(total, all_five, no_five_star, no_four_star) pool_name = self.count_fortune(pool_name, summon_data) - elif pool == BannerType.WEAPON: + elif pool == StarRailBannerType.WEAPON: summon_data = self.get_302_pool_data(total, all_five, all_four, no_five_star, no_four_star) pool_name = self.count_fortune(pool_name, summon_data, True) - elif pool == BannerType.PERMANENT: + elif pool == StarRailBannerType.PERMANENT: summon_data = self.get_200_pool_data(total, all_five, all_four, no_five_star, no_four_star) pool_name = self.count_fortune(pool_name, summon_data) last_time = data[0].time.strftime("%Y-%m-%d %H:%M") @@ -526,9 +510,9 @@ class GachaLog: } async def get_pool_analysis( - self, user_id: int, client: Client, pool: BannerType, assets: AssetsService, group: bool + self, user_id: int, client: Client, pool: StarRailBannerType, assets: AssetsService, group: bool ) -> dict: - """获取抽卡记录分析数据 + """获取跃迁记录分析数据 :param user_id: 用户id :param client: genshin client :param pool: 池子类型 @@ -573,7 +557,7 @@ class GachaLog: } async def get_all_five_analysis(self, user_id: int, client: Client, assets: AssetsService) -> dict: - """获取五星抽卡记录分析数据 + """获取五星跃迁记录分析数据 :param user_id: 用户id :param client: genshin client :param assets: 资源服务 @@ -612,128 +596,3 @@ class GachaLog: "pool": pool_data, "hasMore": False, } - - @staticmethod - def convert_xlsx_to_uigf(file: Union[str, PathLike, IO[bytes]], zh_dict: Dict) -> Dict: - """转换 paimone.moe 或 非小酋 导出 xlsx 数据为 UIGF 格式 - :param file: 导出的 xlsx 文件 - :param zh_dict: - :return: UIGF 格式数据 - """ - - def from_paimon_moe( - uigf_gacha_type: UIGFGachaType, item_type: str, name: str, date_string: str, rank_type: int, _id: int - ) -> UIGFItem: - item_type = ItemType.CHARACTER if item_type == "Character" else ItemType.WEAPON - return UIGFItem( - id=str(_id), - name=zh_dict[name], - gacha_type=uigf_gacha_type, - item_type=item_type, - rank_type=str(rank_type), - time=date_string, - uigf_gacha_type=uigf_gacha_type, - ) - - def from_uigf( - uigf_gacha_type: str, - gacha__type: str, - item_type: str, - name: str, - date_string: str, - rank_type: str, - _id: str, - ) -> UIGFItem: - return UIGFItem( - id=_id, - name=name, - gacha_type=gacha__type, - item_type=item_type, - rank_type=rank_type, - time=date_string, - uigf_gacha_type=uigf_gacha_type, - ) - - def from_fxq( - uigf_gacha_type: UIGFGachaType, item_type: str, name: str, date_string: str, rank_type: int, _id: int - ) -> UIGFItem: - item_type = ItemType.CHARACTER if item_type == "角色" else ItemType.WEAPON - return UIGFItem( - id=str(_id), - name=name, - gacha_type=uigf_gacha_type, - item_type=item_type, - rank_type=str(rank_type), - time=date_string, - uigf_gacha_type=uigf_gacha_type, - ) - - wb = load_workbook(file) - wb_len = len(wb.worksheets) - - if wb_len == 6: - import_type = ImportType.PAIMONMOE - elif wb_len == 5: - import_type = ImportType.UIGF - elif wb_len == 4: - import_type = ImportType.FXQ - else: - raise GachaLogFileError("xlsx 格式错误") - - paimonmoe_sheets = { - UIGFGachaType.BEGINNER: "Beginners' Wish", - UIGFGachaType.STANDARD: "Standard", - UIGFGachaType.CHARACTER: "Character Event", - UIGFGachaType.WEAPON: "Weapon Event", - } - fxq_sheets = { - UIGFGachaType.BEGINNER: "新手祈愿", - UIGFGachaType.STANDARD: "常驻祈愿", - UIGFGachaType.CHARACTER: "角色活动祈愿", - UIGFGachaType.WEAPON: "武器活动祈愿", - } - data = UIGFModel(info=UIGFInfo(export_app=import_type.value), list=[]) - if import_type == ImportType.PAIMONMOE: - ws = wb["Information"] - if ws["B2"].value != PAIMONMOE_VERSION: - raise PaimonMoeGachaLogFileError(file_version=ws["B2"].value, support_version=PAIMONMOE_VERSION) - count = 1 - for gacha_type in paimonmoe_sheets: - ws = wb[paimonmoe_sheets[gacha_type]] - for row in ws.iter_rows(min_row=2, values_only=True): - if row[0] is None: - break - data.list.append(from_paimon_moe(gacha_type, row[0], row[1], row[2], row[3], count)) - count += 1 - elif import_type == ImportType.UIGF: - ws = wb["原始数据"] - type_map = {} - count = 0 - for row in ws["1"]: - if row.value is None: - break - type_map[row.value] = count - count += 1 - for row in ws.iter_rows(min_row=2, values_only=True): - if row[0] is None: - break - data.list.append( - from_uigf( - row[type_map["uigf_gacha_type"]], - row[type_map["gacha_type"]], - row[type_map["item_type"]], - row[type_map["name"]], - row[type_map["time"]], - row[type_map["rank_type"]], - row[type_map["id"]], - ) - ) - else: - for gacha_type in fxq_sheets: - ws = wb[fxq_sheets[gacha_type]] - for row in ws.iter_rows(min_row=2, values_only=True): - if row[0] is None: - break - data.list.append(from_fxq(gacha_type, row[2], row[1], row[0], row[3], row[6])) - - return json.loads(data.json()) diff --git a/modules/gacha_log/models.py b/modules/gacha_log/models.py index 1bf6dae..9582a5d 100644 --- a/modules/gacha_log/models.py +++ b/modules/gacha_log/models.py @@ -4,15 +4,13 @@ from typing import Any, Dict, List, Union from pydantic import BaseModel, validator -from metadata.shortname import not_real_roles, roleToId, weaponToId -from modules.gacha_log.const import UIGF_VERSION +from metadata.shortname import not_real_roles, roleToId, lightConeToId +from modules.gacha_log.const import UIWF_VERSION class ImportType(Enum): PaiGram = "PaiGram" - PAIMONMOE = "PAIMONMOE" - FXQ = "FXQ" - UIGF = "UIGF" + UIWF = "UIWF" UNKNOWN = "UNKNOWN" @@ -44,20 +42,20 @@ class GachaItem(BaseModel): @validator("name") def name_validator(cls, v): - if item_id := (roleToId(v) or weaponToId(v)): + if item_id := (roleToId(v) or lightConeToId(v)): if item_id not in not_real_roles: return v raise ValueError("Invalid name") @validator("gacha_type") def check_gacha_type(cls, v): - if v not in {"100", "200", "301", "302", "400"}: - raise ValueError("gacha_type must be 200, 301, 302 or 400") + if v not in {"1", "2", "11", "12"}: + raise ValueError("gacha_type must be 1, 2, 11 or 12") return v @validator("item_type") def check_item_type(cls, item): - if item not in {"角色", "武器"}: + if item not in {"角色", "光锥"}: raise ValueError("error item type") return item @@ -74,10 +72,10 @@ class GachaLogInfo(BaseModel): update_time: datetime.datetime import_type: str = "" item_list: Dict[str, List[GachaItem]] = { - "角色祈愿": [], - "武器祈愿": [], - "常驻祈愿": [], - "新手祈愿": [], + "角色跃迁": [], + "光锥跃迁": [], + "常驻跃迁": [], + "新手跃迁": [], } @property @@ -131,37 +129,36 @@ class Pool: class ItemType(Enum): CHARACTER = "角色" - WEAPON = "武器" + LIGHTCONE = "光锥" -class UIGFGachaType(Enum): - BEGINNER = "100" - STANDARD = "200" - CHARACTER = "301" - WEAPON = "302" - CHARACTER2 = "400" +class UIWFGachaType(Enum): + BEGINNER = "2" + STANDARD = "1" + CHARACTER = "11" + LIGHTCONE = "12" -class UIGFItem(BaseModel): +class UIWFItem(BaseModel): id: str name: str count: str = "1" - gacha_type: UIGFGachaType + gacha_type: UIWFGachaType item_id: str = "" item_type: ItemType rank_type: str time: str - uigf_gacha_type: UIGFGachaType + uigf_gacha_type: UIWFGachaType -class UIGFInfo(BaseModel): +class UIWFInfo(BaseModel): uid: str = "0" lang: str = "zh-cn" export_time: str = "" export_timestamp: int = 0 export_app: str = "" export_app_version: str = "" - uigf_version: str = UIGF_VERSION + uigf_version: str = UIWF_VERSION def __init__(self, **data: Any): super().__init__(**data) @@ -170,6 +167,6 @@ class UIGFInfo(BaseModel): self.export_timestamp = int(datetime.datetime.now().timestamp()) -class UIGFModel(BaseModel): - info: UIGFInfo - list: List[UIGFItem] +class UIWFModel(BaseModel): + info: UIWFInfo + list: List[UIWFItem] diff --git a/modules/wiki/models/avatar.py b/modules/wiki/models/avatar.py index 51759fa..a46942c 100644 --- a/modules/wiki/models/avatar.py +++ b/modules/wiki/models/avatar.py @@ -44,8 +44,6 @@ class Avatar(BaseModel): """角色ID""" name: str """名称""" - icon: str - """图标""" quality: Quality """品质""" destiny: Destiny diff --git a/modules/wiki/models/avatar_config.py b/modules/wiki/models/avatar_config.py new file mode 100644 index 0000000..77852b1 --- /dev/null +++ b/modules/wiki/models/avatar_config.py @@ -0,0 +1,36 @@ +from typing import List + +from pydantic import BaseModel + + +class AvatarName(BaseModel): + Hash: int + + +class AvatarConfig(BaseModel): + name: str = "" + AvatarID: int + AvatarName: AvatarName + AvatarVOTag: str + Release: bool + + +class AvatarIcon(BaseModel): + id: int + """角色ID""" + name: str + """名称""" + icon: List[str] + """图标(从小到大)""" + + @property + def gacha(self) -> str: + return self.icon[2] + + @property + def icon_(self) -> str: + return self.icon[0] + + @property + def normal(self) -> str: + return self.icon[1] diff --git a/modules/wiki/models/light_cone_config.py b/modules/wiki/models/light_cone_config.py new file mode 100644 index 0000000..88fb96f --- /dev/null +++ b/modules/wiki/models/light_cone_config.py @@ -0,0 +1,20 @@ +from typing import List + +from pydantic import BaseModel + + +class LightConeIcon(BaseModel): + id: int + """光锥ID""" + name: str + """名称""" + icon: List[str] + """图标(从小到大)""" + + @property + def gacha(self) -> str: + return self.icon[1] + + @property + def icon_(self) -> str: + return self.icon[0] diff --git a/modules/wiki/models/monster.py b/modules/wiki/models/monster.py index ccc20d6..474f3c7 100644 --- a/modules/wiki/models/monster.py +++ b/modules/wiki/models/monster.py @@ -23,4 +23,3 @@ class Monster(BaseModel): """抗性""" find_area: str """发现地点""" - diff --git a/modules/wiki/models/wiki.py b/modules/wiki/models/wiki.py new file mode 100644 index 0000000..d096596 --- /dev/null +++ b/modules/wiki/models/wiki.py @@ -0,0 +1,34 @@ +from typing import List, Dict + +import ujson +from pydantic import BaseModel + + +class Content(BaseModel): + content_id: int + """内容ID""" + ext: str + """扩展信息""" + icon: str + """图标""" + summary: str + """摘要""" + title: str + """标题""" + article_user_name: str = "" + """作者""" + bbs_url: str = "" + """BBS对应地址""" + + @property + def data(self) -> Dict: + return ujson.loads(self.ext) + + +class Children(BaseModel): + id: int + """分类ID""" + name: str + """分类名称""" + list: List[Content] + """内容列表""" diff --git a/plugins/starrail/wish_log.py b/plugins/starrail/wish_log.py new file mode 100644 index 0000000..fec9211 --- /dev/null +++ b/plugins/starrail/wish_log.py @@ -0,0 +1,395 @@ +from io import BytesIO + +from genshin.models import StarRailBannerType +from telegram import Document, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User +from telegram.constants import ChatAction +from telegram.ext import CallbackContext, CommandHandler, ConversationHandler, MessageHandler, filters +from telegram.helpers import create_deep_linked_url + +from core.dependence.assets import AssetsService +from core.plugin import Plugin, conversation, handler +from core.services.cookies import CookiesService +from core.services.players import PlayersService +from core.services.template.models import FileType +from core.services.template.services import TemplateService +from modules.gacha_log.error import ( + GachaLogAccountNotFound, + GachaLogAuthkeyTimeout, + GachaLogFileError, + GachaLogInvalidAuthkey, + GachaLogMixedProvider, + GachaLogNotFound, +) +from modules.gacha_log.helpers import from_url_get_authkey +from modules.gacha_log.log import GachaLog +from plugins.tools.genshin import PlayerNotFoundError, GenshinHelper +from utils.log import logger + +try: + import ujson as jsonlib + +except ImportError: + import json as jsonlib + +INPUT_URL, INPUT_FILE, CONFIRM_DELETE = range(10100, 10103) + + +class WishLogPlugin(Plugin.Conversation): + """跃迁记录导入/导出/分析""" + + def __init__( + self, + template_service: TemplateService, + players_service: PlayersService, + assets: AssetsService, + cookie_service: CookiesService, + helper: GenshinHelper, + ): + self.template_service = template_service + self.players_service = players_service + self.assets_service = assets + self.cookie_service = cookie_service + self.gacha_log = GachaLog() + self.helper = helper + + async def _refresh_user_data( + self, user: User, data: dict = None, authkey: str = None, verify_uid: bool = True + ) -> str: + """刷新用户数据 + :param user: 用户 + :param data: 数据 + :param authkey: 认证密钥 + :return: 返回信息 + """ + try: + logger.debug("尝试获取已绑定的星穹铁道账号") + client = await self.helper.get_genshin_client(user.id, need_cookie=False) + if authkey: + new_num = await self.gacha_log.get_gacha_log_data(user.id, client, authkey) + return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条跃迁记录" + if data: + new_num = await self.gacha_log.import_gacha_log_data(user.id, client, data, verify_uid) + return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条跃迁记录" + except GachaLogNotFound: + return "彦卿没有找到你的跃迁记录,快来私聊彦卿导入吧~" + except GachaLogAccountNotFound: + return "导入失败,可能文件包含的跃迁记录所属 uid 与你当前绑定的 uid 不同" + except GachaLogFileError: + return "导入失败,数据格式错误" + except GachaLogInvalidAuthkey: + return "更新数据失败,authkey 无效" + except GachaLogAuthkeyTimeout: + return "更新数据失败,authkey 已经过期" + except GachaLogMixedProvider: + return "导入失败,你已经通过其他方式导入过跃迁记录了,本次无法导入" + except PlayerNotFoundError: + logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) + return "彦卿没有找到您所绑定的账号信息,请先私聊彦卿绑定账号" + + async def import_from_file(self, user: User, message: Message, document: Document = None) -> None: + if not document: + document = message.document + # TODO: 使用 mimetype 判断文件类型 + if document.file_name.endswith(".json"): + file_type = "json" + else: + await message.reply_text("文件格式错误,请发送符合 UIWF 标准的跃迁记录文件") + return + if document.file_size > 5 * 1024 * 1024: + await message.reply_text("文件过大,请发送小于 5 MB 的文件") + return + try: + out = BytesIO() + await (await document.get_file()).download_to_memory(out=out) + if file_type == "json": + # bytesio to json + data = jsonlib.loads(out.getvalue().decode("utf-8")) + else: + await message.reply_text("文件解析失败,请检查文件") + return + except GachaLogFileError: + await message.reply_text("文件解析失败,请检查文件是否符合 UIWF 标准") + return + except (KeyError, IndexError, ValueError): + await message.reply_text("文件解析失败,请检查文件编码是否正确或符合 UIWF 标准") + return + except Exception as exc: + logger.error("文件解析失败 %s", repr(exc)) + await message.reply_text("文件解析失败,请检查文件是否符合 UIWF 标准") + return + await message.reply_chat_action(ChatAction.TYPING) + reply = await message.reply_text("文件解析成功,正在导入数据") + await message.reply_chat_action(ChatAction.TYPING) + try: + text = await self._refresh_user_data(user, data=data, verify_uid=file_type == "json") + except Exception as exc: # pylint: disable=W0703 + logger.error("文件解析失败 %s", repr(exc)) + text = "文件解析失败,请检查文件是否符合 UIWF 标准" + await reply.edit_text(text) + + @conversation.entry_point + @handler.command(command="warp_log_import", filters=filters.ChatType.PRIVATE, block=False) + @handler.message(filters=filters.Regex("^导入跃迁记录(.*)") & filters.ChatType.PRIVATE, block=False) + async def command_start(self, update: Update, context: CallbackContext) -> int: + message = update.effective_message + user = update.effective_user + args = self.get_args(context) + logger.info("用户 %s[%s] 导入跃迁记录命令请求", user.full_name, user.id) + authkey = from_url_get_authkey(args[0] if args else "") + # if not args: + # player_info = await self.players_service.get_player(user.id, region=RegionEnum.HYPERION) + # if player_info is not None: + # cookies = await self.cookie_service.get(user.id, account_id=player_info.account_id) + # if cookies is not None and cookies.data and "stoken" in cookies.data: + # if stuid := next( + # (value for key, value in cookies.data.items() if key in ["ltuid", "login_uid"]), None + # ): + # cookies.data["stuid"] = stuid + # client = genshin.Client( + # cookies=cookies.data, + # game=genshin.types.Game.STARRAIL, + # region=genshin.Region.CHINESE, + # lang="zh-cn", + # uid=player_info.player_id, + # ) + # authkey = await get_authkey_by_stoken(client) + if not authkey: + await message.reply_text( + "开始导入跃迁历史记录:请通过 https://starrailstation.com/cn/warp#import 获取跃迁记录链接后发送给我" + "(非 starrailstation.com 导出的文件数据)\n\n" + "> 你还可以向彦卿发送从其他工具导出的 UIWF 标准的记录文件\n" + # "> 在绑定 Cookie 时添加 stoken 可能有特殊效果哦(仅限国服)\n" + "注意:导入的数据将会与旧数据进行合并。", + parse_mode="html", + ) + return INPUT_URL + text = "小彦卿正在从服务器获取数据,请稍后" + if not args: + text += "\n\n> 由于你绑定的 Cookie 中存在 stoken ,本次通过 stoken 自动刷新数据" + reply = await message.reply_text(text) + await message.reply_chat_action(ChatAction.TYPING) + data = await self._refresh_user_data(user, authkey=authkey) + await reply.edit_text(data) + return ConversationHandler.END + + @conversation.state(state=INPUT_URL) + @handler.message(filters=~filters.COMMAND, block=False) + async def import_data_from_message(self, update: Update, _: CallbackContext) -> int: + message = update.effective_message + user = update.effective_user + if message.document: + await self.import_from_file(user, message) + return ConversationHandler.END + if not message.text: + await message.reply_text("请发送文件或链接") + return INPUT_URL + authkey = from_url_get_authkey(message.text) + reply = await message.reply_text("小彦卿正在从服务器获取数据,请稍后") + await message.reply_chat_action(ChatAction.TYPING) + text = await self._refresh_user_data(user, authkey=authkey) + await reply.edit_text(text) + return ConversationHandler.END + + @conversation.entry_point + @handler.command(command="warp_log_delete", filters=filters.ChatType.PRIVATE, block=False) + @handler.message(filters=filters.Regex("^删除跃迁记录(.*)") & filters.ChatType.PRIVATE, block=False) + async def command_start_delete(self, update: Update, context: CallbackContext) -> int: + message = update.effective_message + user = update.effective_user + logger.info("用户 %s[%s] 删除跃迁记录命令请求", user.full_name, user.id) + try: + client = await self.helper.get_genshin_client(user.id, need_cookie=False) + context.chat_data["uid"] = client.uid + except PlayerNotFoundError: + logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) + await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号") + return ConversationHandler.END + _, status = await self.gacha_log.load_history_info(str(user.id), str(client.uid), only_status=True) + if not status: + await message.reply_text("你还没有导入跃迁记录哦~") + return ConversationHandler.END + await message.reply_text("你确定要删除跃迁记录吗?(此项操作无法恢复),如果确定请发送 ”确定“,发送其他内容取消") + return CONFIRM_DELETE + + @conversation.state(state=CONFIRM_DELETE) + @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False) + async def command_confirm_delete(self, update: Update, context: CallbackContext) -> int: + message = update.effective_message + user = update.effective_user + if message.text == "确定": + status = await self.gacha_log.remove_history_info(str(user.id), str(context.chat_data["uid"])) + await message.reply_text("跃迁记录已删除" if status else "跃迁记录删除失败") + return ConversationHandler.END + await message.reply_text("已取消") + return ConversationHandler.END + + @handler(CommandHandler, command="warp_log_force_delete", block=False, admin=True) + async def command_warp_log_force_delete(self, update: Update, context: CallbackContext): + message = update.effective_message + args = self.get_args(context) + if not args: + await message.reply_text("请指定用户ID") + return + try: + cid = int(args[0]) + if cid < 0: + raise ValueError("Invalid cid") + client = await self.helper.get_genshin_client(cid, need_cookie=False) + _, status = await self.gacha_log.load_history_info(str(cid), str(client.uid), only_status=True) + if not status: + await message.reply_text("该用户还没有导入跃迁记录") + return + status = await self.gacha_log.remove_history_info(str(cid), str(client.uid)) + await message.reply_text("跃迁记录已强制删除" if status else "跃迁记录删除失败") + except GachaLogNotFound: + await message.reply_text("该用户还没有导入跃迁记录") + except PlayerNotFoundError: + await message.reply_text("该用户暂未绑定账号") + except (ValueError, IndexError): + await message.reply_text("用户ID 不合法") + + @handler(CommandHandler, command="warp_log_export", filters=filters.ChatType.PRIVATE, block=False) + @handler(MessageHandler, filters=filters.Regex("^导出跃迁记录(.*)") & filters.ChatType.PRIVATE, block=False) + async def command_start_export(self, update: Update, context: CallbackContext) -> None: + message = update.effective_message + user = update.effective_user + logger.info("用户 %s[%s] 导出跃迁记录命令请求", user.full_name, user.id) + try: + client = await self.helper.get_genshin_client(user.id, need_cookie=False) + await message.reply_chat_action(ChatAction.TYPING) + path = await self.gacha_log.gacha_log_to_uiwf(str(user.id), str(client.uid)) + await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT) + await message.reply_document(document=open(path, "rb+"), caption="跃迁记录导出文件 - UIWF V1.0") + except GachaLogNotFound: + logger.info("未找到用户 %s[%s] 的跃迁记录", user.full_name, user.id) + buttons = [ + [InlineKeyboardButton("点我导入", url=create_deep_linked_url(context.bot.username, "warp_log_import"))] + ] + await message.reply_text("彦卿没有找到你的跃迁记录,快来私聊彦卿导入吧~", reply_markup=InlineKeyboardMarkup(buttons)) + except GachaLogAccountNotFound: + await message.reply_text("导入失败,可能文件包含的跃迁记录所属 uid 与你当前绑定的 uid 不同") + except GachaLogFileError: + await message.reply_text("导入失败,数据格式错误") + except PlayerNotFoundError: + logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) + await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号") + + @handler(CommandHandler, command="warp_log", block=False) + @handler(MessageHandler, filters=filters.Regex("^跃迁记录?(光锥|角色|常驻|新手)$"), block=False) + async def command_start_analysis(self, update: Update, context: CallbackContext) -> None: + message = update.effective_message + user = update.effective_user + pool_type = StarRailBannerType.CHARACTER + if args := self.get_args(context): + if "光锥" in args: + pool_type = StarRailBannerType.WEAPON + elif "常驻" in args: + pool_type = StarRailBannerType.STANDARD + elif "新手" in args: + pool_type = StarRailBannerType.NOVICE + logger.info("用户 %s[%s] 跃迁记录命令请求 || 参数 %s", user.full_name, user.id, pool_type.name) + try: + client = await self.helper.get_genshin_client(user.id, need_cookie=False) + await message.reply_chat_action(ChatAction.TYPING) + data = await self.gacha_log.get_analysis(user.id, client, pool_type, self.assets_service) + if isinstance(data, str): + reply_message = await message.reply_text(data) + if filters.ChatType.GROUPS.filter(message): + self.add_delete_message_job(reply_message, delay=300) + self.add_delete_message_job(message, delay=300) + else: + await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) + png_data = await self.template_service.render( + "starrail/gacha_log/gacha_log.html", + data, + full_page=True, + file_type=FileType.DOCUMENT if len(data.get("fiveLog")) > 36 else FileType.PHOTO, + query_selector=".body_box", + ) + if png_data.file_type == FileType.DOCUMENT: + await png_data.reply_document(message, filename="跃迁记录.png") + else: + await png_data.reply_photo(message) + except GachaLogNotFound: + logger.info("未找到用户 %s[%s] 的跃迁记录", user.full_name, user.id) + buttons = [ + [InlineKeyboardButton("点我导入", url=create_deep_linked_url(context.bot.username, "warp_log_import"))] + ] + await message.reply_text("彦卿没有找到你此卡池的跃迁记录,快来点击按钮私聊彦卿导入吧~", reply_markup=InlineKeyboardMarkup(buttons)) + except PlayerNotFoundError: + logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) + buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]] + if filters.ChatType.GROUPS.filter(message): + reply_message = await message.reply_text( + "未查询到您所绑定的账号信息,请先私聊彦卿绑定账号", reply_markup=InlineKeyboardMarkup(buttons) + ) + self.add_delete_message_job(reply_message, delay=30) + self.add_delete_message_job(message, delay=30) + else: + await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons)) + + @handler(CommandHandler, command="warp_count", block=False) + @handler(MessageHandler, filters=filters.Regex("^跃迁统计?(光锥|角色|常驻|新手)$"), block=False) + async def command_start_count(self, update: Update, context: CallbackContext) -> None: + message = update.effective_message + user = update.effective_user + pool_type = StarRailBannerType.CHARACTER + all_five = False + if args := self.get_args(context): + if "光锥" in args: + pool_type = StarRailBannerType.WEAPON + elif "常驻" in args: + pool_type = StarRailBannerType.STANDARD + elif "新手" in args: + pool_type = StarRailBannerType.NOVICE + if "仅五星" in args: + all_five = True + logger.info("用户 %s[%s] 跃迁统计命令请求 || 参数 %s || 仅五星 %s", user.full_name, user.id, pool_type.name, all_five) + try: + client = await self.helper.get_genshin_client(user.id, need_cookie=False) + group = filters.ChatType.GROUPS.filter(message) + await message.reply_chat_action(ChatAction.TYPING) + if all_five: + data = await self.gacha_log.get_all_five_analysis(user.id, client, self.assets_service) + else: + data = await self.gacha_log.get_pool_analysis(user.id, client, pool_type, self.assets_service, group) + if isinstance(data, str): + reply_message = await message.reply_text(data) + if filters.ChatType.GROUPS.filter(message): + self.add_delete_message_job(reply_message) + self.add_delete_message_job(message) + else: + document = False + if data["hasMore"] and not group: + document = True + data["hasMore"] = False + await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT if document else ChatAction.UPLOAD_PHOTO) + png_data = await self.template_service.render( + "starrail/gacha_count/gacha_count.html", + data, + full_page=True, + query_selector=".body_box", + file_type=FileType.DOCUMENT if document else FileType.PHOTO, + ) + if document: + await png_data.reply_document(message, filename="跃迁统计.png") + else: + await png_data.reply_photo(message) + except GachaLogNotFound: + logger.info("未找到用户 %s[%s] 的跃迁记录", user.full_name, user.id) + buttons = [ + [InlineKeyboardButton("点我导入", url=create_deep_linked_url(context.bot.username, "warp_log_import"))] + ] + await message.reply_text("彦卿没有找到你此卡池的跃迁记录,快来私聊彦卿导入吧~", reply_markup=InlineKeyboardMarkup(buttons)) + except PlayerNotFoundError: + logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) + buttons = [[InlineKeyboardButton("点我绑定账号", url=create_deep_linked_url(context.bot.username, "set_uid"))]] + if filters.ChatType.GROUPS.filter(message): + reply_message = await message.reply_text( + "未查询到您所绑定的账号信息,请先私聊彦卿绑定账号", reply_markup=InlineKeyboardMarkup(buttons) + ) + self.add_delete_message_job(reply_message, delay=30) + + self.add_delete_message_job(message, delay=30) + else: + await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons)) diff --git a/resources/bot/help/help.html b/resources/bot/help/help.html index 4d80d6c..db55edf 100644 --- a/resources/bot/help/help.html +++ b/resources/bot/help/help.html @@ -60,14 +60,14 @@ - - - - - - - - + +
+
+ /dailynote + +
+
查询实时便笺
+
/ledger @@ -103,21 +103,21 @@ - - - - - - - - - - - - - - - + +
+
+ /warp_log + +
+
跃迁记录
+
+
+
+ /warp_count + +
+
跃迁统计
+
@@ -181,19 +181,19 @@ - - - - - - - - - - - - - + +
+
/warp_log_import
+
导入跃迁记录
+
+
+
/warp_log_export
+
导出跃迁记录
+
+
+
/warp_log_delete
+
删除跃迁记录
+
@@ -207,7 +207,7 @@ - + diff --git a/resources/starrail/gacha_count/example.html b/resources/starrail/gacha_count/example.html new file mode 100644 index 0000000..9fddf2c --- /dev/null +++ b/resources/starrail/gacha_count/example.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + +
+
+
ID: 10001
+

抽卡统计-角色祈愿

+ +
+
+
+
+

「枫原万叶、可莉」

+ 98抽 +
+ 2022-08-02 - 2022-08-02 +
+
+
+
+ 20 + +
+
+
+
*完整数据请私聊查看
+ +
+ + diff --git a/resources/starrail/gacha_count/gacha_count.css b/resources/starrail/gacha_count/gacha_count.css new file mode 100644 index 0000000..74cf8f2 --- /dev/null +++ b/resources/starrail/gacha_count/gacha_count.css @@ -0,0 +1,214 @@ +@font-face { + font-family: "tttgbnumber"; + src: url("./../../fonts/tttgbnumber.ttf"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "HYWenHei-55W"; + src: url("../../fonts/HYWenHei-85W.ttf"); + font-weight: normal; + font-style: normal; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; +} + +body { + font-size: 16px; + width: 530px; + color: #1E1F20; + transform: scale(1.5); + transform-origin: 0 0; +} + +.container { + width: 530px; + padding: 20px 15px 10px 15px; + background-color: #F5F6FB; +} + +.head_box { + border-radius: 15px; + font-family: tttgbnumber, serif; + padding: 10px 20px; + position: relative; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); + +} + +.head_box .id_text { + font-size: 24px; +} + +.head_box .day_text { + font-size: 20px; +} + +.head_box .genshin_logo { + position: absolute; + top: 1px; + right: 15px; + width: 97px; +} + +.base_info { + position: relative; + padding-left: 10px; +} + +.uid { + font-family: tttgbnumber, serif; +} + +.pool_box { + font-family: HYWenHei-55W, serif; + border-radius: 12px; + margin-top: 20px; + margin-bottom: 20px; + padding: 10px 5px 5px 5px; + background: #FFF; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); + position: relative; +} + +.title_box { + display: flex; + align-items: center; + margin-bottom: 10px; +} + +.title { + white-space: nowrap; + max-width: 210px; + overflow: hidden; +} + +.name_box { + display: flex; + align-items: center; + flex: 1; +} + +.title_box .date { + margin-right: 10px; +} + +.list_box { + display: flex; + flex-wrap: wrap; +} + +.item { + margin: 0 0 10px 10px; + border-radius: 7px; + overflow: hidden; + box-shadow: 0 2px 6px 0 rgb(132 93 90 / 30%); + height: 70px; + width: 70px; + background: #E9E5DC; + position: relative; +} + +.item .role_img { + width: 100%; + overflow: hidden; + background-size: 100%; + background-repeat: no-repeat; + position: absolute; + top: 0; + /* filter: contrast(95%); */ +} + +.item .num { + position: absolute; + top: 0; + right: 0; + z-index: 9; + font-size: 18px; + text-align: center; + color: #FFF; + border-radius: 3px; + padding: 1px 5px; + background: rgb(0 0 0 / 50%); + font-family: "tttgbnumber", serif; +} + +.label_301 { + background-color: rgb(235 106 75); +} + +.label_302 { + background-color: #E69449; +} + +.label_200 { + background-color: #757CC8; +} + +.label { + color: #FFF; + border-radius: 10px; + font-size: 16px; + padding: 2px 7px; + vertical-align: 2px; +} + +.bg5 { + background-image: url(./../../genshin/abyss/background/roleStarBg5.png); + width: 100%; + height: 70px; + /* filter: brightness(1.1); */ + background-size: 100%; + background-repeat: no-repeat; +} + +.bg4 { + width: 100%; + height: 70px; + background-image: url(./../../genshin/abyss/background/roleStarBg4.png); + background-size: 100%; + background-repeat: no-repeat; +} + +.list_box .item .life1 { + background-color: #62A8EA; +} + +.list_box .item .life2 { + background-color: #62A8EA; +} + +.list_box .item .life3 { + background-color: #45B97C; +} + +.list_box .item .life4 { + background-color: #45B97C; +} + +.list_box .item .life5 { + background-color: #FF5722; +} + +.list_box .item .life6 { + background-color: #FF5722; +} + +.logo { + font-size: 14px; + font-family: "tttgbnumber", serif; + text-align: center; + color: #7994A7; +} + +.hasMore { + font-size: 12px; + margin: -6px 0 10px 6px; + color: #7F858A; +} diff --git a/resources/starrail/gacha_count/gacha_count.html b/resources/starrail/gacha_count/gacha_count.html new file mode 100644 index 0000000..7dac67b --- /dev/null +++ b/resources/starrail/gacha_count/gacha_count.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + +
+
+
ID: {{ uid }}
+

跃迁统计-{{ typeName }}

+ +
+ {% for val in pool %} +
+
+
+

「{{ val.name }}」

+ {{ val.count }}抽 +
+ {% if typeName != "常驻跃迁" %} + {{ val.start }} - {{ val.end }} + {% endif %} +
+
+ {% for v in val.list %} +
+
+ {{ v.count }} + +
+ {% endfor %} +
+
+ {% endfor %} + {% if hasMore %} +
*完整数据请私聊查看
+ {% endif %} + +
+ + diff --git a/resources/starrail/gacha_log/example.html b/resources/starrail/gacha_log/example.html new file mode 100644 index 0000000..a353278 --- /dev/null +++ b/resources/starrail/gacha_log/example.html @@ -0,0 +1,77 @@ + + + + + + + + + + + + + +
+
+ +
+
+ ID: 10001 +
+

+ 81抽 + 角色祈愿池 · 欧 +

+ +
+ +
+
数据总览
+
+
+
1
+
未出五星
+
+
+ +
+ + 五星历史 2022-10-07 01:10 ~ 2022-10-07 23:10 + +
+ + +
+
+ UP + + +
80
+
+
+ +
+ + 四星最近历史 + +
+ +
+
+ + +
10
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/resources/starrail/gacha_log/gacha_log.css b/resources/starrail/gacha_log/gacha_log.css new file mode 100644 index 0000000..412ac47 --- /dev/null +++ b/resources/starrail/gacha_log/gacha_log.css @@ -0,0 +1,341 @@ +@font-face { + font-family: "tttgbnumber"; + src: url("./../../fonts/tttgbnumber.ttf"); + font-weight: normal; + font-style: normal; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; +} + +body { + font-size: 18px; + color: #1e1f20; + font-family: PingFangSC-Medium, PingFang SC, sans-serif; + transform: scale(1.5); + transform-origin: 0 0; + width: 510px; +} + +.container { + width: 510px; + padding: 20px 15px 10px 15px; + background-color: #f5f6fb; +} + +.head_box { + border-radius: 9999px; + font-family: tttgbnumber, sans-serif; + padding: 10px 20px; + position: relative; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); +} + +.head_box .id_text { + font-size: 24px; +} + +.head_box .day_text { + font-size: 20px; +} + +.head_box .genshin_logo { + position: absolute; + top: 1px; + right: 15px; + width: 97px; +} + +.logo { + font-size: 12px; + font-family: "tttgbnumber", serif; + text-align: center; + color: #7994a7; + position: relative; + padding-left: 10px; +} + +.data_box { + border-radius: 15px; + margin-top: 20px; + margin-bottom: 10px; + padding: 20px 0 5px 10px; + background: #fff; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); + position: relative; +} + +.tab_lable { + position: absolute; + top: -10px; + left: -8px; + background: #d4b98c; + color: #fff; + font-size: 14px; + padding: 3px 10px; + border-radius: 15px 0 15px 15px; + z-index: 20; +} + +.data_line { + display: flex; + justify-content: space-around; + margin-bottom: 14px; + padding-right: 10px; +} + +.data_line_item { + width: 100px; + text-align: center; + + /* margin: 0 20px; */ +} + +.num { + font-family: tttgbnumber, serif; + font-size: 24px; +} + +.num .unit { + font-size: 12px; +} + +.data_box .lable { + font-size: 14px; + color: #7f858a; + line-height: 1; + margin-top: 3px; +} + +.info_box_border { + border-radius: 15px; + + /* margin-top: 20px; */ + margin-bottom: 20px; + padding: 6px 0 5px 10px; + background: #fff; + box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); + position: relative; +} + +.card_list { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; +} + +.card_list .item { + margin: 0 8px 10px 0; + border-radius: 7px; + box-shadow: 0 2px 6px 0 rgb(132 93 90 / 30%); + height: 90px; + position: relative; + overflow: hidden; + background: #e7e5d9; +} + +.card_list .item img { + width: 70px; + height: 70px; + border-radius: 7px 7px 20px 0; +} + +.card_list .item.star5 img { + background-image: url(./../../genshin/abyss/background/roleStarBg5.png); + width: 100%; + height: 70px; + /* filter: brightness(1.1); */ + background-size: 100%; + background-repeat: no-repeat; +} + +.card_list .item.star4 img { + width: 100%; + height: 70px; + background-image: url(./../../genshin/abyss/background/roleStarBg4.png); + background-size: 100%; + background-repeat: no-repeat; +} + +.card_list .item .num { + position: absolute; + top: 0; + right: 0; + z-index: 9; + font-size: 18px; + text-align: center; + color: #fff; + border-radius: 3px; + padding: 1px 5px; + background: rgb(0 0 0 / 50%); + font-family: "tttgbnumber", serif; +} + +.card_list .item .name, +.card_list .item .num_name { + position: absolute; + top: 71px; + left: 0; + z-index: 9; + font-size: 12px; + text-align: center; + width: 100%; + height: 16px; + line-height: 18px; +} + +.card_list .item .num_name { + font-family: "tttgbnumber", serif; + font-size: 16px; +} + +.base_info { + position: relative; + padding-left: 10px; + margin: 5px 10px; +} + +.uid::before { + content: " "; + position: absolute; + width: 5px; + height: 24px; + border-radius: 1px; + left: 0; + top: 0; + background: #d3bc8d; +} + +.label_301 { + background-color: rgb(235 106 75); +} + +.label_302 { + background-color: #e69449; +} + +.label_200 { + background-color: #757cc8; +} + +.label { + color: #fff; + border-radius: 10px; + font-size: 12px; + padding: 2px 7px; + vertical-align: 2px; +} + +.ritem { + display: flex; + font-size: 12px; + margin-bottom: 5px; +} + +.info_role { + display: flex; + flex-wrap: wrap; + padding: 0 0 5px 9px; +} + +.ritem .role { + width: 20px; + height: 20px; + background-color: #ffb285; + border-radius: 100%; +} + +.ritem .weapon_box { + overflow: hidden; + width: 20px; + height: 20px; + border-radius: 100%; +} + +.ritem .weapon { + width: 20px; + height: 20px; + background-color: #ffb285; + border-radius: 100%; + transform: scale(1.5); + -webkit-transform: scale(1.5); + +} + +.ritem .role_text { + margin: 2px 3px 0 2px; + display: flex; + align-items: baseline; +} + +.ritem .role_name { + width: 24px; + white-space: nowrap; + overflow: hidden; +} + +.ritem .role_num { + width: 24px; +} + +.line_box { + height: 32px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + font-size: 12px; + color: #7d7d7d; + padding-bottom: 5px; +} + +.line_box .line { + height: 2px; + flex-grow: 1; + background-color: #ebebeb; + margin: 0 10px; +} + +.red { + color: #f21000; +} + +.orange { + color: #ff8d00; +} + +.green { + color: #12d88c; +} + +.blue { + color: #4169e1; +} + +.purple { + color: #7500ff; +} + +.minimum { + position: absolute; + top: 0; + right: 0; + z-index: 9; + font-size: 12px; + text-align: center; + color: #fff; + border-radius: 3px; + padding: 1px 3px; + background-color: rgb(0 0 0 / 80%); + font-family: "tttgbnumber", serif; +} + +.hasMore { + font-size: 12px; + margin: 6px 0; + color: #7f858a; +} diff --git a/resources/starrail/gacha_log/gacha_log.html b/resources/starrail/gacha_log/gacha_log.html new file mode 100644 index 0000000..dfc02bc --- /dev/null +++ b/resources/starrail/gacha_log/gacha_log.html @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + +
+
+ +
+
+ ID: {{ uid }} +
+

+ {{ allNum }}抽 + {{ typeName }} +

+
+ +
+
数据总览
+ {% for val in line %} +
+ {% for item in val %} +
+
{{item.num}}{{item.unit}}
+
{{item.lable}}
+
+ {% endfor %} +
+ {% endfor %} + +
+ + 五星历史 {{firstTime}} ~ {{lastTime}} + +
+ + +
+ {% for val in fiveLog %} +
+ {% if val.isUp %} + UP + {% endif %} + + +
{{ val.count }}
+
+ {% endfor %} +
+ +
+ + 四星最近历史 + +
+ +
+ {% for val in fourLog %} +
+ + +
{{ val.count }}
+
+ {% endfor %} +
+
+ +
+
+ + \ No newline at end of file diff --git a/resources/starrail/gacha_log/img/提纳里.png b/resources/starrail/gacha_log/img/提纳里.png new file mode 100644 index 0000000..fb923ac Binary files /dev/null and b/resources/starrail/gacha_log/img/提纳里.png differ diff --git a/utils/genshin.py b/utils/genshin.py index 2610d65..5889355 100644 --- a/utils/genshin.py +++ b/utils/genshin.py @@ -2,7 +2,7 @@ from typing import Optional from genshin import Client from genshin.client.routes import InternationalRoute # noqa F401 -from genshin.utility import recognize_genshin_server +from genshin.utility import recognize_starrail_server from modules.apihelper.utility.helpers import hex_digest, get_ds @@ -23,8 +23,8 @@ GACHA_HEADERS = { } -def recognize_genshin_game_biz(game_uid: int) -> str: - return "hk4e_cn" if game_uid < 600000000 else "hk4e_global" +def recognize_starrail_game_biz(game_uid: int) -> str: + return "hkrpg_cn" if game_uid < 600000000 else "hkrpg_global" async def get_authkey_by_stoken(client: Client) -> Optional[str]: @@ -32,9 +32,9 @@ async def get_authkey_by_stoken(client: Client) -> Optional[str]: headers = GACHA_HEADERS.copy() json = { "auth_appid": "webview_gacha", - "game_biz": recognize_genshin_game_biz(client.uid), + "game_biz": recognize_starrail_game_biz(client.uid), "game_uid": client.uid, - "region": recognize_genshin_server(client.uid), + "region": recognize_starrail_server(client.uid), } device_id = hex_digest(str(client.uid)) headers["x-rpc-device_id"] = device_id @@ -56,9 +56,9 @@ async def fetch_hk4e_token_by_cookie(client: Client) -> None: "Content-Type": "application/json;charset=UTF-8", } json = { - "game_biz": recognize_genshin_game_biz(client.uid), + "game_biz": recognize_starrail_game_biz(client.uid), "lang": "zh-cn", "uid": str(client.uid), - "region": recognize_genshin_server(client.uid), + "region": recognize_starrail_server(client.uid), } await client.cookie_manager.request(url, method="POST", json=json, headers=headers)