Support starrail warp log

This commit is contained in:
xtaodada 2023-04-27 20:25:06 +08:00
parent c07f0ea8fd
commit 088e9ac315
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
30 changed files with 1729 additions and 2255 deletions

View File

@ -1,47 +1,25 @@
"""用于下载和管理角色、武器、材料等的图标"""
from __future__ import annotations
import asyncio 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 pathlib import Path
from ssl import SSLZeroReturnError 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 import open as async_open
from aiofiles.os import remove as async_remove from httpx import AsyncClient, HTTPError
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 core.base_service import BaseService from core.base_service import BaseService
from core.config import config from modules.wiki.base import WikiModel
from metadata.genshin import AVATAR_DATA, HONEY_DATA, MATERIAL_DATA, NAMECARD_DATA, WEAPON_DATA from modules.wiki.models.avatar_config import AvatarIcon
from metadata.scripts.honey import update_honey_metadata from modules.wiki.models.light_cone_config import LightConeIcon
from metadata.scripts.metadatas import update_metadata_from_ambr, update_metadata_from_github from utils.const import PROJECT_ROOT
from metadata.shortname import roleToId, weaponToId
from utils.const import AMBR_HOST, ENKA_HOST, PROJECT_ROOT
from utils.log import logger from utils.log import logger
from utils.typedefs import StrOrInt, StrOrURL from utils.typedefs import StrOrURL, StrOrInt
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]
ASSETS_PATH = PROJECT_ROOT.joinpath("resources/assets") ASSETS_PATH = PROJECT_ROOT.joinpath("resources/assets")
ASSETS_PATH.mkdir(exist_ok=True, parents=True) ASSETS_PATH.mkdir(exist_ok=True, parents=True)
HONEY_HOST = "" DATA_MAP = {
DATA_MAP = {"avatar": AVATAR_DATA, "weapon": WEAPON_DATA, "material": MATERIAL_DATA} "avatar": WikiModel.BASE_URL + "avatar_icons.json",
"light_cone": WikiModel.BASE_URL + "light_cone_icons.json",
DEFAULT_EnkaAssets = EnkaAssets(lang="chs") }
class AssetsServiceError(Exception): class AssetsServiceError(Exception):
@ -55,84 +33,16 @@ class AssetsCouldNotFound(AssetsServiceError):
super().__init__(f"{message}: target={message}") super().__init__(f"{message}: target={message}")
class _AssetsService(ABC): class _AssetsService:
_lock: ClassVar["RLock"] = Lock() client: Optional[AsyncClient] = None
_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
def __init__(self, client: Optional[AsyncClient] = None) -> None: def __init__(self, client: Optional[AsyncClient] = None) -> None:
self._client = client self.client = client
def __call__(self, target: int) -> Self: async def _download(self, url: StrOrURL, path: Path, retry: int = 5) -> Optional[Path]:
"""用于生成与 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:
"""从 url 下载图标至 path""" """从 url 下载图标至 path"""
logger.debug("正在从 %s 下载图标至 %s", url, path) logger.debug("正在从 %s 下载图标至 %s", url, path)
headers = None 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): for time in range(retry):
try: try:
response = await self.client.get(url, follow_redirects=False, headers=headers) response = await self.client.get(url, follow_redirects=False, headers=headers)
@ -150,356 +60,144 @@ class _AssetsService(ABC):
await file.write(response.content) # 保存图标 await file.write(response.content) # 保存图标
return path.resolve() 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): class _AvatarAssets(_AssetsService):
enka: EnkaCharacterAsset | None path: Path
data: List[AvatarIcon]
name_map: Dict[str, AvatarIcon]
id_map: Dict[int, AvatarIcon]
side: ICON_TYPE def __init__(self, client: Optional[AsyncClient] = None) -> None:
"""侧视图图标"""
card: ICON_TYPE
"""卡片图标"""
gacha: ICON_TYPE
"""抽卡立绘"""
gacha_card: ICON_TYPE
"""抽卡卡片"""
@cached_property
def game_name(self) -> str:
icon = "UI_AvatarIcon_"
if (avatar := AVATAR_DATA.get(str(self.id), None)) is not None:
icon = avatar["icon"]
else:
for aid, avatar in AVATAR_DATA.items():
if aid.startswith(str(self.id)):
icon = avatar["icon"]
return re.findall(r"UI_AvatarIcon_(.*)", icon)[0]
@cached_property
def honey_id(self) -> str:
return HONEY_DATA["avatar"].get(str(self.id), "")[0]
@cached_property
def enka(self) -> Optional[EnkaCharacterAsset]:
api = getattr(self, "_enka_api", None)
cid = getattr(self, "id", None)
return None if api is None or cid is None else api.character(cid)
def __init__(self, client: Optional[AsyncClient] = None, enka: Optional[EnkaAssets] = None):
super().__init__(client) 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": async def initialize(self):
temp = target logger.info("正在初始化角色素材图标")
result = _AvatarAssets(self.client) html = await self.client.get(DATA_MAP["avatar"])
if isinstance(target, str): self.data = [AvatarIcon(**data) for data in html.json()]
try: self.name_map = {icon.name: icon for icon in self.data}
target = int(target) self.id_map = {icon.id: icon for icon in self.data}
except ValueError: tasks = []
target = roleToId(target) for icon in self.data:
if isinstance(target, str) or target is None: base_path = self.path / f"{icon.id}"
raise AssetsCouldNotFound("找不到对应的角色", temp) base_path.mkdir(exist_ok=True, parents=True)
result.id = target gacha_path = base_path / "gacha.webp"
result._enka_api = self._enka_api icon_path = base_path / "icon.webp"
return result 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]: def get_path(self, icon: AvatarIcon, name: str) -> Path:
if item in {"icon", "side", "gacha"}: path = self.path / f"{icon.id}"
yield str(AMBR_HOST.join(f"assets/UI/{self.game_name_map[item]}.png")) path.mkdir(exist_ok=True, parents=True)
return path / f"{name}.webp"
async def _get_from_enka(self, item: str) -> AsyncIterator[str | None]: def get_by_id(self, id_: int) -> Optional[AvatarIcon]:
if (item_id := self.game_name_map.get(item)) is not None: return self.id_map.get(id_, None)
yield str(ENKA_HOST.join(f"ui/{item_id}.png"))
@cached_property def get_by_name(self, name: str) -> Optional[AvatarIcon]:
def honey_name_map(self) -> dict[str, str]: return self.name_map.get(name, None)
return {
"icon": f"{self.honey_id}_icon",
"side": f"{self.honey_id}_side_icon",
"gacha": f"{self.honey_id}_gacha_splash",
"gacha_card": f"{self.honey_id}_gacha_card",
}
@cached_property def get_target(self, target: StrOrInt) -> Optional[AvatarIcon]:
def game_name_map(self) -> dict[str, str]: if isinstance(target, int):
return { return self.get_by_id(target)
"icon": f"UI_AvatarIcon_{self.game_name}", elif isinstance(target, str):
"card": f"UI_AvatarIcon_{self.game_name}_Card", return self.get_by_name(target)
"side": f"UI_AvatarIcon_Side_{self.game_name}", return None
"gacha": f"UI_Gacha_AvatarImg_{self.game_name}",
} 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): class _LightConeAssets(_AssetsService):
awaken: ICON_TYPE 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 async def initialize(self):
def game_name(self) -> str: logger.info("正在初始化光锥素材图标")
return re.findall(r"UI_EquipIcon_(.*)", WEAPON_DATA[str(self.id)]["icon"])[0] 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 get_path(self, icon: LightConeIcon, name: str) -> Path:
def game_name_map(self) -> dict[str, str]: path = self.path / f"{icon.id}"
return { path.mkdir(exist_ok=True, parents=True)
"icon": f"UI_EquipIcon_{self.game_name}", return path / f"{name}.webp"
"awaken": f"UI_EquipIcon_{self.game_name}_Awaken",
"gacha": f"UI_Gacha_EquipIcon_{self.game_name}",
}
@cached_property def get_by_id(self, id_: int) -> Optional[LightConeIcon]:
def honey_id(self) -> str: return self.id_map.get(id_, None)
return f"i_n{self.id}"
def __call__(self, target: StrOrInt) -> Self: def get_by_name(self, name: str) -> Optional[LightConeIcon]:
temp = target return self.name_map.get(name, None)
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
async def _get_from_ambr(self, item: str) -> AsyncIterator[str | None]: def get_target(self, target: StrOrInt) -> Optional[LightConeIcon]:
if item == "icon": if isinstance(target, int):
yield str(AMBR_HOST.join(f"assets/UI/{self.game_name_map.get(item)}.png")) 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]: def gacha(self, target: StrOrInt) -> Path:
if item in self.game_name_map: icon = self.get_target(target)
yield str(ENKA_HOST.join(f"ui/{self.game_name_map.get(item)}.png")) if icon is None:
raise AssetsCouldNotFound("光锥素材图标不存在", target)
return self.get_path(icon, "gacha")
@cached_property def icon(self, target: StrOrInt) -> Path:
def honey_name_map(self) -> dict[str, str]: icon = self.get_target(target)
return { if icon is None:
"icon": f"{self.honey_id}", raise AssetsCouldNotFound("光锥素材图标不存在", target)
"awaken": f"{self.honey_id}_awaken_icon", return self.get_path(icon, "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",
}
class AssetsService(BaseService.Dependence): class AssetsService(BaseService.Dependence):
@ -510,26 +208,19 @@ class AssetsService(BaseService.Dependence):
若本地不存在则从网络上下载若存在则返回其路径 若本地不存在则从网络上下载若存在则返回其路径
""" """
client: Optional[AsyncClient] = None
avatar: _AvatarAssets avatar: _AvatarAssets
"""角色""" """角色"""
weapon: _WeaponAssets light_cone: _LightConeAssets
"""武器""" """光锥"""
material: _MaterialAssets
"""素材"""
artifact: _ArtifactAssets
"""圣遗物"""
namecard: _NamecardAssets
"""名片"""
def __init__(self): def __init__(self):
for attr, assets_type_name in filter( self.client = AsyncClient(timeout=60.0)
lambda x: (not x[0].startswith("_")) and x[1].endswith("Assets"), self.__annotations__.items() self.avatar = _AvatarAssets(self.client)
): self.light_cone = _LightConeAssets(self.client)
setattr(self, attr, globals()[assets_type_name]())
async def initialize(self): # pylint: disable=W0221
AssetsServiceType = TypeVar("AssetsServiceType", bound=_AssetsService) await self.avatar.initialize()
await self.light_cone.initialize()

View File

@ -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)

View File

@ -23,12 +23,15 @@ class WikiService(BaseService):
async def initialize(self) -> None: async def initialize(self) -> None:
logger.info("正在加载 Wiki 数据") logger.info("正在加载 Wiki 数据")
try:
await self.character.read() await self.character.read()
await self.material.read() await self.material.read()
await self.monster.read() await self.monster.read()
await self.relic.read() await self.relic.read()
await self.light_cone.read() await self.light_cone.read()
await self.raider.read() await self.raider.read()
except Exception as e:
logger.error("加载 Wiki 数据失败", exc_info=e)
logger.info("加载 Wiki 数据完成") logger.info("加载 Wiki 数据完成")
async def refresh_wiki(self) -> NoReturn: async def refresh_wiki(self) -> NoReturn:

View File

@ -1,13 +1,16 @@
from metadata.pool.pool_200 import POOL_200 from metadata.pool.pool_1 import POOL_1
from metadata.pool.pool_301 import POOL_301 from metadata.pool.pool_2 import POOL_2
from metadata.pool.pool_302 import POOL_302 from metadata.pool.pool_11 import POOL_11
from metadata.pool.pool_12 import POOL_12
def get_pool_by_id(pool_type): def get_pool_by_id(pool_type):
if pool_type == 200: if pool_type == 1:
return POOL_200 return POOL_1
if pool_type == 301: elif pool_type == 2:
return POOL_301 return POOL_2
if pool_type == 302: if pool_type == 11:
return POOL_302 return POOL_11
if pool_type == 12:
return POOL_12
return None return None

1
metadata/pool/pool_1.py Normal file
View File

@ -0,0 +1 @@
POOL_1 = [{"five": ["常驻池"], "four": [], "from": "2023-04-26 06:00:00", "name": "常驻池", "to": "2050-09-15 17:59:59"}]

9
metadata/pool/pool_11.py Normal file
View File

@ -0,0 +1,9 @@
POOL_11 = [
{
"five": ["希尔"],
"four": ["娜塔莎", "佩拉", "虎克"],
"from": "2023-04-26 06:00:00",
"name": "蝶立锋锷",
"to": "2023-05-17 17:59:59",
},
]

9
metadata/pool/pool_12.py Normal file
View File

@ -0,0 +1,9 @@
POOL_12 = [
{
"five": ["于夜色中"],
"four": ["一场术后对话", "晚安与睡颜", "鼹鼠党欢迎你"],
"from": "2023-04-26 06:00:00",
"name": "流光定影",
"to": "2023-05-17 17:59:59",
},
]

1
metadata/pool/pool_2.py Normal file
View File

@ -0,0 +1 @@
POOL_2 = [{"five": ["新手池"], "four": [], "from": "2023-04-26 06:00:00", "name": "新手池", "to": "2050-09-15 17:59:59"}]

View File

@ -1 +0,0 @@
POOL_200 = [{"five": ["常驻池"], "four": [], "from": "2020-09-15 06:00:00", "name": "常驻池", "to": "2050-09-15 17:59:59"}]

View File

@ -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",
},
]

View File

@ -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",
},
]

View File

@ -5,700 +5,107 @@ from typing import List
from metadata.genshin import WEAPON_DATA 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 # noinspection SpellCheckingInspection
roles = { roles = {
20000000: [ 1001: ['三月七'],
"旅行者", 1002: ['丹恒'],
"主角", 1003: ['姬子'],
"卑鄙的外乡人", 1004: ['瓦尔特'],
"荣誉骑士", 1005: ['卡芙卡'],
"", 1006: ['银狼'],
"履刑者", 1008: ['阿兰'],
"人之子", 1009: ['艾丝妲'],
"命定之人", 1013: ['黑塔'],
"荣誉骑士", 1101: ['布洛妮娅'],
"小可爱", # 丽莎 1102: ['希儿'],
"小家伙", # 八重神子 1103: ['希露瓦'],
"金发异乡人", 1104: ['杰帕德'],
"大黄金钓鱼手", # 派蒙 1105: ['娜塔莎'],
"黄毛阿姨", 1106: ['佩拉'],
"黄毛叔叔", 1107: ['克拉拉'],
"大黄倭瓜那菈", 1108: ['桑博'],
], 1109: ['虎克'],
10000002: ["神里绫华", "ayaka", "kamisato ayaka", "神里", "绫华", "神里凌华", "凌华", "白鹭公主", "神里大小姐", "冰骗骗花", "龟龟"], 1201: ['青雀'],
10000003: ["", "jean", "团长", "代理团长", "琴团长", "蒲公英骑士", "蒙德砍王", "骑士团的魂"], 1202: ['停云'],
10000005: ["", "aether", "男主", "男主角", "龙哥", "空哥", "王子"], 1203: ['罗刹'],
10000006: ["丽莎", "lisa", "图书管理员", "图书馆管理员", "蔷薇魔女"], 1204: ['景元'],
10000007: ["", "lumine", "女主", "女主角", "", "", "黄毛阿姨", "荧妹", "公主殿下"], 1206: ['素裳'],
10000014: ["芭芭拉", "barbara", "巴巴拉", "拉粑粑", "拉巴巴", "内鬼", "加湿器", "闪耀偶像", "偶像", "蒙德辣王"], 1209: ['彦卿'],
10000015: ["凯亚", "kaeya", "盖亚", "凯子哥", "凯鸭", "矿工", "矿工头子", "骑兵队长", "凯子", "凝冰渡海真君", "花脸猫"], 1211: ['白露'],
10000016: [ 8004: ['开拓者'],
"迪卢克",
"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"],
} }
not_real_roles = [10000081, 10000082] not_real_roles = []
weapons = { light_cones = {
# 1.x 20000: ['锋镝'],
"决斗之枪": ["决斗枪", "决斗", "月卡枪"], 20001: ['物穰'],
"螭骨剑": ["螭骨", "丈育剑", "离骨剑", "月卡大剑"], 20002: ['天倾'],
"黑剑": ["月卡剑"], 20003: ['琥珀'],
"苍翠猎弓": ["绿弓", "月卡弓"], 20004: ['幽邃'],
"匣里日月": ["日月"], 20005: ['齐颂'],
"匣里灭辰": ["灭辰"], 20006: ['智库'],
"匣里龙吟": ["龙吟"], 20007: ['离弦'],
"流月针": [""], 20008: ['嘉果'],
"流浪乐章": ["赌狗书", "赌狗乐章", "赌狗"], 20009: ['乐圮'],
"昭心": ["糟心"], 20010: ['戍御'],
"讨龙英杰谭": ["讨龙"], 20011: ['渊环'],
"神射手之誓": ["脚气弓", "神射手"], 20012: ['轮契'],
"黑缨枪": ["史莱姆枪"], 20013: ['灵钥'],
"黑岩刺枪": ["黑岩枪"], 20014: ['相抗'],
"黑岩战弓": ["黑岩弓"], 20015: ['蕃息'],
"天空之刃": ["天空剑"], 20016: ['俱殁'],
"天空之傲": ["天空大剑"], 20017: ['开疆'],
"天空之脊": ["天空枪", "薄荷枪", "薄荷"], 20018: ['匿影'],
"天空之卷": ["天空书", "厕纸"], 20019: ['调和'],
"天空之翼": ["天空弓"], 20020: ['睿见'],
"四风原典": ["四风", "可莉专武"], 21000: ['一场术后对话'],
"阿莫斯之弓": ["阿莫斯", "ams", "痛苦弓", "甘雨专武"], 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: ['镂月裁云之意'],
# 2.x 21033: ['无处可逃'],
"天目影打刀": ["天目刀", "天目"], 21034: ['今日亦是和平的一日'],
"桂木斩长正": ["桂木", "斩长正"], 23000: ['银河铁道之夜'],
"喜多院十文字": ["喜多院", "十文字"], 23001: ['于夜色中'],
"破魔之弓": ["破魔弓", "破魔"], 23002: ['无可取代的东西'],
"白辰之环": ["白辰", "白辰环"], 23003: ['但战斗还未结束'],
"雾切之回光": ["雾切", "神里绫华专武"], 23004: ['以世界之名'],
"飞雷之弦振": ["飞雷", "飞雷弓", "宵宫专武"], 23005: ['制胜的瞬间'],
"薙草之稻光": ["薙草", "稻光", "薙草稻光", "马尾枪", "马尾", "薙刀", "雷电将军专武"], 23010: ['拂晓之前'],
"不灭月华": ["月华", "珊瑚宫心海专武"], 23012: ['如泥酣眠'],
"「渔获」": ["鱼叉", "渔叉", "渔获"], 23013: ['时节不居'],
"衔珠海皇": ["海皇", "咸鱼剑", "咸鱼大剑"], 24000: ['记一位星神的陨落'],
"冬极白星": ["冬极", "达达利亚专武"], 24001: ['星海巡航'],
"曚云之月": ["曚云弓", "曚云"], 24002: ['记忆的质料']
"恶王丸": ["断浪大剑"],
"断浪长鳍": ["断浪", "断浪长枪", "断浪枪"],
"辰砂之纺锤": ["辰砂", "辰砂纺锤", "纺锤", "阿贝多专武"],
"赤角石溃杵": ["赤角", "石溃杵", "荒泷一斗专武", "巧乐兹"],
"息灾": ["申鹤专武"],
"神乐之真意": ["神乐", "真意", "八重神子专武"],
"证誓之明瞳": ["证誓", "明瞳", "证誓明瞳"],
"波乱月白经津": ["波乱", "月白", "波乱月白", "经津", "波波津", "神里绫人专武", "钵钵鸡"],
"若水": ["麒麟弓", "夜兰专武"],
"笼钓瓶一心": ["万叶刀", "一心传名刀", "妖刀"],
# 3.x
"猎人之径": ["草弓", "提纳里专武"],
"竭泽": ["鱼弓"],
"原木刀": ["须弥锻造单手剑"],
"森林王器": ["须弥锻造大剑", "原木大剑"],
"贯月矢": ["须弥锻造长枪", "原木枪"],
"盈满之实": ["须弥锻造法器"],
"王下近侍": ["须弥锻造弓", "原木弓"],
"赤沙之杖": ["赤沙", "赛诺专武", "船桨"],
"圣显之钥": ["圣显之钥", "圣显", "不灭剑华", "妮露专武", "板砖"],
"风信之锋": ["风信", "风信锋"],
"西福斯的月光": ["西福斯", "月光", "月光小剑", "月光剑"],
"玛海菈的水色": ["玛海菈", "水色"],
"流浪的晚星": ["晚星"],
"千夜浮梦": ["千夜", "神灯", "茶壶", "夜壶"],
"图莱杜拉的回忆": ["图莱杜拉", "铃铛", "流浪者专武"],
"东花坊时雨": ["东花坊", "时雨", ""],
"裁叶萃光": ["萃光", "韭菜刀", "裁叶", "菜叶"],
"饰铁之花": ["饰铁", "铁花"],
"苇海信标": ["苇海", "信标"],
"碧落之珑": ["碧落", "白术专武"],
} }
@ -720,16 +127,18 @@ def roleToId(name: str) -> int | None:
# noinspection PyPep8Naming # noinspection PyPep8Naming
@functools.lru_cache() @functools.lru_cache()
def weaponToName(shortname: str) -> str: def lightConeToName(shortname: str) -> str:
"""将武器昵称转为正式名""" """将光锥昵称转为正式名"""
return next((key for key, value in weapons.items() if shortname == key or shortname in value), shortname) shortname = str.casefold(shortname) # 忽略大小写
return next((value[0] for value in light_cones.values() for name in value if name == shortname), shortname)
# noinspection PyPep8Naming # noinspection PyPep8Naming
@functools.lru_cache() @functools.lru_cache()
def weaponToId(name: str) -> int | None: def lightConeToId(name: str) -> int | None:
"""获取武器ID""" """获取光锥ID"""
return next((int(key) for key, value in WEAPON_DATA.items() if weaponToName(name) in value["name"]), None) name = str.casefold(name)
return next((key for key, value in light_cones.items() for n in value if n == name), None)
# noinspection PyPep8Naming # noinspection PyPep8Naming

View File

@ -1,13 +1,11 @@
from genshin.models import BannerType from genshin.models import StarRailBannerType
PAIMONMOE_VERSION = 3 UIWF_VERSION = "v1.0"
UIGF_VERSION = "v2.2"
GACHA_TYPE_LIST = { GACHA_TYPE_LIST = {
BannerType.NOVICE: "新手祈愿", StarRailBannerType.NOVICE: "新手跃迁",
BannerType.PERMANENT: "常驻祈愿", StarRailBannerType.PERMANENT: "常驻跃迁",
BannerType.WEAPON: "武器祈愿", StarRailBannerType.CHARACTER: "角色跃迁",
BannerType.CHARACTER1: "角色祈愿", StarRailBannerType.WEAPON: "光锥跃迁",
BannerType.CHARACTER2: "角色祈愿",
} }

View File

@ -3,19 +3,16 @@ import contextlib
import datetime import datetime
import json import json
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from os import PathLike
from pathlib import Path from pathlib import Path
from typing import Dict, IO, List, Optional, Tuple, Union from typing import Dict, List, Optional, Tuple
import aiofiles import aiofiles
from genshin import AuthkeyTimeout, Client, InvalidAuthkey from genshin import AuthkeyTimeout, Client, InvalidAuthkey
from genshin.models import BannerType from genshin.models import StarRailBannerType
from openpyxl import load_workbook
from core.dependence.assets import AssetsService from core.dependence.assets import AssetsService
from metadata.pool.pool import get_pool_by_id from metadata.pool.pool import get_pool_by_id
from metadata.shortname import roleToId, weaponToId from modules.gacha_log.const import GACHA_TYPE_LIST
from modules.gacha_log.const import GACHA_TYPE_LIST, PAIMONMOE_VERSION
from modules.gacha_log.error import ( from modules.gacha_log.error import (
GachaLogAccountNotFound, GachaLogAccountNotFound,
GachaLogAuthkeyTimeout, GachaLogAuthkeyTimeout,
@ -24,7 +21,6 @@ from modules.gacha_log.error import (
GachaLogInvalidAuthkey, GachaLogInvalidAuthkey,
GachaLogMixedProvider, GachaLogMixedProvider,
GachaLogNotFound, GachaLogNotFound,
PaimonMoeGachaLogFileError,
) )
from modules.gacha_log.models import ( from modules.gacha_log.models import (
FiveStarItem, FiveStarItem,
@ -32,16 +28,14 @@ from modules.gacha_log.models import (
GachaItem, GachaItem,
GachaLogInfo, GachaLogInfo,
ImportType, ImportType,
ItemType,
Pool, Pool,
UIGFGachaType, UIWFInfo,
UIGFInfo, UIWFItem,
UIGFItem, UIWFModel,
UIGFModel,
) )
from utils.const import PROJECT_ROOT 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) GACHA_LOG_PATH.mkdir(parents=True, exist_ok=True)
@ -64,11 +58,11 @@ class GachaLog:
async def load_history_info( async def load_history_info(
self, user_id: str, uid: str, only_status: bool = False self, user_id: str, uid: str, only_status: bool = False
) -> Tuple[Optional[GachaLogInfo], bool]: ) -> Tuple[Optional[GachaLogInfo], bool]:
"""读取历史抽卡记录数据 """读取历史跃迁记录数据
:param user_id: 用户id :param user_id: 用户id
:param uid: 原神uid :param uid: 原神uid
:param only_status: 是否只读取状态 :param only_status: 是否只读取状态
:return: 抽卡记录数据 :return: 跃迁记录数据
""" """
file_path = self.gacha_log_path / f"{user_id}-{uid}.json" file_path = self.gacha_log_path / f"{user_id}-{uid}.json"
if only_status: if only_status:
@ -81,7 +75,7 @@ class GachaLog:
return GachaLogInfo(user_id=user_id, uid=uid, update_time=datetime.datetime.now()), False 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: async def remove_history_info(self, user_id: str, uid: str) -> bool:
"""删除历史抽卡记录数据 """删除历史跃迁记录数据
:param user_id: 用户id :param user_id: 用户id
:param uid: 原神uid :param uid: 原神uid
:return: 是否删除成功 :return: 是否删除成功
@ -102,10 +96,10 @@ class GachaLog:
return False return False
async def save_gacha_log_info(self, user_id: str, uid: str, info: GachaLogInfo): async def save_gacha_log_info(self, user_id: str, uid: str, info: GachaLogInfo):
"""保存抽卡记录数据 """保存跃迁记录数据
:param user_id: 用户id :param user_id: 用户id
:param uid: 原神uid :param uid: 原神uid
:param info: 抽卡记录数据 :param info: 跃迁记录数据
""" """
save_path = self.gacha_log_path / f"{user_id}-{uid}.json" save_path = self.gacha_log_path / f"{user_id}-{uid}.json"
save_path_bak = self.gacha_log_path / f"{user_id}-{uid}.json.bak" 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()) await self.save_json(save_path, info.json())
async def gacha_log_to_uigf(self, user_id: str, uid: str) -> Optional[Path]: async def gacha_log_to_uiwf(self, user_id: str, uid: str) -> Optional[Path]:
"""抽卡日记转换为 UIGF 格式 """跃迁日记转换为 UIWF 格式
:param user_id: 用户ID :param user_id: 用户ID
:param uid: 游戏UID :param uid: 游戏UID
:return: 转换是否成功转换信息UIGF文件目录 :return: 转换是否成功转换信息UIWF文件目录
""" """
data, state = await self.load_history_info(user_id, uid) data, state = await self.load_history_info(user_id, uid)
if not state: if not state:
raise GachaLogNotFound raise GachaLogNotFound
save_path = self.gacha_log_path / f"{user_id}-{uid}-uigf.json" save_path = self.gacha_log_path / f"{user_id}-{uid}-uiwf.json"
info = UIGFModel(info=UIGFInfo(uid=uid, export_app=ImportType.PaiGram.value, export_app_version="v3"), list=[]) info = UIWFModel(info=UIWFInfo(uid=uid, export_app=ImportType.PaiGram.value, export_app_version="v3"), list=[])
for items in data.item_list.values(): for items in data.item_list.values():
for item in items: for item in items:
info.list.append( info.list.append(
UIGFItem( UIWFItem(
id=item.id, id=item.id,
name=item.name, name=item.name,
gacha_type=item.gacha_type, gacha_type=item.gacha_type,
@ -153,9 +147,9 @@ class GachaLog:
four_star = len([i for i in data if i.rank_type == "4"]) four_star = len([i for i in data if i.rank_type == "4"])
if total > 50: if total > 50:
if total <= five_star * 15: if total <= five_star * 15:
raise GachaLogFileError("检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。") raise GachaLogFileError("检测到您将要导入的跃迁记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。")
if four_star < five_star: if four_star < five_star:
raise GachaLogFileError("检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。") raise GachaLogFileError("检测到您将要导入的跃迁记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。")
return True return True
except Exception as exc: # pylint: disable=W0703 except Exception as exc: # pylint: disable=W0703
raise GachaLogFileError from exc 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: def import_data_backend(all_items: List[GachaItem], gacha_log: GachaLogInfo, temp_id_data: Dict) -> int:
new_num = 0 new_num = 0
for item_info in all_items: 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]: if item_info.id not in temp_id_data[pool_name]:
gacha_log.item_list[pool_name].append(item_info) gacha_log.item_list[pool_name].append(item_info)
temp_id_data[pool_name].append(item_info.id) temp_id_data[pool_name].append(item_info.id)
@ -187,11 +181,6 @@ class GachaLog:
all_items = [GachaItem(**i) for i in data["list"]] all_items = [GachaItem(**i) for i in data["list"]]
await self.verify_data(all_items) await self.verify_data(all_items)
gacha_log, status = await self.load_history_info(str(user_id), uid) 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 放入临时数据中,加快查找速度 # 将唯一 id 放入临时数据中,加快查找速度
temp_id_data = { temp_id_data = {
pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items() 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: except GachaLogMixedProvider as e:
raise GachaLogMixedProvider from e raise GachaLogMixedProvider from e
except Exception as exc: except Exception as exc:
breakpoint()
raise GachaLogException from exc raise GachaLogException from exc
async def get_gacha_log_data(self, user_id: int, client: Client, authkey: str) -> int: async def get_gacha_log_data(self, user_id: int, client: Client, authkey: str) -> int:
"""使用authkey获取抽卡记录数据,并合并旧数据 """使用authkey获取跃迁记录数据,并合并旧数据
:param user_id: 用户id :param user_id: 用户id
:param client: genshin client :param client: genshin client
:param authkey: authkey :param authkey: authkey
@ -227,8 +217,6 @@ class GachaLog:
""" """
new_num = 0 new_num = 0
gacha_log, _ = await self.load_history_info(str(user_id), str(client.uid)) gacha_log, _ = await self.load_history_info(str(user_id), str(client.uid))
if gacha_log.get_import_type == ImportType.PAIMONMOE:
raise GachaLogMixedProvider
# 将唯一 id 放入临时数据中,加快查找速度 # 将唯一 id 放入临时数据中,加快查找速度
temp_id_data = {pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()} temp_id_data = {pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()}
try: try:
@ -261,30 +249,20 @@ class GachaLog:
for i in gacha_log.item_list.values(): for i in gacha_log.item_list.values():
i.sort(key=lambda x: (x.time, x.id)) i.sort(key=lambda x: (x.time, x.id))
gacha_log.update_time = datetime.datetime.now() 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) await self.save_gacha_log_info(str(user_id), str(client.uid), gacha_log)
return new_num return new_num
@staticmethod @staticmethod
def check_avatar_up(name: str, gacha_time: datetime.datetime) -> bool: 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 False
return True 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星角色 获取所有5星角色
:param data: 抽卡记录 :param data: 跃迁记录
:param assets: 资源服务 :param assets: 资源服务
:param pool_name: 池子名称 :param pool_name: 池子名称
:return: 5星角色列表 :return: 5星角色列表
@ -294,21 +272,27 @@ class GachaLog:
for item in data: for item in data:
count += 1 count += 1
if item.rank_type == "5": 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 = { data = {
"name": item.name, "name": item.name,
"icon": (await assets.avatar(roleToId(item.name)).icon()).as_uri(), "icon": assets.avatar.icon(item.name).as_uri(),
"count": count, "count": count,
"type": "角色", "type": "角色",
"isUp": self.check_avatar_up(item.name, item.time) if pool_name == "角色祈愿" else False, "isUp": isUp,
"isBig": (not result[-1].isUp) if result and pool_name == "角色祈愿" else False, "isBig": isBig,
"time": item.time, "time": item.time,
} }
result.append(FiveStarItem.construct(**data)) result.append(FiveStarItem.construct(**data))
elif item.item_type == "武器" and pool_name in {"武器祈愿", "常驻祈愿"}: elif item.item_type == "光锥" and pool_name in {"光锥跃迁", "常驻跃迁"}:
data = { data = {
"name": item.name, "name": item.name,
"icon": (await assets.weapon(weaponToId(item.name)).icon()).as_uri(), "icon": assets.light_cone.icon(item.name).as_uri(),
"count": count, "count": count,
"type": "武器", "type": "武器",
"isUp": False, "isUp": False,
@ -324,7 +308,7 @@ class GachaLog:
async def get_all_4_star_items(data: List[GachaItem], assets: AssetsService): async def get_all_4_star_items(data: List[GachaItem], assets: AssetsService):
""" """
获取 no_fout_star 获取 no_fout_star
:param data: 抽卡记录 :param data: 跃迁记录
:param assets: 资源服务 :param assets: 资源服务
:return: no_fout_star :return: no_fout_star
""" """
@ -336,18 +320,18 @@ class GachaLog:
if item.item_type == "角色": if item.item_type == "角色":
data = { data = {
"name": item.name, "name": item.name,
"icon": (await assets.avatar(roleToId(item.name)).icon()).as_uri(), "icon": assets.avatar.icon(item.name).as_uri(),
"count": count, "count": count,
"type": "角色", "type": "角色",
"time": item.time, "time": item.time,
} }
result.append(FourStarItem.construct(**data)) result.append(FourStarItem.construct(**data))
elif item.item_type == "武器": elif item.item_type == "光锥":
data = { data = {
"name": item.name, "name": item.name,
"icon": (await assets.weapon(weaponToId(item.name)).icon()).as_uri(), "icon": assets.light_cone.icon(item.name).as_uri(),
"count": count, "count": count,
"type": "武器", "type": "光锥",
"time": item.time, "time": item.time,
} }
result.append(FourStarItem.construct(**data)) result.append(FourStarItem.construct(**data))
@ -387,7 +371,7 @@ class GachaLog:
{"num": no_four_star, "unit": "", "lable": "未出四星"}, {"num": no_four_star, "unit": "", "lable": "未出四星"},
{"num": five_star_const, "unit": "", "lable": "五星常驻"}, {"num": five_star_const, "unit": "", "lable": "五星常驻"},
{"num": up_avg, "unit": "", "lable": "UP平均"}, {"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_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) four_star = len(all_four)
# 四星平均 # 四星平均
@ -414,7 +398,7 @@ class GachaLog:
{"num": no_five_star, "unit": "", "lable": "未出五星"}, {"num": no_five_star, "unit": "", "lable": "未出五星"},
{"num": five_star, "unit": "", "lable": "五星"}, {"num": five_star, "unit": "", "lable": "五星"},
{"num": five_star_avg, "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": "未出四星"}, {"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 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) four_star = len(all_four)
# 四星平均 # 四星平均
@ -447,7 +431,7 @@ class GachaLog:
{"num": no_five_star, "unit": "", "lable": "未出五星"}, {"num": no_five_star, "unit": "", "lable": "未出五星"},
{"num": five_star, "unit": "", "lable": "五星"}, {"num": five_star, "unit": "", "lable": "五星"},
{"num": five_star_avg, "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": "未出四星"}, {"num": no_four_star, "unit": "", "lable": "未出四星"},
@ -460,7 +444,7 @@ class GachaLog:
@staticmethod @staticmethod
def count_fortune(pool_name: str, summon_data, weapon: bool = False): def count_fortune(pool_name: str, summon_data, weapon: bool = False):
""" """
角色 武器 角色 光锥
50以下 45以下 50以下 45以下
50-60 45-55 50-60 45-55
60-70 55-65 60-70 55-65
@ -482,9 +466,9 @@ class GachaLog:
return f"{pool_name} · 非" return f"{pool_name} · 非"
return 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 user_id: 用户id
:param client: genshin client :param client: genshin client
:param pool: 池子类型 :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_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) all_four, no_four_star = await self.get_all_4_star_items(data, assets)
summon_data = None 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) 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) 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) 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) 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) 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) pool_name = self.count_fortune(pool_name, summon_data)
last_time = data[0].time.strftime("%Y-%m-%d %H:%M") last_time = data[0].time.strftime("%Y-%m-%d %H:%M")
@ -526,9 +510,9 @@ class GachaLog:
} }
async def get_pool_analysis( 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: ) -> dict:
"""获取抽卡记录分析数据 """获取跃迁记录分析数据
:param user_id: 用户id :param user_id: 用户id
:param client: genshin client :param client: genshin client
:param pool: 池子类型 :param pool: 池子类型
@ -573,7 +557,7 @@ class GachaLog:
} }
async def get_all_five_analysis(self, user_id: int, client: Client, assets: AssetsService) -> dict: async def get_all_five_analysis(self, user_id: int, client: Client, assets: AssetsService) -> dict:
"""获取五星抽卡记录分析数据 """获取五星跃迁记录分析数据
:param user_id: 用户id :param user_id: 用户id
:param client: genshin client :param client: genshin client
:param assets: 资源服务 :param assets: 资源服务
@ -612,128 +596,3 @@ class GachaLog:
"pool": pool_data, "pool": pool_data,
"hasMore": False, "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())

View File

@ -4,15 +4,13 @@ from typing import Any, Dict, List, Union
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from metadata.shortname import not_real_roles, roleToId, weaponToId from metadata.shortname import not_real_roles, roleToId, lightConeToId
from modules.gacha_log.const import UIGF_VERSION from modules.gacha_log.const import UIWF_VERSION
class ImportType(Enum): class ImportType(Enum):
PaiGram = "PaiGram" PaiGram = "PaiGram"
PAIMONMOE = "PAIMONMOE" UIWF = "UIWF"
FXQ = "FXQ"
UIGF = "UIGF"
UNKNOWN = "UNKNOWN" UNKNOWN = "UNKNOWN"
@ -44,20 +42,20 @@ class GachaItem(BaseModel):
@validator("name") @validator("name")
def name_validator(cls, v): 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: if item_id not in not_real_roles:
return v return v
raise ValueError("Invalid name") raise ValueError("Invalid name")
@validator("gacha_type") @validator("gacha_type")
def check_gacha_type(cls, v): def check_gacha_type(cls, v):
if v not in {"100", "200", "301", "302", "400"}: if v not in {"1", "2", "11", "12"}:
raise ValueError("gacha_type must be 200, 301, 302 or 400") raise ValueError("gacha_type must be 1, 2, 11 or 12")
return v return v
@validator("item_type") @validator("item_type")
def check_item_type(cls, item): def check_item_type(cls, item):
if item not in {"角色", "武器"}: if item not in {"角色", "光锥"}:
raise ValueError("error item type") raise ValueError("error item type")
return item return item
@ -74,10 +72,10 @@ class GachaLogInfo(BaseModel):
update_time: datetime.datetime update_time: datetime.datetime
import_type: str = "" import_type: str = ""
item_list: Dict[str, List[GachaItem]] = { item_list: Dict[str, List[GachaItem]] = {
"角色祈愿": [], "角色跃迁": [],
"武器祈愿": [], "光锥跃迁": [],
"常驻祈愿": [], "常驻跃迁": [],
"新手祈愿": [], "新手跃迁": [],
} }
@property @property
@ -131,37 +129,36 @@ class Pool:
class ItemType(Enum): class ItemType(Enum):
CHARACTER = "角色" CHARACTER = "角色"
WEAPON = "武器" LIGHTCONE = "光锥"
class UIGFGachaType(Enum): class UIWFGachaType(Enum):
BEGINNER = "100" BEGINNER = "2"
STANDARD = "200" STANDARD = "1"
CHARACTER = "301" CHARACTER = "11"
WEAPON = "302" LIGHTCONE = "12"
CHARACTER2 = "400"
class UIGFItem(BaseModel): class UIWFItem(BaseModel):
id: str id: str
name: str name: str
count: str = "1" count: str = "1"
gacha_type: UIGFGachaType gacha_type: UIWFGachaType
item_id: str = "" item_id: str = ""
item_type: ItemType item_type: ItemType
rank_type: str rank_type: str
time: str time: str
uigf_gacha_type: UIGFGachaType uigf_gacha_type: UIWFGachaType
class UIGFInfo(BaseModel): class UIWFInfo(BaseModel):
uid: str = "0" uid: str = "0"
lang: str = "zh-cn" lang: str = "zh-cn"
export_time: str = "" export_time: str = ""
export_timestamp: int = 0 export_timestamp: int = 0
export_app: str = "" export_app: str = ""
export_app_version: str = "" export_app_version: str = ""
uigf_version: str = UIGF_VERSION uigf_version: str = UIWF_VERSION
def __init__(self, **data: Any): def __init__(self, **data: Any):
super().__init__(**data) super().__init__(**data)
@ -170,6 +167,6 @@ class UIGFInfo(BaseModel):
self.export_timestamp = int(datetime.datetime.now().timestamp()) self.export_timestamp = int(datetime.datetime.now().timestamp())
class UIGFModel(BaseModel): class UIWFModel(BaseModel):
info: UIGFInfo info: UIWFInfo
list: List[UIGFItem] list: List[UIWFItem]

View File

@ -44,8 +44,6 @@ class Avatar(BaseModel):
"""角色ID""" """角色ID"""
name: str name: str
"""名称""" """名称"""
icon: str
"""图标"""
quality: Quality quality: Quality
"""品质""" """品质"""
destiny: Destiny destiny: Destiny

View File

@ -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]

View File

@ -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]

View File

@ -23,4 +23,3 @@ class Monster(BaseModel):
"""抗性""" """抗性"""
find_area: str find_area: str
"""发现地点""" """发现地点"""

View File

@ -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]
"""内容列表"""

View File

@ -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(
"<b>开始导入跃迁历史记录:请通过 https://starrailstation.com/cn/warp#import 获取跃迁记录链接后发送给我"
"(非 starrailstation.com 导出的文件数据)</b>\n\n"
"> 你还可以向彦卿发送从其他工具导出的 UIWF 标准的记录文件\n"
# "> 在绑定 Cookie 时添加 stoken 可能有特殊效果哦(仅限国服)\n"
"<b>注意:导入的数据将会与旧数据进行合并。</b>",
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))

View File

@ -60,14 +60,14 @@
<!-- </div>--> <!-- </div>-->
<!-- <div class="command-description">角色卡片</div>--> <!-- <div class="command-description">角色卡片</div>-->
<!-- </div>--> <!-- </div>-->
<!-- &lt;!&ndash; 最高查询类 &ndash;&gt;--> <!-- 最高查询类 -->
<!-- <div class="command">--> <div class="command">
<!-- <div class="command-name">--> <div class="command-name">
<!-- /dailynote--> /dailynote
<!-- <i class="fa fa-id-card-o ml-2"></i>--> <i class="fa fa-id-card-o ml-2"></i>
<!-- </div>--> </div>
<!-- <div class="command-description">查询实时便笺</div>--> <div class="command-description">查询实时便笺</div>
<!-- </div>--> </div>
<div class="command"> <div class="command">
<div class="command-name"> <div class="command-name">
/ledger /ledger
@ -103,21 +103,21 @@
<!-- </div>--> <!-- </div>-->
<!-- <div class="command-description">原神账号注册时间</div>--> <!-- <div class="command-description">原神账号注册时间</div>-->
<!-- </div>--> <!-- </div>-->
<!-- &lt;!&ndash;! gacha_log 相关 &ndash;&gt;--> <!--! warp_log 相关 -->
<!-- <div class="command">--> <div class="command">
<!-- <div class="command-name">--> <div class="command-name">
<!-- /gacha_log--> /warp_log
<!-- <i class="fa fa-user-circle-o ml-2"></i>--> <i class="fa fa-user-circle-o ml-2"></i>
<!-- </div>--> </div>
<!-- <div class="command-description">抽卡记录</div>--> <div class="command-description">跃迁记录</div>
<!-- </div>--> </div>
<!-- <div class="command">--> <div class="command">
<!-- <div class="command-name">--> <div class="command-name">
<!-- /gacha_count--> /warp_count
<!-- <i class="fa fa-user-circle-o ml-2"></i>--> <i class="fa fa-user-circle-o ml-2"></i>
<!-- </div>--> </div>
<!-- <div class="command-description">抽卡统计</div>--> <div class="command-description">跃迁统计</div>
<!-- </div>--> </div>
<!-- <div class="command">--> <!-- <div class="command">-->
<!-- <div class="command-name">--> <!-- <div class="command-name">-->
<!-- /pay_log--> <!-- /pay_log-->
@ -181,19 +181,19 @@
<!-- 派蒙的十万个为什么--> <!-- 派蒙的十万个为什么-->
<!-- </div>--> <!-- </div>-->
<!-- </div>--> <!-- </div>-->
<!-- &lt;!&ndash;! gacha_log 相关 &ndash;&gt;--> <!--! warp_log 相关 -->
<!-- <div class="command">--> <div class="command">
<!-- <div class="command-name">/gacha_log_import</div>--> <div class="command-name">/warp_log_import</div>
<!-- <div class="command-description">导入抽卡记录</div>--> <div class="command-description">导入跃迁记录</div>
<!-- </div>--> </div>
<!-- <div class="command">--> <div class="command">
<!-- <div class="command-name">/gacha_log_export</div>--> <div class="command-name">/warp_log_export</div>
<!-- <div class="command-description">导出抽卡记录</div>--> <div class="command-description">导出跃迁记录</div>
<!-- </div>--> </div>
<!-- <div class="command">--> <div class="command">
<!-- <div class="command-name">/gacha_log_delete</div>--> <div class="command-name">/warp_log_delete</div>
<!-- <div class="command-description">删除抽卡记录</div>--> <div class="command-description">删除跃迁记录</div>
<!-- </div>--> </div>
<!-- &lt;!&ndash;! pay_log 相关 &ndash;&gt;--> <!-- &lt;!&ndash;! pay_log 相关 &ndash;&gt;-->
<!-- <div class="command">--> <!-- <div class="command">-->
<!-- <div class="command-name">/pay_log_import</div>--> <!-- <div class="command-name">/pay_log_import</div>-->
@ -207,7 +207,7 @@
<!-- <div class="command-name">/pay_log_delete</div>--> <!-- <div class="command-name">/pay_log_delete</div>-->
<!-- <div class="command-description">删除充值记录</div>--> <!-- <div class="command-description">删除充值记录</div>-->
<!-- </div>--> <!-- </div>-->
<!--! user 相关 --> <!-- ! user 相关-->
<!-- <div class="command">--> <!-- <div class="command">-->
<!-- <div class="command-name">/setuid</div>--> <!-- <div class="command-name">/setuid</div>-->
<!-- <div class="command-description">添加/重设UID请私聊BOT</div>--> <!-- <div class="command-description">添加/重设UID请私聊BOT</div>-->

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="gacha_count.css"/>
<link rel="preload" href="../../fonts/HYWenHei-85W.ttf" as="font">
<link rel="preload" href="./../../fonts/tttgbnumber.ttf" as="font">
<link rel="preload" href="./../abyss/background/roleStarBg5.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg4.png" as="image">
<style>
.head_box {
background-position-x: 42px;
background: #fff url(../gacha_log/img/提纳里.png) no-repeat;
background-size: auto 101%;
}
</style>
<title></title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="head_box">
<div class="id_text">ID: 10001</div>
<h2 class="day_text">抽卡统计-角色祈愿</h2>
<img class="genshin_logo" src="./../../bot/help/background/genshin.png"/>
</div>
<div class="pool_box">
<div class="title_box">
<div class="name_box">
<div class="title"><h2>「枫原万叶、可莉」</h2></div>
<span class="label label_301">98抽</span>
</div>
<span class="date">2022-08-02 - 2022-08-02</span>
</div>
<div class="list_box">
<div class="item">
<div class="bg5"></div>
<span class="num life5">20</span>
<img class="role_img" src=""/>
</div>
</div>
</div>
<div class="hasMore">*完整数据请私聊查看</div>
<div class="logo">Template By Yunzai-Bot</div>
</div>
</body>
</html>

View File

@ -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;
}

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="gacha_count.css"/>
<link rel="preload" href="../../fonts/HYWenHei-85W.ttf" as="font">
<link rel="preload" href="./../../fonts/tttgbnumber.ttf" as="font">
<link rel="preload" href="./../abyss/background/roleStarBg5.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg4.png" as="image">
<style>
.head_box {
background-position-x: 42px;
background: #fff url(../gacha_log/img/提纳里.png) no-repeat;
background-size: auto 101%;
}
</style>
<title></title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="head_box">
<div class="id_text">ID: {{ uid }}</div>
<h2 class="day_text">跃迁统计-{{ typeName }}</h2>
<img class="genshin_logo" src="./../../bot/help/background/genshin.png" alt=""/>
</div>
{% for val in pool %}
<div class="pool_box">
<div class="title_box">
<div class="name_box">
<div class="title"><h2>「{{ val.name }}」</h2></div>
<span class="label label_301">{{ val.count }}抽</span>
</div>
{% if typeName != "常驻跃迁" %}
<span class="date">{{ val.start }} - {{ val.end }}</span>
{% endif %}
</div>
<div class="list_box">
{% for v in val.list %}
<div class="item">
<div class="bg{{ v.rank_type }}"></div>
<span class="num {% if v.count>=5 and v.rank_type == 5 %}life5{% endif %}">{{ v.count }}</span>
<img class="role_img" src="{{ v.icon }}" alt=""/>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% if hasMore %}
<div class="hasMore">*完整数据请私聊查看</div>
{% endif %}
<div class="logo">Template By Yunzai-Bot</div>
</div>
</body>
</html>

View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="gacha_log.css"/>
<link rel="preload" href="./../../fonts/tttgbnumber.ttf" as="font">
<link rel="preload" href="./img/提纳里.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg5.png" as="image">
<style>
.head_box {
background-position-x: 42px;
background: #fff url(./img/提纳里.png) no-repeat;
background-size: auto 101%;
}
</style>
<title></title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="info_box">
<div class="head_box">
<div class="id_text">
ID: 10001
</div>
<h2 class="day_text">
81抽
<span class="label label_301">角色祈愿池 · 欧</span>
</h2>
<img class="genshin_logo" src="./../../bot/help/background/genshin.png" alt=""/>
</div>
<div class="data_box">
<div class="tab_lable">数据总览</div>
<div class="data_line">
<div class="data_line_item">
<div class="num">1<span class="unit"></span></div>
<div class="lable">未出五星</div>
</div>
</div>
<div class="line_box">
<span class="line"></span>
<span class="text">五星历史 2022-10-07 01:10 ~ 2022-10-07 23:10</span>
<span class="line"></span>
</div>
<div class="card_list">
<div class="item star5">
<span class="minimum">UP</span>
<img class="role" src="" alt=""/>
<!-- <div class="num">{{val.num}}</div>-->
<div class="num_name">80</div>
</div>
</div>
<div class="line_box">
<span class="line"></span>
<span class="text">四星最近历史</span>
<span class="line"></span>
</div>
<div class="card_list">
<div class="item star4">
<img class="role" src="" alt=""/>
<!-- <div class="num">{{val.num}}</div>-->
<div class="num_name">10</div>
</div>
</div>
</div>
<div class="logo"> Template By Yunzai-Bot</div>
</div>
</div>
</body>
</html>

View File

@ -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;
}

View File

@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
<link rel="shortcut icon" href="#"/>
<link rel="stylesheet" type="text/css" href="gacha_log.css"/>
<link rel="preload" href="./../../fonts/tttgbnumber.ttf" as="font">
<link rel="preload" href="./img/提纳里.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg5.png" as="image">
<link rel="preload" href="./../abyss/background/roleStarBg4.png" as="image">
<style>
.head_box {
background-position: right center;
background: #fff url(./img/提纳里.png) no-repeat;
background-size: cover;
}
</style>
<title></title>
</head>
<body id="container" class="body_box">
<div class="container">
<div class="info_box">
<div class="head_box">
<div class="id_text">
ID: {{ uid }}
</div>
<h2 class="day_text">
{{ allNum }}抽
<span class="label label_{{type}}">{{ typeName }}</span>
</h2>
</div>
<div class="data_box">
<div class="tab_lable">数据总览</div>
{% for val in line %}
<div class="data_line">
{% for item in val %}
<div class="data_line_item">
<div class="num">{{item.num}}<span class="unit">{{item.unit}}</span></div>
<div class="lable">{{item.lable}}</div>
</div>
{% endfor %}
</div>
{% endfor %}
<div class="line_box">
<span class="line"></span>
<span class="text">五星历史 {{firstTime}} ~ {{lastTime}}</span>
<span class="line"></span>
</div>
<div class="card_list">
{% for val in fiveLog %}
<div class="item star5">
{% if val.isUp %}
<span class="minimum">UP</span>
{% endif %}
<img class="role" src="{{ val.icon }}" alt=""/>
<!-- <div class="num">{{val.num}}</div>-->
<div class="num_name">{{ val.count }}</div>
</div>
{% endfor %}
</div>
<div class="line_box">
<span class="line"></span>
<span class="text">四星最近历史</span>
<span class="line"></span>
</div>
<div class="card_list">
{% for val in fourLog %}
<div class="item star4">
<img class="role" src="{{ val.icon }}" alt=""/>
<!-- <div class="num">{{val.num}}</div>-->
<div class="num_name">{{ val.count }}</div>
</div>
{% endfor %}
</div>
</div>
<div class="logo"> Template By Yunzai-Bot</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -2,7 +2,7 @@ from typing import Optional
from genshin import Client from genshin import Client
from genshin.client.routes import InternationalRoute # noqa F401 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 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: def recognize_starrail_game_biz(game_uid: int) -> str:
return "hk4e_cn" if game_uid < 600000000 else "hk4e_global" return "hkrpg_cn" if game_uid < 600000000 else "hkrpg_global"
async def get_authkey_by_stoken(client: Client) -> Optional[str]: 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() headers = GACHA_HEADERS.copy()
json = { json = {
"auth_appid": "webview_gacha", "auth_appid": "webview_gacha",
"game_biz": recognize_genshin_game_biz(client.uid), "game_biz": recognize_starrail_game_biz(client.uid),
"game_uid": 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)) device_id = hex_digest(str(client.uid))
headers["x-rpc-device_id"] = device_id 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", "Content-Type": "application/json;charset=UTF-8",
} }
json = { json = {
"game_biz": recognize_genshin_game_biz(client.uid), "game_biz": recognize_starrail_game_biz(client.uid),
"lang": "zh-cn", "lang": "zh-cn",
"uid": str(client.uid), "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) await client.cookie_manager.request(url, method="POST", json=json, headers=headers)