✨ Support signal_log
avatar
weapon
command
359
core/dependence/assets.py
Normal file
@ -0,0 +1,359 @@
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from ssl import SSLZeroReturnError
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
from aiofiles import open as async_open
|
||||
from httpx import AsyncClient, HTTPError
|
||||
|
||||
from core.base_service import BaseService
|
||||
from modules.wiki.base import WikiModel
|
||||
from modules.wiki.models.avatar import Avatar
|
||||
from modules.wiki.models.weapon import Weapon
|
||||
from modules.wiki.models.buddy import Buddy
|
||||
from modules.wiki.models.equipment_suit import EquipmentSuit
|
||||
from utils.const import PROJECT_ROOT
|
||||
from utils.log import logger
|
||||
from utils.typedefs import StrOrURL, StrOrInt
|
||||
|
||||
ASSETS_PATH = PROJECT_ROOT.joinpath("resources/assets")
|
||||
ASSETS_PATH.mkdir(exist_ok=True, parents=True)
|
||||
DATA_MAP = {
|
||||
"avatar": WikiModel.BASE_URL + "avatars.json",
|
||||
"weapon": WikiModel.BASE_URL + "weapons.json",
|
||||
"buddy": WikiModel.BASE_URL + "buddy.json",
|
||||
"equipment_suit": WikiModel.BASE_URL + "equipment_suits.json",
|
||||
}
|
||||
|
||||
|
||||
class AssetsServiceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetsCouldNotFound(AssetsServiceError):
|
||||
def __init__(self, message: str, target: str):
|
||||
self.message = message
|
||||
self.target = target
|
||||
super().__init__(f"{message}: target={target}")
|
||||
|
||||
|
||||
class _AssetsService:
|
||||
client: Optional[AsyncClient] = None
|
||||
|
||||
def __init__(self, client: Optional[AsyncClient] = None) -> None:
|
||||
self.client = client
|
||||
|
||||
async def _download(self, url: StrOrURL, path: Path, retry: int = 5) -> Optional[Path]:
|
||||
"""从 url 下载图标至 path"""
|
||||
if not url:
|
||||
return None
|
||||
logger.debug("正在从 %s 下载图标至 %s", url, path)
|
||||
headers = None
|
||||
for time in range(retry):
|
||||
try:
|
||||
response = await self.client.get(url, follow_redirects=False, headers=headers)
|
||||
except Exception as error: # pylint: disable=W0703
|
||||
if not isinstance(error, (HTTPError, SSLZeroReturnError)):
|
||||
logger.error(error) # 打印未知错误
|
||||
if time != retry - 1: # 未达到重试次数
|
||||
await asyncio.sleep(1)
|
||||
else:
|
||||
raise error
|
||||
continue
|
||||
if response.status_code != 200: # 判定页面是否正常
|
||||
return None
|
||||
async with async_open(path, "wb") as file:
|
||||
await file.write(response.content) # 保存图标
|
||||
return path.resolve()
|
||||
|
||||
|
||||
class _AvatarAssets(_AssetsService):
|
||||
path: Path
|
||||
data: List[Avatar]
|
||||
name_map: Dict[str, Avatar]
|
||||
id_map: Dict[int, Avatar]
|
||||
|
||||
def __init__(self, client: Optional[AsyncClient] = None) -> None:
|
||||
super().__init__(client)
|
||||
self.path = ASSETS_PATH.joinpath("agent")
|
||||
self.path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
async def initialize(self):
|
||||
logger.info("正在初始化角色素材图标")
|
||||
html = await self.client.get(DATA_MAP["avatar"])
|
||||
self.data = [Avatar(**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.png"
|
||||
icon_path = base_path / "icon.png"
|
||||
normal_path = base_path / "normal.png"
|
||||
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("角色素材图标初始化完成")
|
||||
|
||||
def get_path(self, icon: Avatar, name: str, ext: str = "png") -> Path:
|
||||
path = self.path / f"{icon.id}"
|
||||
path.mkdir(exist_ok=True, parents=True)
|
||||
return path / f"{name}.{ext}"
|
||||
|
||||
def get_by_id(self, id_: int) -> Optional[Avatar]:
|
||||
return self.id_map.get(id_, None)
|
||||
|
||||
def get_by_name(self, name: str) -> Optional[Avatar]:
|
||||
return self.name_map.get(name, None)
|
||||
|
||||
def get_target(self, target: StrOrInt, second_target: StrOrInt = None) -> Avatar:
|
||||
data = None
|
||||
if isinstance(target, int):
|
||||
data = self.get_by_id(target)
|
||||
elif isinstance(target, str):
|
||||
data = self.get_by_name(target)
|
||||
if data is None:
|
||||
if second_target:
|
||||
return self.get_target(second_target)
|
||||
raise AssetsCouldNotFound("角色素材图标不存在", target)
|
||||
return data
|
||||
|
||||
def gacha(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
icon = self.get_target(target, second_target)
|
||||
return self.get_path(icon, "gacha")
|
||||
|
||||
def icon(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
icon = self.get_target(target, second_target)
|
||||
return self.get_path(icon, "icon")
|
||||
|
||||
def normal(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
icon = self.get_target(target, second_target)
|
||||
return self.get_path(icon, "normal")
|
||||
|
||||
|
||||
class _WeaponAssets(_AssetsService):
|
||||
path: Path
|
||||
data: List[Weapon]
|
||||
name_map: Dict[str, Weapon]
|
||||
id_map: Dict[int, Weapon]
|
||||
|
||||
def __init__(self, client: Optional[AsyncClient] = None) -> None:
|
||||
super().__init__(client)
|
||||
self.path = ASSETS_PATH.joinpath("engines")
|
||||
self.path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
async def initialize(self):
|
||||
logger.info("正在初始化武器素材图标")
|
||||
html = await self.client.get(DATA_MAP["weapon"])
|
||||
self.data = [Weapon(**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)
|
||||
icon_path = base_path / "icon.webp"
|
||||
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("武器素材图标初始化完成")
|
||||
|
||||
def get_path(self, icon: Weapon, name: str) -> Path:
|
||||
path = self.path / f"{icon.id}"
|
||||
path.mkdir(exist_ok=True, parents=True)
|
||||
return path / f"{name}.webp"
|
||||
|
||||
def get_by_id(self, id_: int) -> Optional[Weapon]:
|
||||
return self.id_map.get(id_, None)
|
||||
|
||||
def get_by_name(self, name: str) -> Optional[Weapon]:
|
||||
return self.name_map.get(name, None)
|
||||
|
||||
def get_target(self, target: StrOrInt, second_target: StrOrInt = None) -> Optional[Weapon]:
|
||||
if isinstance(target, int):
|
||||
return self.get_by_id(target)
|
||||
elif isinstance(target, str):
|
||||
return self.get_by_name(target)
|
||||
if second_target:
|
||||
return self.get_target(second_target)
|
||||
raise AssetsCouldNotFound("武器素材图标不存在", target)
|
||||
|
||||
def icon(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
icon = self.get_target(target, second_target)
|
||||
return self.get_path(icon, "icon")
|
||||
|
||||
|
||||
class _BuddyAssets(_AssetsService):
|
||||
path: Path
|
||||
data: List[Buddy]
|
||||
id_map: Dict[int, Buddy]
|
||||
name_map: Dict[str, Buddy]
|
||||
|
||||
def __init__(self, client: Optional[AsyncClient] = None) -> None:
|
||||
super().__init__(client)
|
||||
self.path = ASSETS_PATH.joinpath("buddy")
|
||||
self.path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
async def initialize(self):
|
||||
logger.info("正在初始化邦布素材图标")
|
||||
html = await self.client.get(DATA_MAP["buddy"])
|
||||
self.data = [Buddy(**data) for data in html.json()]
|
||||
self.id_map = {icon.id: icon for icon in self.data}
|
||||
self.name_map = {icon.name: icon for icon in self.data}
|
||||
tasks = []
|
||||
for icon in self.data:
|
||||
webp_path = self.path / f"{icon.id}.webp"
|
||||
png_path = self.path / f"{icon.id}.png"
|
||||
if not webp_path.exists() and icon.webp:
|
||||
tasks.append(self._download(icon.webp, webp_path))
|
||||
if not png_path.exists() and icon.png:
|
||||
tasks.append(self._download(icon.png, png_path))
|
||||
if len(tasks) >= 100:
|
||||
await asyncio.gather(*tasks)
|
||||
tasks = []
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks)
|
||||
logger.info("邦布素材图标初始化完成")
|
||||
|
||||
def get_path(self, icon: Buddy, ext: str) -> Path:
|
||||
path = self.path / f"{icon.id}.{ext}"
|
||||
return path
|
||||
|
||||
def get_by_id(self, id_: int) -> Optional[Buddy]:
|
||||
return self.id_map.get(id_, None)
|
||||
|
||||
def get_by_name(self, name: str) -> Optional[Buddy]:
|
||||
return self.name_map.get(name, None)
|
||||
|
||||
def get_target(self, target: StrOrInt, second_target: StrOrInt = None) -> Optional[Buddy]:
|
||||
if isinstance(target, int):
|
||||
return self.get_by_id(target)
|
||||
elif isinstance(target, str):
|
||||
return self.get_by_name(target)
|
||||
if second_target:
|
||||
return self.get_target(second_target)
|
||||
raise AssetsCouldNotFound("邦布素材图标不存在", target)
|
||||
|
||||
def webp(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
icon = self.get_target(target, second_target)
|
||||
return self.get_path(icon, "webp")
|
||||
|
||||
def png(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
icon = self.get_target(target, second_target)
|
||||
return self.get_path(icon, "png")
|
||||
|
||||
def icon(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
icon = self.get_target(target, second_target)
|
||||
webp_path = self.get_path(icon, "webp")
|
||||
png_path = self.get_path(icon, "png")
|
||||
if webp_path.exists():
|
||||
return webp_path
|
||||
if png_path.exists():
|
||||
return png_path
|
||||
raise AssetsCouldNotFound("邦布素材图标不存在", target)
|
||||
|
||||
|
||||
class _EquipmentSuitAssets(_AssetsService):
|
||||
path: Path
|
||||
data: List[EquipmentSuit]
|
||||
id_map: Dict[int, EquipmentSuit]
|
||||
name_map: Dict[str, EquipmentSuit]
|
||||
|
||||
def __init__(self, client: Optional[AsyncClient] = None) -> None:
|
||||
super().__init__(client)
|
||||
self.path = ASSETS_PATH.joinpath("equipment_suit")
|
||||
self.path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
async def initialize(self):
|
||||
logger.info("正在初始化驱动盘素材图标")
|
||||
html = await self.client.get(DATA_MAP["equipment_suit"])
|
||||
self.data = [EquipmentSuit(**data) for data in html.json()]
|
||||
self.id_map = {theme.id: theme for theme in self.data}
|
||||
self.name_map = {theme.name: theme for theme in self.data}
|
||||
tasks = []
|
||||
for theme in self.data:
|
||||
path = self.path / f"{theme.id}.webp"
|
||||
if not path.exists():
|
||||
tasks.append(self._download(theme.icon, path))
|
||||
if len(tasks) >= 100:
|
||||
await asyncio.gather(*tasks)
|
||||
tasks = []
|
||||
if tasks:
|
||||
await asyncio.gather(*tasks)
|
||||
logger.info("驱动盘素材图标初始化完成")
|
||||
|
||||
def get_path(self, theme: EquipmentSuit, ext: str) -> Path:
|
||||
path = self.path / f"{theme.id}.{ext}"
|
||||
return path
|
||||
|
||||
def get_by_id(self, id_: int) -> Optional[EquipmentSuit]:
|
||||
return self.id_map.get(id_, None)
|
||||
|
||||
def get_by_name(self, name_: str) -> Optional[EquipmentSuit]:
|
||||
return self.name_map.get(name_, None)
|
||||
|
||||
def get_target(self, target: StrOrInt, second_target: StrOrInt = None) -> Optional[EquipmentSuit]:
|
||||
if isinstance(target, int):
|
||||
return self.get_by_id(target)
|
||||
elif isinstance(target, str):
|
||||
return self.get_by_name(target)
|
||||
if second_target:
|
||||
return self.get_target(second_target)
|
||||
raise AssetsCouldNotFound("驱动盘素材图标不存在", target)
|
||||
|
||||
def icon(self, target: StrOrInt, second_target: StrOrInt = None) -> Path:
|
||||
theme = self.get_target(target, second_target)
|
||||
webp_path = self.get_path(theme, "webp")
|
||||
if webp_path.exists():
|
||||
return webp_path
|
||||
raise AssetsCouldNotFound("驱动盘素材图标不存在", target)
|
||||
|
||||
|
||||
class AssetsService(BaseService.Dependence):
|
||||
"""asset服务
|
||||
|
||||
用于储存和管理 asset :
|
||||
当对应的 asset (如某角色图标)不存在时,该服务会先查找本地。
|
||||
若本地不存在,则从网络上下载;若存在,则返回其路径
|
||||
"""
|
||||
|
||||
client: Optional[AsyncClient] = None
|
||||
|
||||
avatar: _AvatarAssets
|
||||
"""角色"""
|
||||
|
||||
weapon: _WeaponAssets
|
||||
"""武器"""
|
||||
|
||||
buddy: _BuddyAssets
|
||||
"""邦布"""
|
||||
|
||||
equipment_suit: _EquipmentSuitAssets
|
||||
"""驱动盘"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = AsyncClient(timeout=60.0)
|
||||
self.avatar = _AvatarAssets(self.client)
|
||||
self.weapon = _WeaponAssets(self.client)
|
||||
self.buddy = _BuddyAssets(self.client)
|
||||
self.equipment_suit = _EquipmentSuitAssets(self.client)
|
||||
|
||||
async def initialize(self): # pylint: disable=W0221
|
||||
await self.avatar.initialize()
|
||||
await self.weapon.initialize()
|
||||
await self.buddy.initialize()
|
||||
await self.equipment_suit.initialize()
|
1
core/services/game/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""GameService"""
|
61
core/services/game/cache.py
Normal file
@ -0,0 +1,61 @@
|
||||
from typing import List
|
||||
|
||||
from core.base_service import BaseService
|
||||
from core.dependence.redisdb import RedisDB
|
||||
|
||||
__all__ = [
|
||||
"GameCache",
|
||||
"GameCacheForAvatar",
|
||||
"GameCacheForStrategy",
|
||||
"GameCacheForBuddy",
|
||||
"GameCacheForWeapon",
|
||||
"GameCacheForEquipmentSuit",
|
||||
]
|
||||
|
||||
|
||||
class GameCache:
|
||||
qname: str
|
||||
|
||||
def __init__(self, redis: RedisDB, ttl: int = 3600):
|
||||
self.client = redis.client
|
||||
self.ttl = ttl
|
||||
|
||||
async def get_url_list(self, character_name: str):
|
||||
qname = f"{self.qname}:{character_name}"
|
||||
return [str(str_data, encoding="utf-8") for str_data in await self.client.lrange(qname, 0, -1)][::-1]
|
||||
|
||||
async def set_url_list(self, character_name: str, str_list: List[str]):
|
||||
qname = f"{self.qname}:{character_name}"
|
||||
await self.client.ltrim(qname, 1, 0)
|
||||
await self.client.lpush(qname, *str_list)
|
||||
await self.client.expire(qname, self.ttl)
|
||||
return await self.client.llen(qname)
|
||||
|
||||
async def get_file(self, character_name: str):
|
||||
qname = f"{self.qname}:{character_name}"
|
||||
return await self.client.get(qname)
|
||||
|
||||
async def set_file(self, character_name: str, file: str):
|
||||
qname = f"{self.qname}:{character_name}"
|
||||
await self.client.set(qname, file)
|
||||
await self.client.expire(qname, self.ttl)
|
||||
|
||||
|
||||
class GameCacheForAvatar(BaseService.Component, GameCache):
|
||||
qname = "game:avatar"
|
||||
|
||||
|
||||
class GameCacheForStrategy(BaseService.Component, GameCache):
|
||||
qname = "game:strategy"
|
||||
|
||||
|
||||
class GameCacheForBuddy(BaseService.Component, GameCache):
|
||||
qname = "game:buddy"
|
||||
|
||||
|
||||
class GameCacheForWeapon(BaseService.Component, GameCache):
|
||||
qname = "game:weapon"
|
||||
|
||||
|
||||
class GameCacheForEquipmentSuit(BaseService.Component, GameCache):
|
||||
qname = "game:relics"
|
66
core/services/game/services.py
Normal file
@ -0,0 +1,66 @@
|
||||
from core.base_service import BaseService
|
||||
from core.services.game.cache import (
|
||||
GameCacheForAvatar,
|
||||
GameCacheForStrategy,
|
||||
GameCacheForBuddy,
|
||||
GameCacheForWeapon,
|
||||
GameCacheForEquipmentSuit,
|
||||
)
|
||||
|
||||
__all__ = "GameCacheService"
|
||||
|
||||
|
||||
class GameCacheService(BaseService):
|
||||
def __init__(
|
||||
self,
|
||||
avatar_cache: GameCacheForAvatar,
|
||||
strategy_cache: GameCacheForStrategy,
|
||||
buddy_cache: GameCacheForBuddy,
|
||||
weapon_cache: GameCacheForWeapon,
|
||||
equipment_suit_cache: GameCacheForEquipmentSuit,
|
||||
):
|
||||
self.avatar_cache = avatar_cache
|
||||
self.strategy_cache = strategy_cache
|
||||
self.buddy_cache = buddy_cache
|
||||
self.weapon_cache = weapon_cache
|
||||
self.equipment_suit_cache = equipment_suit_cache
|
||||
|
||||
async def get_avatar_cache(self, character_name: str) -> str:
|
||||
cache = await self.avatar_cache.get_file(character_name)
|
||||
if cache is not None:
|
||||
return cache.decode("utf-8")
|
||||
|
||||
async def set_avatar_cache(self, character_name: str, file: str) -> None:
|
||||
await self.avatar_cache.set_file(character_name, file)
|
||||
|
||||
async def get_strategy_cache(self, character_name: str) -> str:
|
||||
cache = await self.strategy_cache.get_file(character_name)
|
||||
if cache is not None:
|
||||
return cache.decode("utf-8")
|
||||
|
||||
async def set_strategy_cache(self, character_name: str, file: str) -> None:
|
||||
await self.strategy_cache.set_file(character_name, file)
|
||||
|
||||
async def get_buddy_cache(self, character_name: str) -> str:
|
||||
cache = await self.buddy_cache.get_file(character_name)
|
||||
if cache is not None:
|
||||
return cache.decode("utf-8")
|
||||
|
||||
async def set_buddy_cache(self, character_name: str, file: str) -> None:
|
||||
await self.buddy_cache.set_file(character_name, file)
|
||||
|
||||
async def get_weapon_cache(self, weapon_name: str) -> str:
|
||||
cache = await self.weapon_cache.get_file(weapon_name)
|
||||
if cache is not None:
|
||||
return cache.decode("utf-8")
|
||||
|
||||
async def set_weapon_cache(self, weapon_name: str, file: str) -> None:
|
||||
await self.weapon_cache.set_file(weapon_name, file)
|
||||
|
||||
async def get_equipment_suit_cache(self, relics_name: str) -> str:
|
||||
cache = await self.equipment_suit_cache.get_file(relics_name)
|
||||
if cache is not None:
|
||||
return cache.decode("utf-8")
|
||||
|
||||
async def set_equipment_suit_cache(self, relics_name: str, file: str) -> None:
|
||||
await self.equipment_suit_cache.set_file(relics_name, file)
|
1
core/services/wiki/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""WikiService"""
|
44
core/services/wiki/services.py
Normal file
@ -0,0 +1,44 @@
|
||||
from core.base_service import BaseService
|
||||
from modules.wiki.character import Character
|
||||
from modules.wiki.weapon import Weapon
|
||||
from modules.wiki.buddy import Buddy
|
||||
from modules.wiki.raider import Raider
|
||||
from modules.wiki.equipment_suit import EquipmentSuit
|
||||
from utils.log import logger
|
||||
|
||||
__all__ = ["WikiService"]
|
||||
|
||||
|
||||
class WikiService(BaseService):
|
||||
def __init__(self):
|
||||
self.character = Character()
|
||||
self.weapon = Weapon()
|
||||
self.buddy = Buddy()
|
||||
self.raider = Raider()
|
||||
self.equipment_suit = EquipmentSuit()
|
||||
|
||||
async def initialize(self) -> None:
|
||||
logger.info("正在加载 Wiki 数据")
|
||||
try:
|
||||
await self.character.read()
|
||||
await self.weapon.read()
|
||||
await self.buddy.read()
|
||||
await self.raider.read()
|
||||
await self.equipment_suit.read()
|
||||
except Exception as e:
|
||||
logger.error("加载 Wiki 数据失败", exc_info=e)
|
||||
logger.info("加载 Wiki 数据完成")
|
||||
|
||||
async def refresh_wiki(self) -> None:
|
||||
logger.info("正在重新获取Wiki")
|
||||
logger.info("正在重新获取角色信息")
|
||||
await self.character.refresh()
|
||||
logger.info("正在重新获取武器信息")
|
||||
await self.weapon.refresh()
|
||||
logger.info("正在重新获取邦布信息")
|
||||
await self.buddy.refresh()
|
||||
logger.info("正在重新获取攻略信息")
|
||||
await self.raider.refresh()
|
||||
logger.info("正在重新获取驱动盘信息")
|
||||
await self.equipment_suit.refresh()
|
||||
logger.info("刷新成功")
|
@ -1,19 +1,16 @@
|
||||
from metadata.pool.pool_100 import POOL_100
|
||||
from metadata.pool.pool_200 import POOL_200
|
||||
from metadata.pool.pool_301 import POOL_301
|
||||
from metadata.pool.pool_302 import POOL_302
|
||||
from metadata.pool.pool_500 import POOL_500
|
||||
from metadata.pool.pool_1 import POOL_1
|
||||
from metadata.pool.pool_2 import POOL_2
|
||||
from metadata.pool.pool_3 import POOL_3
|
||||
from metadata.pool.pool_5 import POOL_5
|
||||
|
||||
|
||||
def get_pool_by_id(pool_type):
|
||||
if pool_type == 100:
|
||||
return POOL_100
|
||||
if pool_type == 200:
|
||||
return POOL_200
|
||||
if pool_type in [301, 400]:
|
||||
return POOL_301
|
||||
if pool_type == 302:
|
||||
return POOL_302
|
||||
if pool_type == 500:
|
||||
return POOL_500
|
||||
if pool_type == 1:
|
||||
return POOL_1
|
||||
if pool_type == 2:
|
||||
return POOL_2
|
||||
if pool_type == 3:
|
||||
return POOL_3
|
||||
if pool_type == 5:
|
||||
return POOL_5
|
||||
return None
|
||||
|
3
metadata/pool/pool_1.py
Normal file
@ -0,0 +1,3 @@
|
||||
POOL_1 = [
|
||||
{"five": ["热门卡司"], "four": [], "from": "2024-07-04 06:00:00", "name": "热门卡司", "to": "2050-09-15 17:59:59"}
|
||||
]
|
@ -1,3 +0,0 @@
|
||||
POOL_100 = [
|
||||
{"five": ["新手池"], "four": [], "from": "2020-09-15 06:00:00", "name": "新手池", "to": "2050-09-15 17:59:59"}
|
||||
]
|
9
metadata/pool/pool_2.py
Normal file
@ -0,0 +1,9 @@
|
||||
POOL_2 = [
|
||||
{
|
||||
"five": ["艾莲"],
|
||||
"four": ["苍角", "安东"],
|
||||
"name": "慵懒逐浪",
|
||||
"from": "2024-07-04 06:00:00",
|
||||
"to": "2024-07-24 11:59:59",
|
||||
},
|
||||
]
|
@ -1,3 +0,0 @@
|
||||
POOL_200 = [
|
||||
{"five": ["常驻池"], "four": [], "from": "2020-09-15 06:00:00", "name": "奔行世间", "to": "2050-09-15 17:59:59"}
|
||||
]
|
9
metadata/pool/pool_3.py
Normal file
@ -0,0 +1,9 @@
|
||||
POOL_3 = [
|
||||
{
|
||||
"five": ["深海访客"],
|
||||
"four": ["含羞恶面", "旋钻机-赤轴"],
|
||||
"name": "喧哗奏鸣",
|
||||
"from": "2024-07-04 06:00:00",
|
||||
"to": "2024-07-24 11:59:59",
|
||||
},
|
||||
]
|
@ -1,471 +0,0 @@
|
||||
POOL_301 = [
|
||||
{
|
||||
"five": ["希格雯", "芙宁娜"],
|
||||
"four": ["诺艾尔", "嘉明", "罗莎莉亚"],
|
||||
"name": "柔柔海露心|众水的颂诗",
|
||||
"from": "2024-06-25 18:00:00",
|
||||
"to": "2024-07-16 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["克洛琳德", "艾尔海森"],
|
||||
"four": ["赛索斯", "班尼特", "托马"],
|
||||
"name": "流霆贯夜|敕诫枢谋",
|
||||
"from": "2024-06-05 06:00:00",
|
||||
"to": "2024-06-25 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["流浪者", "白术"],
|
||||
"four": ["莱依拉", "珐露珊", "北斗"],
|
||||
"name": "余火变相|心珠循琅",
|
||||
"from": "2024-05-14 18:00:00",
|
||||
"to": "2024-06-04 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["阿蕾奇诺", "林尼"],
|
||||
"four": ["菲米尼", "琳妮特", "香菱"],
|
||||
"name": "炉边烬影|光与影的戏术",
|
||||
"from": "2024-04-24 06:00:00",
|
||||
"to": "2024-05-14 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["那维莱特", "枫原万叶"],
|
||||
"four": ["芭芭拉", "行秋", "烟绯"],
|
||||
"name": "谕告的潮音|红叶逐荒波",
|
||||
"from": "2024-04-02 18:00:00",
|
||||
"to": "2024-04-23 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["千织", "荒泷一斗"],
|
||||
"four": ["五郎", "云堇", "多莉"],
|
||||
"name": "千云绘羽织|鬼门斗宴",
|
||||
"from": "2024-03-13 06:00:00",
|
||||
"to": "2024-04-02 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["魈", "八重神子"],
|
||||
"four": ["瑶瑶", "辛焱", "凝光"],
|
||||
"name": "烟火之邀|华紫樱绯",
|
||||
"from": "2024-02-20 18:00:00",
|
||||
"to": "2024-03-12 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["闲云", "纳西妲"],
|
||||
"four": ["嘉明", "珐露珊", "诺艾尔"],
|
||||
"name": "云府鹤行|月草的赐慧",
|
||||
"from": "2024-01-31 06:00:00",
|
||||
"to": "2024-02-20 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["雷电将军", "宵宫"],
|
||||
"four": ["夏沃蕾", "九条裟罗", "班尼特"],
|
||||
"name": "影寂天下人|琉焰华舞",
|
||||
"from": "2024-01-09 18:00:00",
|
||||
"to": "2024-01-30 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["娜维娅", "神里绫华"],
|
||||
"four": ["砂糖", "罗莎莉亚", "坎蒂丝"],
|
||||
"name": "刺玫的铭誓|白鹭之庭",
|
||||
"from": "2023-12-20 06:00:00",
|
||||
"to": "2024-01-09 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["赛诺", "神里绫人"],
|
||||
"four": ["绮良良", "久岐忍", "香菱"],
|
||||
"name": "雳裁冥昭|苍流踏花",
|
||||
"from": "2023-11-28 18:00:00",
|
||||
"to": "2023-12-19 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["芙宁娜", "白术"],
|
||||
"four": ["夏洛蒂", "柯莱", "北斗"],
|
||||
"from": "2023-11-08 06:00:00",
|
||||
"name": "众水的颂诗|心珠循琅",
|
||||
"to": "2023-11-28 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["莱欧斯利", "温迪"],
|
||||
"four": ["重云", "托马", "多莉"],
|
||||
"from": "2023-10-17 18:00:00",
|
||||
"name": "劫中泛滥|杯装之诗",
|
||||
"to": "2023-11-07 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["那维莱特", "胡桃"],
|
||||
"four": ["菲谢尔", "行秋", "迪奥娜"],
|
||||
"from": "2023-09-27 06:00:00",
|
||||
"name": "谕告的潮音|雪霁梅香",
|
||||
"to": "2023-10-17 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["钟离", "达达利亚"],
|
||||
"four": ["菲米尼", "早柚", "诺艾尔"],
|
||||
"from": "2023-09-05 18:00:00",
|
||||
"name": "陵薮市朝|暂别冬都",
|
||||
"to": "2023-09-26 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["林尼", "夜兰"],
|
||||
"four": ["琳妮特", "班尼特", "芭芭拉"],
|
||||
"from": "2023-08-16 06:00:00",
|
||||
"name": "光与影的戏术|素霓伣天",
|
||||
"to": "2023-09-05 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["珊瑚宫心海", "流浪者"],
|
||||
"four": ["珐露珊", "罗莎莉亚", "烟绯"],
|
||||
"from": "2023-07-25 18:00:00",
|
||||
"name": "浮岳虹珠|余火变相",
|
||||
"to": "2023-08-15 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["优菈", "可莉"],
|
||||
"four": ["米卡", "雷泽", "托马"],
|
||||
"from": "2023-07-05 06:00:00",
|
||||
"name": "浪涌之瞬|闪焰的驻足",
|
||||
"to": "2023-07-25 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["艾尔海森", "枫原万叶"],
|
||||
"four": ["瑶瑶", "鹿野院平藏", "香菱"],
|
||||
"from": "2023-06-13 18:00:00",
|
||||
"name": "敕诫枢谋|叶落风随",
|
||||
"to": "2023-07-04 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["宵宫", "八重神子"],
|
||||
"four": ["绮良良", "云堇", "重云"],
|
||||
"from": "2023-05-24 06:00:00",
|
||||
"name": "琉焰华舞|浮世笑百姿",
|
||||
"to": "2023-06-13 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["白术", "甘雨"],
|
||||
"four": ["卡维", "坎蒂丝", "菲谢尔"],
|
||||
"from": "2023-05-02 18:00:00",
|
||||
"name": "心珠循琅|浮生孰来",
|
||||
"to": "2023-05-23 14:59:59",
|
||||
},
|
||||
{
|
||||
"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",
|
||||
},
|
||||
]
|
@ -1,464 +0,0 @@
|
||||
POOL_302 = [
|
||||
{
|
||||
"five": ["白雨心弦", "静水流涌之辉"],
|
||||
"four": ["千岩古剑", "匣里龙吟", "西风长枪", "西风秘典", "祭礼弓"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-06-25 18:00:00",
|
||||
"to": "2024-07-16 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["赦罪", "裁叶萃光"],
|
||||
"four": ["千岩长枪", "祭礼剑", "祭礼大剑", "流浪乐章", "绝弦"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-06-05 06:00:00",
|
||||
"to": "2024-06-25 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["图莱杜拉的回忆", "碧落之珑"],
|
||||
"four": ["勘探钻机", "测距规", "西风剑", "雨裁", "祭礼残章"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-05-14 18:00:00",
|
||||
"to": "2024-06-04 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["赤月之形", "最初的大魔术"],
|
||||
"four": ["船坞长剑", "便携动力锯", "匣里灭辰", "昭心", "西风猎弓"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-04-24 06:00:00",
|
||||
"to": "2024-05-14 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["万世流涌大典", "苍古自由之誓"],
|
||||
"four": ["暗巷的酒与诗", "幽夜华尔兹", "笛剑", "西风大剑", "西风长枪"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-04-02 18:00:00",
|
||||
"to": "2024-04-23 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["有乐御簾切", "赤角石溃杵"],
|
||||
"four": ["暗巷闪光", "暗巷猎手", "钟剑", "匣里灭辰", "西风秘典"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-03-13 06:00:00",
|
||||
"to": "2024-04-02 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["神乐之真意", "和璞鸢"],
|
||||
"four": ["千岩古剑", "匣里龙吟", "西风长枪", "流浪乐章", "绝弦"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-02-20 18:00:00",
|
||||
"to": "2024-03-12 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["鹤鸣余音", "千夜浮梦"],
|
||||
"four": ["千岩长枪", "祭礼剑", "祭礼大剑", "祭礼残章", "祭礼弓"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-01-31 06:00:00",
|
||||
"to": "2024-02-20 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["薙草之稻光", "飞雷之弦振"],
|
||||
"four": ["断浪长鳍", "西风剑", "雨裁", "昭心", "弓藏"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2024-01-09 18:00:00",
|
||||
"to": "2024-01-30 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["裁断", "雾切之回光"],
|
||||
"four": ["恶王丸", "曚云之月", "笛剑", "匣里灭辰", "西风秘典"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2023-12-20 06:00:00",
|
||||
"to": "2024-01-09 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["赤沙之杖", "波乱月白经津"],
|
||||
"four": ["西风猎弓", "流浪乐章", "西风长枪", "西风大剑", "匣里龙吟"],
|
||||
"name": "神铸赋形",
|
||||
"from": "2023-11-28 18:00:00",
|
||||
"to": "2023-12-19 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["静水流涌之辉", "碧落之珑"],
|
||||
"four": ["祭礼剑", "钟剑", "匣里灭辰", "祭礼残", "绝弦"],
|
||||
"from": "2023-11-08 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-11-28 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["金流监督", "终末嗟叹之诗"],
|
||||
"four": ["勘探钻机", "测距规", "西风剑", "雨裁", "昭心"],
|
||||
"from": "2023-10-17 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-11-07 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["万世流涌大典", "护摩之杖"],
|
||||
"four": ["船坞长剑", "便携动力锯", "幽夜华尔兹", "西风长枪", "西风秘典"],
|
||||
"from": "2023-09-27 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-10-17 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["贯虹之槊", "冬极白星"],
|
||||
"four": ["笛剑", "祭礼大剑", "匣里灭辰", "流浪乐章", "弓藏"],
|
||||
"from": "2023-09-05 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-09-26 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["最初的大魔术", "若水"],
|
||||
"four": ["祭礼剑", "西风大剑", "西风长枪", "祭礼残章", "祭礼弓"],
|
||||
"from": "2023-08-16 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-09-05 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["不灭月华", "图莱杜拉的回忆"],
|
||||
"four": ["暗巷的酒与诗", "匣里龙吟", "钟剑", "匣里灭辰", "西风猎弓"],
|
||||
"from": "2023-07-25 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-08-15 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["松籁响起之时", "四风原典"],
|
||||
"four": ["暗巷闪光", "暗巷猎手", "雨裁", "西风长枪", "昭心"],
|
||||
"from": "2023-07-05 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-07-25 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["裁叶萃光", "苍古自由之誓"],
|
||||
"four": ["断浪长鳍", "曚云之月", "西风剑", "祭礼大剑", "西风秘典"],
|
||||
"from": "2023-06-13 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-07-04 14:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["飞雷之弦振", "神乐之真意"],
|
||||
"four": ["恶王丸", "笛剑", "匣里灭辰", "流浪乐章", "弓藏"],
|
||||
"from": "2023-05-24 06:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-06-13 17:59:59",
|
||||
},
|
||||
{
|
||||
"five": ["碧落之珑", "阿莫斯之弓"],
|
||||
"four": ["玛海菈的水色", "流浪的晚星", "匣里龙吟", "西风长枪", "祭礼弓"],
|
||||
"from": "2023-05-02 18:00:00",
|
||||
"name": "神铸赋形",
|
||||
"to": "2023-05-23 14:59:59",
|
||||
},
|
||||
{
|
||||
"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",
|
||||
},
|
||||
]
|
3
metadata/pool/pool_5.py
Normal file
@ -0,0 +1,3 @@
|
||||
POOL_5 = [
|
||||
{"five": ["卓越搭档"], "four": [], "from": "2024-07-04 06:00:00", "name": "卓越搭档", "to": "2050-09-15 17:59:59"}
|
||||
]
|
@ -1,9 +0,0 @@
|
||||
POOL_500 = [
|
||||
{
|
||||
"five": ["晨风之诗"],
|
||||
"four": [],
|
||||
"name": "晨风之诗",
|
||||
"from": "2024-03-13 06:00:00",
|
||||
"to": "2024-04-02 17:59:59",
|
||||
},
|
||||
]
|
@ -1,15 +1,12 @@
|
||||
from simnet.models.genshin.wish import BannerType
|
||||
from simnet.models.zzz.wish import ZZZBannerType
|
||||
|
||||
PAIMONMOE_VERSION = 3
|
||||
UIGF_VERSION = "v3.0"
|
||||
ZZZGF_VERSION = "v1.0"
|
||||
|
||||
|
||||
GACHA_TYPE_LIST = {
|
||||
BannerType.NOVICE: "新手祈愿",
|
||||
BannerType.PERMANENT: "常驻祈愿",
|
||||
BannerType.WEAPON: "武器祈愿",
|
||||
BannerType.CHARACTER1: "角色祈愿",
|
||||
BannerType.CHARACTER2: "角色祈愿",
|
||||
BannerType.CHRONICLED: "集录祈愿",
|
||||
ZZZBannerType.STANDARD: "常驻调频",
|
||||
ZZZBannerType.CHARACTER: "代理人调频",
|
||||
ZZZBannerType.WEAPON: "音擎调频",
|
||||
ZZZBannerType.BANGBOO: "邦布调频",
|
||||
}
|
||||
GACHA_TYPE_LIST_REVERSE = {v: k for k, v in GACHA_TYPE_LIST.items()}
|
||||
|
@ -3,20 +3,17 @@ import contextlib
|
||||
import datetime
|
||||
import json
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from os import PathLike
|
||||
from pathlib import Path
|
||||
from typing import Dict, IO, List, Optional, Tuple, Union, TYPE_CHECKING
|
||||
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
|
||||
|
||||
import aiofiles
|
||||
from openpyxl import load_workbook
|
||||
from simnet import GenshinClient, Region
|
||||
from simnet import ZZZClient, Region
|
||||
from simnet.errors import AuthkeyTimeout, InvalidAuthkey
|
||||
from simnet.models.genshin.wish import BannerType
|
||||
from simnet.utils.player import recognize_genshin_server
|
||||
from simnet.models.zzz.wish import ZZZBannerType
|
||||
from simnet.utils.player import recognize_zzz_server
|
||||
|
||||
from metadata.pool.pool import get_pool_by_id
|
||||
from metadata.shortname import roleToId, weaponToId
|
||||
from modules.gacha_log.const import GACHA_TYPE_LIST, PAIMONMOE_VERSION
|
||||
from modules.gacha_log.const import GACHA_TYPE_LIST
|
||||
from modules.gacha_log.error import (
|
||||
GachaLogAccountNotFound,
|
||||
GachaLogAuthkeyTimeout,
|
||||
@ -25,7 +22,6 @@ from modules.gacha_log.error import (
|
||||
GachaLogInvalidAuthkey,
|
||||
GachaLogMixedProvider,
|
||||
GachaLogNotFound,
|
||||
PaimonMoeGachaLogFileError,
|
||||
)
|
||||
from modules.gacha_log.models import (
|
||||
FiveStarItem,
|
||||
@ -33,12 +29,10 @@ from modules.gacha_log.models import (
|
||||
GachaItem,
|
||||
GachaLogInfo,
|
||||
ImportType,
|
||||
ItemType,
|
||||
Pool,
|
||||
UIGFGachaType,
|
||||
UIGFInfo,
|
||||
UIGFItem,
|
||||
UIGFModel,
|
||||
ZZZGFInfo,
|
||||
ZZZGFItem,
|
||||
ZZZGFModel,
|
||||
)
|
||||
from utils.const import PROJECT_ROOT
|
||||
from utils.uid import mask_number
|
||||
@ -47,7 +41,7 @@ if TYPE_CHECKING:
|
||||
from core.dependence.assets import AssetsService
|
||||
|
||||
|
||||
GACHA_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "gacha_log")
|
||||
GACHA_LOG_PATH = PROJECT_ROOT.joinpath("data", "apihelper", "signal_log")
|
||||
GACHA_LOG_PATH.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
@ -70,11 +64,11 @@ class GachaLog:
|
||||
async def load_history_info(
|
||||
self, user_id: str, uid: str, only_status: bool = False
|
||||
) -> Tuple[Optional[GachaLogInfo], bool]:
|
||||
"""读取历史抽卡记录数据
|
||||
"""读取历史调频记录数据
|
||||
:param user_id: 用户id
|
||||
:param uid: 原神uid
|
||||
:param only_status: 是否只读取状态
|
||||
:return: 抽卡记录数据
|
||||
:return: 调频记录数据
|
||||
"""
|
||||
file_path = self.gacha_log_path / f"{user_id}-{uid}.json"
|
||||
if only_status:
|
||||
@ -87,7 +81,7 @@ class GachaLog:
|
||||
return GachaLogInfo(user_id=user_id, uid=uid, update_time=datetime.datetime.now()), False
|
||||
|
||||
async def remove_history_info(self, user_id: str, uid: str) -> bool:
|
||||
"""删除历史抽卡记录数据
|
||||
"""删除历史调频记录数据
|
||||
:param user_id: 用户id
|
||||
:param uid: 原神uid
|
||||
:return: 是否删除成功
|
||||
@ -125,10 +119,10 @@ class GachaLog:
|
||||
return False
|
||||
|
||||
async def save_gacha_log_info(self, user_id: str, uid: str, info: GachaLogInfo):
|
||||
"""保存抽卡记录数据
|
||||
"""保存调频记录数据
|
||||
:param user_id: 用户id
|
||||
:param uid: 玩家uid
|
||||
:param info: 抽卡记录数据
|
||||
:param info: 调频记录数据
|
||||
"""
|
||||
save_path = self.gacha_log_path / f"{user_id}-{uid}.json"
|
||||
save_path_bak = self.gacha_log_path / f"{user_id}-{uid}.json.bak"
|
||||
@ -141,29 +135,31 @@ class GachaLog:
|
||||
# 写入数据
|
||||
await self.save_json(save_path, info.json())
|
||||
|
||||
async def gacha_log_to_uigf(self, user_id: str, uid: str) -> Optional[Path]:
|
||||
"""抽卡日记转换为 UIGF 格式
|
||||
async def gacha_log_to_zzzgf(self, user_id: str, uid: str) -> Optional[Path]:
|
||||
"""调频日记转换为 ZZZGF 格式
|
||||
:param user_id: 用户ID
|
||||
:param uid: 游戏UID
|
||||
:return: 转换是否成功、转换信息、UIGF文件目录
|
||||
:return: 转换是否成功、转换信息、ZZZGF 文件目录
|
||||
"""
|
||||
data, state = await self.load_history_info(user_id, uid)
|
||||
if not state:
|
||||
raise GachaLogNotFound
|
||||
save_path = self.gacha_log_path / f"{user_id}-{uid}-uigf.json"
|
||||
info = UIGFModel(info=UIGFInfo(uid=uid, export_app=ImportType.PaiGram.value, export_app_version="v3"), list=[])
|
||||
save_path = self.gacha_log_path / f"{user_id}-{uid}-zzzgf.json"
|
||||
info = ZZZGFModel(
|
||||
info=ZZZGFInfo(uid=uid, export_app=ImportType.PaiGram.value, export_app_version="v4"), list=[]
|
||||
)
|
||||
for items in data.item_list.values():
|
||||
for item in items:
|
||||
info.list.append(
|
||||
UIGFItem(
|
||||
ZZZGFItem(
|
||||
id=item.id,
|
||||
name=item.name,
|
||||
gacha_id=item.gacha_id,
|
||||
gacha_type=item.gacha_type,
|
||||
item_id=roleToId(item.name) if item.item_type == "角色" else weaponToId(item.name),
|
||||
item_id=item.item_id,
|
||||
item_type=item.item_type,
|
||||
rank_type=item.rank_type,
|
||||
time=item.time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
uigf_gacha_type=item.gacha_type if item.gacha_type != "400" else "301",
|
||||
)
|
||||
)
|
||||
await self.save_json(save_path, json.loads(info.json()))
|
||||
@ -178,11 +174,11 @@ class GachaLog:
|
||||
if total > 50:
|
||||
if total <= five_star * 15:
|
||||
raise GachaLogFileError(
|
||||
"检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。"
|
||||
"检测到您将要导入的调频记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。"
|
||||
)
|
||||
if four_star < five_star:
|
||||
raise GachaLogFileError(
|
||||
"检测到您将要导入的抽卡记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。"
|
||||
"检测到您将要导入的调频记录中五星数量过多,可能是由于文件错误导致的,请检查后重新导入。"
|
||||
)
|
||||
return True
|
||||
except Exception as exc: # pylint: disable=W0703
|
||||
@ -192,7 +188,7 @@ class GachaLog:
|
||||
def import_data_backend(all_items: List[GachaItem], gacha_log: GachaLogInfo, temp_id_data: Dict) -> int:
|
||||
new_num = 0
|
||||
for item_info in all_items:
|
||||
pool_name = GACHA_TYPE_LIST[BannerType(int(item_info.gacha_type))]
|
||||
pool_name = GACHA_TYPE_LIST[ZZZBannerType(int(item_info.gacha_type))]
|
||||
if pool_name not in temp_id_data:
|
||||
temp_id_data[pool_name] = []
|
||||
if pool_name not in gacha_log.item_list:
|
||||
@ -219,11 +215,6 @@ class GachaLog:
|
||||
all_items = [GachaItem(**i) for i in data["list"]]
|
||||
await self.verify_data(all_items)
|
||||
gacha_log, status = await self.load_history_info(str(user_id), uid)
|
||||
if import_type == ImportType.PAIMONMOE:
|
||||
if status and gacha_log.get_import_type != ImportType.PAIMONMOE:
|
||||
raise GachaLogMixedProvider
|
||||
elif status and gacha_log.get_import_type == ImportType.PAIMONMOE:
|
||||
raise GachaLogMixedProvider
|
||||
# 将唯一 id 放入临时数据中,加快查找速度
|
||||
temp_id_data = {
|
||||
pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()
|
||||
@ -244,20 +235,21 @@ class GachaLog:
|
||||
await self.save_gacha_log_info(str(user_id), uid, gacha_log)
|
||||
return new_num
|
||||
except GachaLogAccountNotFound as e:
|
||||
raise GachaLogAccountNotFound("导入失败,文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同") from e
|
||||
raise GachaLogAccountNotFound("导入失败,文件包含的调频记录所属 uid 与你当前绑定的 uid 不同") from e
|
||||
except GachaLogMixedProvider as e:
|
||||
raise GachaLogMixedProvider from e
|
||||
except Exception as exc:
|
||||
raise GachaLogException from exc
|
||||
|
||||
@staticmethod
|
||||
def get_game_client(player_id: int) -> GenshinClient:
|
||||
if recognize_genshin_server(player_id) in ["cn_gf01", "cn_qd01"]:
|
||||
return GenshinClient(player_id=player_id, region=Region.CHINESE, lang="zh-cn")
|
||||
return GenshinClient(player_id=player_id, region=Region.OVERSEAS, lang="zh-cn")
|
||||
def get_game_client(player_id: int) -> ZZZClient:
|
||||
if recognize_zzz_server(player_id) in ["prod_gf_cn"]:
|
||||
return ZZZClient(player_id=player_id, region=Region.CHINESE, lang="zh-cn")
|
||||
else:
|
||||
return ZZZClient(player_id=player_id, region=Region.OVERSEAS, lang="zh-cn")
|
||||
|
||||
async def get_gacha_log_data(self, user_id: int, player_id: int, authkey: str) -> int:
|
||||
"""使用authkey获取抽卡记录数据,并合并旧数据
|
||||
"""使用authkey获取调频记录数据,并合并旧数据
|
||||
:param user_id: 用户id
|
||||
:param player_id: 玩家id
|
||||
:param authkey: authkey
|
||||
@ -265,10 +257,8 @@ class GachaLog:
|
||||
"""
|
||||
new_num = 0
|
||||
gacha_log, _ = await self.load_history_info(str(user_id), str(player_id))
|
||||
if gacha_log.get_import_type == ImportType.PAIMONMOE:
|
||||
raise GachaLogMixedProvider
|
||||
# 将唯一 id 放入临时数据中,加快查找速度
|
||||
temp_id_data = {pool_name: [i.id for i in pool_data] for pool_name, pool_data in gacha_log.item_list.items()}
|
||||
temp_id_data = {pool_name: {i.id: i for i in pool_data} for pool_name, pool_data in gacha_log.item_list.items()}
|
||||
client = self.get_game_client(player_id)
|
||||
try:
|
||||
for pool_id, pool_name in GACHA_TYPE_LIST.items():
|
||||
@ -277,7 +267,9 @@ class GachaLog:
|
||||
item = GachaItem(
|
||||
id=str(data.id),
|
||||
name=data.name,
|
||||
gacha_id=str(data.banner_id),
|
||||
gacha_type=str(data.banner_type.value),
|
||||
item_id=str(data.item_id),
|
||||
item_type=data.type,
|
||||
rank_type=str(data.rarity),
|
||||
time=datetime.datetime(
|
||||
@ -291,13 +283,17 @@ class GachaLog:
|
||||
)
|
||||
|
||||
if pool_name not in temp_id_data:
|
||||
temp_id_data[pool_name] = []
|
||||
temp_id_data[pool_name] = {}
|
||||
if pool_name not in gacha_log.item_list:
|
||||
gacha_log.item_list[pool_name] = []
|
||||
if item.id not in temp_id_data[pool_name]:
|
||||
if item.id not in temp_id_data[pool_name].keys():
|
||||
gacha_log.item_list[pool_name].append(item)
|
||||
temp_id_data[pool_name].append(item.id)
|
||||
temp_id_data[pool_name][item.id] = item
|
||||
new_num += 1
|
||||
else:
|
||||
old_item: GachaItem = temp_id_data[pool_name][item.id]
|
||||
old_item.gacha_id = item.gacha_id
|
||||
old_item.item_id = item.item_id
|
||||
except AuthkeyTimeout as exc:
|
||||
raise GachaLogAuthkeyTimeout from exc
|
||||
except InvalidAuthkey as exc:
|
||||
@ -307,56 +303,64 @@ class GachaLog:
|
||||
for i in gacha_log.item_list.values():
|
||||
i.sort(key=lambda x: (x.time, x.id))
|
||||
gacha_log.update_time = datetime.datetime.now()
|
||||
gacha_log.import_type = ImportType.UIGF.value
|
||||
gacha_log.import_type = ImportType.PaiGram.value
|
||||
await self.save_gacha_log_info(str(user_id), str(player_id), gacha_log)
|
||||
return new_num
|
||||
|
||||
@staticmethod
|
||||
def check_avatar_up(name: str, gacha_time: datetime.datetime) -> bool:
|
||||
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:
|
||||
if name in {"莱卡恩", "猫又", "格莉丝", "丽娜", "「11号」", "珂蕾妲"}:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def get_all_5_star_items(self, data: List[GachaItem], assets: "AssetsService", pool_name: str = "角色祈愿"):
|
||||
async def get_all_5_star_items(self, data: List[GachaItem], assets: "AssetsService", pool_name: str = "代理人调频"):
|
||||
"""
|
||||
获取所有5星角色
|
||||
:param data: 抽卡记录
|
||||
获取所有5星代理人
|
||||
:param data: 调频记录
|
||||
:param assets: 资源服务
|
||||
:param pool_name: 池子名称
|
||||
:return: 5星角色列表
|
||||
:return: 5星代理人列表
|
||||
"""
|
||||
count = 0
|
||||
result = []
|
||||
for item in data:
|
||||
count += 1
|
||||
if item.rank_type == "5":
|
||||
if item.item_type == "角色" and pool_name in {"角色祈愿", "常驻祈愿", "新手祈愿", "集录祈愿"}:
|
||||
if item.item_type == "代理人" and pool_name in {"代理人调频", "常驻调频"}:
|
||||
if pool_name == "代理人调频":
|
||||
isUp, isBig = (
|
||||
self.check_avatar_up(item.name, item.time),
|
||||
(not result[-1].isUp) if result else False,
|
||||
)
|
||||
else:
|
||||
isUp, isBig = False, False
|
||||
data = {
|
||||
"name": item.name,
|
||||
"icon": (await assets.avatar(roleToId(item.name)).icon()).as_uri(),
|
||||
"icon": assets.avatar.normal(item.name).as_uri(),
|
||||
"count": count,
|
||||
"type": "角色",
|
||||
"isUp": self.check_avatar_up(item.name, item.time) if pool_name == "角色祈愿" else False,
|
||||
"isBig": (not result[-1].isUp) if result and pool_name == "角色祈愿" else False,
|
||||
"type": "代理人",
|
||||
"isUp": isUp,
|
||||
"isBig": isBig,
|
||||
"time": item.time,
|
||||
}
|
||||
result.append(FiveStarItem.construct(**data))
|
||||
elif item.item_type == "武器" and pool_name in {"武器祈愿", "常驻祈愿", "新手祈愿", "集录祈愿"}:
|
||||
elif item.item_type == "音擎" and pool_name in {"音擎调频", "常驻调频"}:
|
||||
data = {
|
||||
"name": item.name,
|
||||
"icon": (await assets.weapon(weaponToId(item.name)).icon()).as_uri(),
|
||||
"icon": assets.weapon.icon(item.name).as_uri(),
|
||||
"count": count,
|
||||
"type": "武器",
|
||||
"type": "音擎",
|
||||
"isUp": False,
|
||||
"isBig": False,
|
||||
"time": item.time,
|
||||
}
|
||||
result.append(FiveStarItem.construct(**data))
|
||||
elif item.item_type == "邦布" and pool_name in {"邦布调频"}:
|
||||
data = {
|
||||
"name": item.name,
|
||||
"icon": assets.buddy.icon(item.name).as_uri(),
|
||||
"count": count,
|
||||
"type": "邦布",
|
||||
"isUp": False,
|
||||
"isBig": False,
|
||||
"time": item.time,
|
||||
@ -370,7 +374,7 @@ class GachaLog:
|
||||
async def get_all_4_star_items(data: List[GachaItem], assets: "AssetsService"):
|
||||
"""
|
||||
获取 no_fout_star
|
||||
:param data: 抽卡记录
|
||||
:param data: 调频记录
|
||||
:param assets: 资源服务
|
||||
:return: no_fout_star
|
||||
"""
|
||||
@ -379,21 +383,30 @@ class GachaLog:
|
||||
for item in data:
|
||||
count += 1
|
||||
if item.rank_type == "4":
|
||||
if item.item_type == "角色":
|
||||
if item.item_type == "代理人":
|
||||
data = {
|
||||
"name": item.name,
|
||||
"icon": (await assets.avatar(roleToId(item.name)).icon()).as_uri(),
|
||||
"icon": assets.avatar.normal(item.name).as_uri(),
|
||||
"count": count,
|
||||
"type": "角色",
|
||||
"type": "代理人",
|
||||
"time": item.time,
|
||||
}
|
||||
result.append(FourStarItem.construct(**data))
|
||||
elif item.item_type == "武器":
|
||||
elif item.item_type == "音擎":
|
||||
data = {
|
||||
"name": item.name,
|
||||
"icon": (await assets.weapon(weaponToId(item.name)).icon()).as_uri(),
|
||||
"icon": assets.weapon.icon(item.name).as_uri(),
|
||||
"count": count,
|
||||
"type": "武器",
|
||||
"type": "音擎",
|
||||
"time": item.time,
|
||||
}
|
||||
result.append(FourStarItem.construct(**data))
|
||||
elif item.item_type == "邦布":
|
||||
data = {
|
||||
"name": item.name,
|
||||
"icon": assets.buddy.icon(item.name).as_uri(),
|
||||
"count": count,
|
||||
"type": "邦布",
|
||||
"time": item.time,
|
||||
}
|
||||
result.append(FourStarItem.construct(**data))
|
||||
@ -402,7 +415,7 @@ class GachaLog:
|
||||
return result, count
|
||||
|
||||
@staticmethod
|
||||
def get_301_pool_data(total: int, all_five: List[FiveStarItem], no_five_star: int, no_four_star: int):
|
||||
def get_2_pool_data(total: int, all_five: List[FiveStarItem], no_five_star: int, no_four_star: int):
|
||||
# 总共五星
|
||||
five_star = len(all_five)
|
||||
five_star_up = len([i for i in all_five if i.isUp])
|
||||
@ -435,20 +448,20 @@ class GachaLog:
|
||||
{"num": no_four_star, "unit": "抽", "lable": "未出四星"},
|
||||
{"num": five_star_const, "unit": "个", "lable": "五星常驻"},
|
||||
{"num": up_avg, "unit": "抽", "lable": "UP平均"},
|
||||
{"num": up_cost, "unit": "", "lable": "UP花费原石"},
|
||||
{"num": up_cost, "unit": "", "lable": "UP花费星琼"},
|
||||
],
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_200_pool_data(
|
||||
def get_1_pool_data(
|
||||
total: int, all_five: List[FiveStarItem], all_four: List[FourStarItem], no_five_star: int, no_four_star: int
|
||||
):
|
||||
# 总共五星
|
||||
five_star = len(all_five)
|
||||
# 五星平均
|
||||
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)
|
||||
# 四星平均
|
||||
@ -462,7 +475,7 @@ class GachaLog:
|
||||
{"num": no_five_star, "unit": "抽", "lable": "未出五星"},
|
||||
{"num": five_star, "unit": "个", "lable": "五星"},
|
||||
{"num": five_star_avg, "unit": "抽", "lable": "五星平均"},
|
||||
{"num": five_star_weapon, "unit": "个", "lable": "五星武器"},
|
||||
{"num": five_star_weapon, "unit": "个", "lable": "五星音擎"},
|
||||
{"num": no_four_star, "unit": "抽", "lable": "未出四星"},
|
||||
{"num": four_star, "unit": "个", "lable": "四星"},
|
||||
{"num": four_star_avg, "unit": "抽", "lable": "四星平均"},
|
||||
@ -471,15 +484,15 @@ class GachaLog:
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_302_pool_data(
|
||||
def get_3_pool_data(
|
||||
total: int, all_five: List[FiveStarItem], all_four: List[FourStarItem], no_five_star: int, no_four_star: int
|
||||
):
|
||||
# 总共五星
|
||||
five_star = len(all_five)
|
||||
# 五星平均
|
||||
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)
|
||||
# 四星平均
|
||||
@ -493,38 +506,7 @@ class GachaLog:
|
||||
{"num": no_five_star, "unit": "抽", "lable": "未出五星"},
|
||||
{"num": five_star, "unit": "个", "lable": "五星"},
|
||||
{"num": five_star_avg, "unit": "抽", "lable": "五星平均"},
|
||||
{"num": four_star_weapon, "unit": "个", "lable": "四星武器"},
|
||||
{"num": no_four_star, "unit": "抽", "lable": "未出四星"},
|
||||
{"num": four_star, "unit": "个", "lable": "四星"},
|
||||
{"num": four_star_avg, "unit": "抽", "lable": "四星平均"},
|
||||
{"num": four_star_max_count, "unit": four_star_max, "lable": "四星最多"},
|
||||
],
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_500_pool_data(
|
||||
total: int, all_five: List[FiveStarItem], all_four: List[FourStarItem], no_five_star: int, no_four_star: int
|
||||
):
|
||||
# 总共五星
|
||||
five_star = len(all_five)
|
||||
# 五星平均
|
||||
five_star_avg = round((total - no_five_star) / five_star, 2) if five_star != 0 else 0
|
||||
# 四星角色
|
||||
four_star_character = len([i for i in all_four if i.type == "角色"])
|
||||
# 总共四星
|
||||
four_star = len(all_four)
|
||||
# 四星平均
|
||||
four_star_avg = round((total - no_four_star) / four_star, 2) if four_star != 0 else 0
|
||||
# 四星最多
|
||||
four_star_name_list = [i.name for i in all_four]
|
||||
four_star_max = max(four_star_name_list, key=four_star_name_list.count) if four_star_name_list else ""
|
||||
four_star_max_count = four_star_name_list.count(four_star_max)
|
||||
return [
|
||||
[
|
||||
{"num": no_five_star, "unit": "抽", "lable": "未出五星"},
|
||||
{"num": five_star, "unit": "个", "lable": "五星"},
|
||||
{"num": five_star_avg, "unit": "抽", "lable": "五星平均"},
|
||||
{"num": four_star_character, "unit": "个", "lable": "四星角色"},
|
||||
{"num": four_star_weapon, "unit": "个", "lable": "四星"},
|
||||
{"num": no_four_star, "unit": "抽", "lable": "未出四星"},
|
||||
{"num": four_star, "unit": "个", "lable": "四星"},
|
||||
{"num": four_star_avg, "unit": "抽", "lable": "四星平均"},
|
||||
@ -535,7 +517,7 @@ class GachaLog:
|
||||
@staticmethod
|
||||
def count_fortune(pool_name: str, summon_data, weapon: bool = False):
|
||||
"""
|
||||
角色 武器
|
||||
代理人 音擎
|
||||
欧 50以下 45以下
|
||||
吉 50-60 45-55
|
||||
中 60-70 55-65
|
||||
@ -557,9 +539,9 @@ class GachaLog:
|
||||
return f"{pool_name} · 非"
|
||||
return pool_name
|
||||
|
||||
async def get_analysis(self, user_id: int, player_id: int, pool: BannerType, assets: "AssetsService"):
|
||||
async def get_analysis(self, user_id: int, player_id: int, pool: ZZZBannerType, assets: "AssetsService"):
|
||||
"""
|
||||
获取抽卡记录分析数据
|
||||
获取调频记录分析数据
|
||||
:param user_id: 用户id
|
||||
:param player_id: 玩家id
|
||||
:param pool: 池子类型
|
||||
@ -579,17 +561,14 @@ class GachaLog:
|
||||
all_five, no_five_star = await self.get_all_5_star_items(data, assets, pool_name)
|
||||
all_four, no_four_star = await self.get_all_4_star_items(data, assets)
|
||||
summon_data = None
|
||||
if pool in [BannerType.CHARACTER1, BannerType.CHARACTER2, BannerType.NOVICE]:
|
||||
summon_data = self.get_301_pool_data(total, all_five, no_five_star, no_four_star)
|
||||
if pool == ZZZBannerType.CHARACTER:
|
||||
summon_data = self.get_2_pool_data(total, all_five, no_five_star, no_four_star)
|
||||
pool_name = self.count_fortune(pool_name, summon_data)
|
||||
elif pool == BannerType.WEAPON:
|
||||
summon_data = self.get_302_pool_data(total, all_five, all_four, no_five_star, no_four_star)
|
||||
elif pool in [ZZZBannerType.WEAPON, ZZZBannerType.BANGBOO]:
|
||||
summon_data = self.get_3_pool_data(total, all_five, all_four, no_five_star, no_four_star)
|
||||
pool_name = self.count_fortune(pool_name, summon_data, True)
|
||||
elif pool == BannerType.PERMANENT:
|
||||
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)
|
||||
elif pool == BannerType.CHRONICLED:
|
||||
summon_data = self.get_500_pool_data(total, all_five, all_four, no_five_star, no_four_star)
|
||||
elif pool == ZZZBannerType.STANDARD:
|
||||
summon_data = self.get_1_pool_data(total, all_five, all_four, no_five_star, no_four_star)
|
||||
pool_name = self.count_fortune(pool_name, summon_data)
|
||||
last_time = data[0].time.strftime("%Y-%m-%d %H:%M")
|
||||
first_time = data[-1].time.strftime("%Y-%m-%d %H:%M")
|
||||
@ -606,9 +585,9 @@ class GachaLog:
|
||||
}
|
||||
|
||||
async def get_pool_analysis(
|
||||
self, user_id: int, player_id: int, pool: BannerType, assets: "AssetsService", group: bool
|
||||
self, user_id: int, player_id: int, pool: ZZZBannerType, assets: "AssetsService", group: bool
|
||||
) -> dict:
|
||||
"""获取抽卡记录分析数据
|
||||
"""获取调频记录分析数据
|
||||
:param user_id: 用户id
|
||||
:param player_id: 玩家id
|
||||
:param pool: 池子类型
|
||||
@ -648,14 +627,14 @@ class GachaLog:
|
||||
)
|
||||
pool_data = [i for i in pool_data if i["count"] > 0]
|
||||
return {
|
||||
"uid": player_id,
|
||||
"uid": mask_number(player_id),
|
||||
"typeName": pool_name,
|
||||
"pool": pool_data[:6] if group else pool_data,
|
||||
"hasMore": len(pool_data) > 6,
|
||||
}
|
||||
|
||||
async def get_all_five_analysis(self, user_id: int, player_id: int, assets: "AssetsService") -> dict:
|
||||
"""获取五星抽卡记录分析数据
|
||||
"""获取五星调频记录分析数据
|
||||
:param user_id: 用户id
|
||||
:param player_id: 玩家id
|
||||
:param assets: 资源服务
|
||||
@ -689,133 +668,8 @@ class GachaLog:
|
||||
for up_pool in pools
|
||||
]
|
||||
return {
|
||||
"uid": player_id,
|
||||
"uid": mask_number(player_id),
|
||||
"typeName": "五星列表",
|
||||
"pool": pool_data,
|
||||
"hasMore": False,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def convert_xlsx_to_uigf(file: Union[str, PathLike, IO[bytes]], zh_dict: Dict) -> Dict:
|
||||
"""转换 paimone.moe 或 非小酋 导出 xlsx 数据为 UIGF 格式
|
||||
:param file: 导出的 xlsx 文件
|
||||
:param zh_dict:
|
||||
:return: UIGF 格式数据
|
||||
"""
|
||||
|
||||
def from_paimon_moe(
|
||||
uigf_gacha_type: UIGFGachaType, item_type: str, name: str, date_string: str, rank_type: int, _id: int
|
||||
) -> UIGFItem:
|
||||
item_type = ItemType.CHARACTER if item_type == "Character" else ItemType.WEAPON
|
||||
return UIGFItem(
|
||||
id=str(_id),
|
||||
name=zh_dict[name],
|
||||
gacha_type=uigf_gacha_type,
|
||||
item_type=item_type,
|
||||
rank_type=str(rank_type),
|
||||
time=date_string,
|
||||
uigf_gacha_type=uigf_gacha_type,
|
||||
)
|
||||
|
||||
def from_uigf(
|
||||
uigf_gacha_type: str,
|
||||
gacha__type: str,
|
||||
item_type: str,
|
||||
name: str,
|
||||
date_string: str,
|
||||
rank_type: str,
|
||||
_id: str,
|
||||
) -> UIGFItem:
|
||||
return UIGFItem(
|
||||
id=_id,
|
||||
name=name,
|
||||
gacha_type=gacha__type,
|
||||
item_type=item_type,
|
||||
rank_type=rank_type,
|
||||
time=date_string,
|
||||
uigf_gacha_type=uigf_gacha_type,
|
||||
)
|
||||
|
||||
def from_fxq(
|
||||
uigf_gacha_type: UIGFGachaType, item_type: str, name: str, date_string: str, rank_type: int, _id: int
|
||||
) -> UIGFItem:
|
||||
item_type = ItemType.CHARACTER if item_type == "角色" else ItemType.WEAPON
|
||||
return UIGFItem(
|
||||
id=str(_id),
|
||||
name=name,
|
||||
gacha_type=uigf_gacha_type,
|
||||
item_type=item_type,
|
||||
rank_type=str(rank_type),
|
||||
time=date_string,
|
||||
uigf_gacha_type=uigf_gacha_type,
|
||||
)
|
||||
|
||||
wb = load_workbook(file)
|
||||
wb_len = len(wb.worksheets)
|
||||
|
||||
if wb_len == 6:
|
||||
import_type = ImportType.PAIMONMOE
|
||||
elif wb_len == 5:
|
||||
import_type = ImportType.UIGF
|
||||
elif wb_len == 4:
|
||||
import_type = ImportType.FXQ
|
||||
else:
|
||||
raise GachaLogFileError("xlsx 格式错误")
|
||||
|
||||
paimonmoe_sheets = {
|
||||
UIGFGachaType.BEGINNER: "Beginners' Wish",
|
||||
UIGFGachaType.STANDARD: "Standard",
|
||||
UIGFGachaType.CHARACTER: "Character Event",
|
||||
UIGFGachaType.WEAPON: "Weapon Event",
|
||||
}
|
||||
fxq_sheets = {
|
||||
UIGFGachaType.BEGINNER: "新手祈愿",
|
||||
UIGFGachaType.STANDARD: "常驻祈愿",
|
||||
UIGFGachaType.CHARACTER: "角色活动祈愿",
|
||||
UIGFGachaType.WEAPON: "武器活动祈愿",
|
||||
}
|
||||
data = UIGFModel(info=UIGFInfo(export_app=import_type.value), list=[])
|
||||
if import_type == ImportType.PAIMONMOE:
|
||||
ws = wb["Information"]
|
||||
if ws["B2"].value != PAIMONMOE_VERSION:
|
||||
raise PaimonMoeGachaLogFileError(file_version=ws["B2"].value, support_version=PAIMONMOE_VERSION)
|
||||
count = 1
|
||||
for gacha_type in paimonmoe_sheets:
|
||||
ws = wb[paimonmoe_sheets[gacha_type]]
|
||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||
if row[0] is None:
|
||||
break
|
||||
data.list.append(from_paimon_moe(gacha_type, row[0], row[1], row[2], row[3], count))
|
||||
count += 1
|
||||
elif import_type == ImportType.UIGF:
|
||||
ws = wb["原始数据"]
|
||||
type_map = {}
|
||||
count = 0
|
||||
for row in ws["1"]:
|
||||
if row.value is None:
|
||||
break
|
||||
type_map[row.value] = count
|
||||
count += 1
|
||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||
if row[0] is None:
|
||||
break
|
||||
data.list.append(
|
||||
from_uigf(
|
||||
row[type_map["uigf_gacha_type"]],
|
||||
row[type_map["gacha_type"]],
|
||||
row[type_map["item_type"]],
|
||||
row[type_map["name"]],
|
||||
row[type_map["time"]],
|
||||
row[type_map["rank_type"]],
|
||||
row[type_map["id"]],
|
||||
)
|
||||
)
|
||||
else:
|
||||
for gacha_type in fxq_sheets:
|
||||
ws = wb[fxq_sheets[gacha_type]]
|
||||
for row in ws.iter_rows(min_row=2, values_only=True):
|
||||
if row[0] is None:
|
||||
break
|
||||
data.list.append(from_fxq(gacha_type, row[2], row[1], row[0], row[3], row[6]))
|
||||
|
||||
return json.loads(data.json())
|
||||
|
@ -4,15 +4,13 @@ from typing import Any, Dict, List, Union
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
from metadata.shortname import not_real_roles, roleToId, weaponToId
|
||||
from modules.gacha_log.const import UIGF_VERSION
|
||||
from metadata.shortname import not_real_roles, roleToId, weaponToId, buddyToId
|
||||
from modules.gacha_log.const import ZZZGF_VERSION
|
||||
|
||||
|
||||
class ImportType(Enum):
|
||||
PaiGram = "PaiGram"
|
||||
PAIMONMOE = "PAIMONMOE"
|
||||
FXQ = "FXQ"
|
||||
UIGF = "UIGF"
|
||||
ZZZGF = "ZZZGF"
|
||||
UNKNOWN = "UNKNOWN"
|
||||
|
||||
|
||||
@ -37,27 +35,29 @@ class FourStarItem(BaseModel):
|
||||
class GachaItem(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
gacha_id: str = ""
|
||||
gacha_type: str
|
||||
item_id: str = ""
|
||||
item_type: str
|
||||
rank_type: str
|
||||
time: datetime.datetime
|
||||
|
||||
@validator("name")
|
||||
def name_validator(cls, v):
|
||||
if item_id := (roleToId(v) or weaponToId(v)):
|
||||
if item_id := (roleToId(v) or weaponToId(v) or buddyToId(v)):
|
||||
if item_id not in not_real_roles:
|
||||
return v
|
||||
raise ValueError(f"Invalid name {v}")
|
||||
|
||||
@validator("gacha_type")
|
||||
def check_gacha_type(cls, v):
|
||||
if v not in {"100", "200", "301", "302", "400", "500"}:
|
||||
raise ValueError(f"gacha_type must be 200, 301, 302, 400, 500, invalid value: {v}")
|
||||
if v not in {"1", "2", "3", "5"}:
|
||||
raise ValueError(f"gacha_type must be 1, 2, 3 or 5, invalid value: {v}")
|
||||
return v
|
||||
|
||||
@validator("item_type")
|
||||
def check_item_type(cls, item):
|
||||
if item not in {"角色", "武器"}:
|
||||
if item not in {"代理人", "音擎", "邦布"}:
|
||||
raise ValueError(f"error item type {item}")
|
||||
return item
|
||||
|
||||
@ -74,11 +74,10 @@ class GachaLogInfo(BaseModel):
|
||||
update_time: datetime.datetime
|
||||
import_type: str = ""
|
||||
item_list: Dict[str, List[GachaItem]] = {
|
||||
"角色祈愿": [],
|
||||
"武器祈愿": [],
|
||||
"常驻祈愿": [],
|
||||
"新手祈愿": [],
|
||||
"集录祈愿": [],
|
||||
"代理人调频": [],
|
||||
"音擎调频": [],
|
||||
"常驻调频": [],
|
||||
"邦布调频": [],
|
||||
}
|
||||
|
||||
@property
|
||||
@ -131,57 +130,47 @@ class Pool:
|
||||
|
||||
|
||||
class ItemType(Enum):
|
||||
CHARACTER = "角色"
|
||||
WEAPON = "武器"
|
||||
CHARACTER = "代理人"
|
||||
WEAPON = "音擎"
|
||||
BANGBOO = "邦布"
|
||||
|
||||
|
||||
class UIGFGachaType(Enum):
|
||||
BEGINNER = "100"
|
||||
STANDARD = "200"
|
||||
CHARACTER = "301"
|
||||
WEAPON = "302"
|
||||
CHARACTER2 = "400"
|
||||
CHRONICLED = "500"
|
||||
class ZZZGFGachaType(Enum):
|
||||
STANDARD = "1"
|
||||
CHARACTER = "2"
|
||||
WEAPON = "3"
|
||||
BANGBOO = "5"
|
||||
|
||||
|
||||
class UIGFItem(BaseModel):
|
||||
class ZZZGFItem(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
count: str = "1"
|
||||
gacha_type: UIGFGachaType
|
||||
gacha_id: str = ""
|
||||
gacha_type: ZZZGFGachaType
|
||||
item_id: str = ""
|
||||
item_type: ItemType
|
||||
rank_type: str
|
||||
time: str
|
||||
uigf_gacha_type: UIGFGachaType
|
||||
|
||||
|
||||
class UIGFInfo(BaseModel):
|
||||
class ZZZGFInfo(BaseModel):
|
||||
uid: str = "0"
|
||||
lang: str = "zh-cn"
|
||||
region_time_zone: int = 8
|
||||
export_time: str = ""
|
||||
export_timestamp: int = 0
|
||||
export_app: str = ""
|
||||
export_app_version: str = ""
|
||||
uigf_version: str = UIGF_VERSION
|
||||
region_time_zone: int = 8
|
||||
zzzgf_version: str = ZZZGF_VERSION
|
||||
|
||||
def __init__(self, **data: Any):
|
||||
data["region_time_zone"] = data.get("region_time_zone", UIGFInfo.get_region_time_zone(data.get("uid", "0")))
|
||||
super().__init__(**data)
|
||||
if not self.export_time:
|
||||
self.export_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.export_timestamp = int(datetime.datetime.now().timestamp())
|
||||
|
||||
@staticmethod
|
||||
def get_region_time_zone(uid: str) -> int:
|
||||
if uid.startswith("6"):
|
||||
return -5
|
||||
if uid.startswith("7"):
|
||||
return 1
|
||||
return 8
|
||||
|
||||
|
||||
class UIGFModel(BaseModel):
|
||||
info: UIGFInfo
|
||||
list: List[UIGFItem]
|
||||
class ZZZGFModel(BaseModel):
|
||||
info: ZZZGFInfo
|
||||
list: List[ZZZGFItem]
|
||||
|
@ -1,254 +1,39 @@
|
||||
import asyncio
|
||||
import re
|
||||
from abc import abstractmethod
|
||||
from asyncio import Queue
|
||||
from multiprocessing import Value
|
||||
from ssl import SSLZeroReturnError
|
||||
from typing import AsyncIterator, ClassVar, List, Optional, Tuple, Union
|
||||
from pathlib import Path
|
||||
from typing import List, Dict
|
||||
|
||||
import anyio
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL, AsyncClient, HTTPError, Response
|
||||
from pydantic import BaseConfig as PydanticBaseConfig
|
||||
from pydantic import BaseModel as PydanticBaseModel
|
||||
|
||||
from utils.log import logger
|
||||
|
||||
try:
|
||||
import ujson as jsonlib
|
||||
except ImportError:
|
||||
import json as jsonlib
|
||||
|
||||
__all__ = ["Model", "WikiModel", "HONEY_HOST"]
|
||||
|
||||
HONEY_HOST = URL("https://gensh.honeyhunterworld.com/")
|
||||
import aiofiles
|
||||
import ujson as jsonlib
|
||||
from httpx import AsyncClient
|
||||
|
||||
|
||||
class Model(PydanticBaseModel):
|
||||
"""基类"""
|
||||
class WikiModel:
|
||||
BASE_URL = "https://zzz-res.paimon.vip/data/"
|
||||
BASE_PATH = Path("data/wiki-zzz")
|
||||
BASE_PATH.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
# 让每次new的时候都解析
|
||||
cls.update_forward_refs()
|
||||
return super(Model, cls).__new__(cls) # pylint: disable=E1120
|
||||
def __init__(self):
|
||||
self.client = AsyncClient(timeout=120.0)
|
||||
|
||||
class Config(PydanticBaseConfig):
|
||||
# 使用 ujson 作为解析库
|
||||
json_dumps = jsonlib.dumps
|
||||
json_loads = jsonlib.loads
|
||||
|
||||
|
||||
class WikiModel(Model):
|
||||
# noinspection PyUnresolvedReferences
|
||||
"""wiki所用到的基类
|
||||
|
||||
Attributes:
|
||||
id (:obj:`int`): ID
|
||||
name (:obj:`str`): 名称
|
||||
rarity (:obj:`int`): 星级
|
||||
|
||||
_client (:class:`httpx.AsyncClient`): 发起 http 请求的 client
|
||||
"""
|
||||
_client: ClassVar[AsyncClient] = AsyncClient()
|
||||
|
||||
id: str
|
||||
name: str
|
||||
rarity: int
|
||||
async def remote_get(self, url: str):
|
||||
return await self.client.get(url)
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def scrape_urls() -> List[URL]:
|
||||
"""爬取的目标网页集合
|
||||
|
||||
例如有关武器的页面有:
|
||||
[单手剑](https://genshin.honeyhunterworld.com/fam_sword/?lang=CHS)
|
||||
[双手剑](https://genshin.honeyhunterworld.com/fam_claymore/?lang=CHS)
|
||||
[长柄武器](https://genshin.honeyhunterworld.com/fam_polearm/?lang=CHS)
|
||||
。。。
|
||||
这个函数就是返回这些页面的网址所组成的 List
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def _client_get(cls, url: Union[URL, str], retry_times: int = 5, sleep: float = 1) -> Response:
|
||||
"""用自己的 client 发起 get 请求的快捷函数
|
||||
|
||||
Args:
|
||||
url: 发起请求的 url
|
||||
retry_times: 发生错误时的重复次数。不能小于 0 .
|
||||
sleep: 发生错误后等待重试的时间,单位为秒。
|
||||
Returns:
|
||||
返回对应的请求
|
||||
Raises:
|
||||
请求所需要的异常
|
||||
"""
|
||||
for _ in range(retry_times):
|
||||
try:
|
||||
return await cls._client.get(url, follow_redirects=True)
|
||||
except (HTTPError, SSLZeroReturnError):
|
||||
await anyio.sleep(sleep)
|
||||
return await cls._client.get(url, follow_redirects=True) # 防止 retry_times 等于 0 的时候无法发生请求
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
async def _parse_soup(cls, soup: BeautifulSoup) -> "WikiModel":
|
||||
"""解析 soup 生成对应 WikiModel
|
||||
|
||||
Args:
|
||||
soup: 需要解析的 soup
|
||||
Returns:
|
||||
返回对应的 WikiModel
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
async def _scrape(cls, url: Union[URL, str]) -> "WikiModel":
|
||||
"""从 url 中爬取数据,并返回对应的 Model
|
||||
|
||||
Args:
|
||||
url: 目标 url. 可以为字符串 str , 也可以为 httpx.URL
|
||||
Returns:
|
||||
返回对应的 WikiModel
|
||||
"""
|
||||
response = await cls._client_get(url)
|
||||
return await cls._parse_soup(BeautifulSoup(response.text, "lxml"))
|
||||
|
||||
@classmethod
|
||||
async def get_by_id(cls, id_: str) -> "WikiModel":
|
||||
"""通过ID获取Model
|
||||
|
||||
Args:
|
||||
id_: 目标 ID
|
||||
Returns:
|
||||
返回对应的 WikiModel
|
||||
"""
|
||||
return await cls._scrape(await cls.get_url_by_id(id_))
|
||||
|
||||
@classmethod
|
||||
async def get_by_name(cls, name: str) -> Optional["WikiModel"]:
|
||||
"""通过名称获取Model
|
||||
|
||||
Args:
|
||||
name: 目标名
|
||||
Returns:
|
||||
返回对应的 WikiModel
|
||||
"""
|
||||
url = await cls.get_url_by_name(name)
|
||||
return None if url is None else await cls._scrape(url)
|
||||
|
||||
@classmethod
|
||||
async def get_full_data(cls) -> List["WikiModel"]:
|
||||
"""获取全部数据的 Model
|
||||
|
||||
Returns:
|
||||
返回能爬到的所有的 Model 所组成的 List
|
||||
"""
|
||||
return [i async for i in cls.full_data_generator()]
|
||||
|
||||
@classmethod
|
||||
async def full_data_generator(cls) -> AsyncIterator["WikiModel"]:
|
||||
"""Model 生成器
|
||||
|
||||
这是一个异步生成器,该函数在使用时会爬取所有数据,并将其转为对应的 Model,然后存至一个队列中
|
||||
当有需要时,再一个一个地迭代取出
|
||||
|
||||
Returns:
|
||||
返回能爬到的所有的 WikiModel 所组成的 List
|
||||
"""
|
||||
queue: Queue["WikiModel"] = Queue() # 存放 Model 的队列
|
||||
signal = Value("i", 0) # 一个用于异步任务同步的信号
|
||||
|
||||
async def task(u):
|
||||
# 包装的爬虫任务
|
||||
try:
|
||||
await queue.put(await cls._scrape(u)) # 爬取一条数据,并将其放入队列中
|
||||
except Exception as exc: # pylint: disable=W0703
|
||||
logger.error("爬取数据出现异常 %s", str(exc))
|
||||
logger.debug("异常信息", exc_info=exc)
|
||||
finally:
|
||||
signal.value -= 1 # 信号量减少 1 ,说明该爬虫任务已经完成
|
||||
|
||||
for _, url in await cls.get_name_list(with_url=True): # 遍历爬取所有需要爬取的页面
|
||||
signal.value += 1 # 信号量增加 1 ,说明有一个爬虫任务被添加
|
||||
asyncio.create_task(task(url)) # 创建一个爬虫任务
|
||||
|
||||
while signal.value > 0 or not queue.empty(): # 当还有未完成的爬虫任务或存放数据的队列不为空时
|
||||
yield await queue.get() # 取出并返回一个存放的 Model
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"<{self.__class__.__name__} {super(WikiModel, self).__str__()}>"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.__str__()
|
||||
async def dump(datas, path: Path):
|
||||
async with aiofiles.open(path, "w", encoding="utf-8") as f:
|
||||
await f.write(jsonlib.dumps(datas, indent=4, ensure_ascii=False))
|
||||
|
||||
@staticmethod
|
||||
async def get_url_by_id(id_: str) -> URL:
|
||||
"""根据 id 获取对应的 url
|
||||
async def read(path: Path) -> List[Dict]:
|
||||
async with aiofiles.open(path, "r", encoding="utf-8") as f:
|
||||
datas = jsonlib.loads(await f.read())
|
||||
return datas
|
||||
|
||||
例如神里绫华的ID为 ayaka_002,对应的数据页url为 https://genshin.honeyhunterworld.com/ayaka_002/?lang=CHS
|
||||
@staticmethod
|
||||
async def save_file(data, path: Path):
|
||||
async with aiofiles.open(path, "wb") as f:
|
||||
await f.write(data)
|
||||
|
||||
Args:
|
||||
id_ : 实列ID
|
||||
Returns:
|
||||
返回对应的 url
|
||||
"""
|
||||
return HONEY_HOST.join(f"{id_}/?lang=CHS")
|
||||
|
||||
@classmethod
|
||||
async def _name_list_generator(cls, *, with_url: bool = False) -> AsyncIterator[Union[str, Tuple[str, URL]]]:
|
||||
"""一个 Model 的名称 和 其对应 url 的异步生成器
|
||||
|
||||
Args:
|
||||
with_url: 是否返回相应的 url
|
||||
Returns:
|
||||
返回对应的名称列表 或者 名称与url 的列表
|
||||
"""
|
||||
urls = cls.scrape_urls()
|
||||
queue: Queue[Union[str, Tuple[str, URL]]] = Queue() # 存放 Model 的队列
|
||||
signal = Value("i", len(urls)) # 一个用于异步任务同步的信号,初始值为存放所需要爬取的页面数
|
||||
|
||||
async def task(page: URL):
|
||||
"""包装的爬虫任务"""
|
||||
response = await cls._client_get(page)
|
||||
# 从页面中获取对应的 chaos data (未处理的json格式字符串)
|
||||
chaos_data = re.findall(r"sortable_data\.push\((.*?)\);\s*sortable_cur_page", response.text)[0]
|
||||
json_data = jsonlib.loads(chaos_data) # 转为 json
|
||||
for data in json_data: # 遍历 json
|
||||
data_name = re.findall(r">(.*)<", data[1])[0].strip() # 获取 Model 的名称
|
||||
if with_url: # 如果需要返回对应的 url
|
||||
data_url = HONEY_HOST.join(re.findall(r"\"(.*?)\"", data[0])[0])
|
||||
await queue.put((data_name, data_url))
|
||||
else:
|
||||
await queue.put(data_name)
|
||||
signal.value = signal.value - 1 # 信号量减少 1 ,说明该爬虫任务已经完成
|
||||
|
||||
for url in urls: # 遍历需要爬出的页面
|
||||
asyncio.create_task(task(url)) # 添加爬虫任务
|
||||
while signal.value > 0 or not queue.empty(): # 当还有未完成的爬虫任务或存放数据的队列不为空时
|
||||
yield await queue.get() # 取出并返回一个存放的 Model
|
||||
|
||||
@classmethod
|
||||
async def get_name_list(cls, *, with_url: bool = False) -> List[Union[str, Tuple[str, URL]]]:
|
||||
"""获取全部 Model 的 名称
|
||||
|
||||
Returns:
|
||||
返回能爬到的所有的 Model 的名称所组成的 List
|
||||
"""
|
||||
return [i async for i in cls._name_list_generator(with_url=with_url)]
|
||||
|
||||
@classmethod
|
||||
async def get_url_by_name(cls, name: str) -> Optional[URL]:
|
||||
"""通过 Model 的名称获取对应的 url
|
||||
|
||||
Args:
|
||||
name: 实列名
|
||||
Returns:
|
||||
若有对应的实列,则返回对应的 url; 若没有, 则返回 None
|
||||
"""
|
||||
async for n, url in cls._name_list_generator(with_url=True):
|
||||
if name == n:
|
||||
return url
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def icon(self):
|
||||
"""返回此 Model 的图标链接"""
|
||||
@staticmethod
|
||||
async def read_file(path: Path):
|
||||
async with aiofiles.open(path, "rb") as f:
|
||||
return await f.read()
|
||||
|
46
modules/wiki/buddy.py
Normal file
@ -0,0 +1,46 @@
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from modules.wiki.base import WikiModel
|
||||
from modules.wiki.models.buddy import Buddy as BuddyModel
|
||||
|
||||
|
||||
class Buddy(WikiModel):
|
||||
buddy_url = WikiModel.BASE_URL + "buddy.json"
|
||||
buddy_path = WikiModel.BASE_PATH / "buddy.json"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.all_buddys: List[BuddyModel] = []
|
||||
self.all_buddys_map: Dict[int, BuddyModel] = {}
|
||||
self.all_buddys_name: Dict[str, BuddyModel] = {}
|
||||
|
||||
def clear_class_data(self) -> None:
|
||||
self.all_buddys.clear()
|
||||
self.all_buddys_map.clear()
|
||||
self.all_buddys_name.clear()
|
||||
|
||||
async def refresh(self):
|
||||
datas = await self.remote_get(self.buddy_url)
|
||||
await self.dump(datas.json(), self.buddy_path)
|
||||
await self.read()
|
||||
|
||||
async def read(self):
|
||||
if not self.buddy_path.exists():
|
||||
await self.refresh()
|
||||
return
|
||||
datas = await WikiModel.read(self.buddy_path)
|
||||
self.clear_class_data()
|
||||
for data in datas:
|
||||
m = BuddyModel(**data)
|
||||
self.all_buddys.append(m)
|
||||
self.all_buddys_map[m.id] = m
|
||||
self.all_buddys_name[m.name] = m
|
||||
|
||||
def get_by_id(self, cid: int) -> Optional[BuddyModel]:
|
||||
return self.all_buddys_map.get(cid)
|
||||
|
||||
def get_by_name(self, name: str) -> Optional[BuddyModel]:
|
||||
return self.all_buddys_name.get(name)
|
||||
|
||||
def get_name_list(self) -> List[str]:
|
||||
return list(self.all_buddys_name.keys())
|
@ -1,199 +1,46 @@
|
||||
import re
|
||||
from typing import List, Optional
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
|
||||
from modules.wiki.base import HONEY_HOST, Model, WikiModel
|
||||
from modules.wiki.other import Association, Element, WeaponType
|
||||
|
||||
|
||||
class Birth(Model):
|
||||
"""生日
|
||||
Attributes:
|
||||
day: 天
|
||||
month: 月
|
||||
"""
|
||||
|
||||
day: int
|
||||
month: int
|
||||
|
||||
|
||||
class CharacterAscension(Model):
|
||||
"""角色的突破材料
|
||||
|
||||
Attributes:
|
||||
level: 等级突破材料
|
||||
skill: 技能/天赋培养材料
|
||||
"""
|
||||
|
||||
level: List[str] = []
|
||||
skill: List[str] = []
|
||||
|
||||
|
||||
class CharacterState(Model):
|
||||
"""角色属性值
|
||||
|
||||
Attributes:
|
||||
level: 等级
|
||||
HP: 生命
|
||||
ATK: 攻击力
|
||||
DEF: 防御力
|
||||
CR: 暴击率
|
||||
CD: 暴击伤害
|
||||
bonus: 突破属性
|
||||
"""
|
||||
|
||||
level: str
|
||||
HP: int
|
||||
ATK: float
|
||||
DEF: float
|
||||
CR: str
|
||||
CD: str
|
||||
bonus: str
|
||||
|
||||
|
||||
class CharacterIcon(Model):
|
||||
icon: str
|
||||
side: str
|
||||
gacha: str
|
||||
splash: Optional[str]
|
||||
from modules.wiki.base import WikiModel
|
||||
from modules.wiki.models.avatar import Avatar
|
||||
|
||||
|
||||
class Character(WikiModel):
|
||||
"""角色
|
||||
Attributes:
|
||||
title: 称号
|
||||
occupation: 所属
|
||||
association: 地区
|
||||
weapon_type: 武器类型
|
||||
element: 元素
|
||||
birth: 生日
|
||||
constellation: 命之座
|
||||
cn_cv: 中配
|
||||
jp_cv: 日配
|
||||
en_cv: 英配
|
||||
kr_cv: 韩配
|
||||
description: 描述
|
||||
"""
|
||||
avatar_url = WikiModel.BASE_URL + "avatars.json"
|
||||
avatar_path = WikiModel.BASE_PATH / "avatars.json"
|
||||
|
||||
id: str
|
||||
title: str
|
||||
occupation: str
|
||||
association: Association
|
||||
weapon_type: WeaponType
|
||||
element: Element
|
||||
birth: Optional[Birth]
|
||||
constellation: str
|
||||
cn_cv: str
|
||||
jp_cv: str
|
||||
en_cv: str
|
||||
kr_cv: str
|
||||
description: str
|
||||
ascension: CharacterAscension
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.all_avatars: List[Avatar] = []
|
||||
self.all_avatars_map: Dict[int, Avatar] = {}
|
||||
self.all_avatars_name: Dict[str, Avatar] = {}
|
||||
|
||||
stats: List[CharacterState]
|
||||
def clear_class_data(self) -> None:
|
||||
self.all_avatars.clear()
|
||||
self.all_avatars_map.clear()
|
||||
self.all_avatars_name.clear()
|
||||
|
||||
@classmethod
|
||||
def scrape_urls(cls) -> List[URL]:
|
||||
return [HONEY_HOST.join("fam_chars/?lang=CHS")]
|
||||
async def refresh(self):
|
||||
datas = await self.remote_get(self.avatar_url)
|
||||
await self.dump(datas.json(), self.avatar_path)
|
||||
await self.read()
|
||||
|
||||
@classmethod
|
||||
async def _parse_soup(cls, soup: BeautifulSoup) -> "Character":
|
||||
"""解析角色页"""
|
||||
soup = soup.select(".wp-block-post-content")[0]
|
||||
tables = soup.find_all("table")
|
||||
table_rows = tables[0].find_all("tr")
|
||||
async def read(self):
|
||||
if not self.avatar_path.exists():
|
||||
await self.refresh()
|
||||
return
|
||||
datas = await WikiModel.read(self.avatar_path)
|
||||
self.clear_class_data()
|
||||
for data in datas:
|
||||
m = Avatar(**data)
|
||||
self.all_avatars.append(m)
|
||||
self.all_avatars_map[m.id] = m
|
||||
self.all_avatars_name[m.name] = m
|
||||
|
||||
def get_table_text(row_num: int) -> str:
|
||||
"""一个快捷函数,用于返回表格对应行的最后一个单元格中的文本"""
|
||||
return table_rows[row_num].find_all("td")[-1].text.replace("\xa0", "")
|
||||
def get_by_id(self, cid: int) -> Optional[Avatar]:
|
||||
return self.all_avatars_map.get(cid)
|
||||
|
||||
id_ = re.findall(r"img/(.*?_\d+)_.*", table_rows[0].find("img").attrs["src"])[0]
|
||||
name = get_table_text(0)
|
||||
if name != "旅行者": # 如果角色名不是 旅行者
|
||||
title = get_table_text(1)
|
||||
occupation = get_table_text(2)
|
||||
association = Association.convert(get_table_text(3).lower().title())
|
||||
rarity = len(table_rows[4].find_all("img"))
|
||||
weapon_type = WeaponType[get_table_text(5)]
|
||||
element = Element[get_table_text(6)]
|
||||
birth = Birth(day=int(get_table_text(7)), month=int(get_table_text(8)))
|
||||
constellation = get_table_text(10)
|
||||
cn_cv = get_table_text(11)
|
||||
jp_cv = get_table_text(12)
|
||||
en_cv = get_table_text(13)
|
||||
kr_cv = get_table_text(14)
|
||||
else:
|
||||
name = "空" if id_.endswith("5") else "荧"
|
||||
title = get_table_text(0)
|
||||
occupation = get_table_text(1)
|
||||
association = Association.convert(get_table_text(2).lower().title())
|
||||
rarity = len(table_rows[3].find_all("img"))
|
||||
weapon_type = WeaponType[get_table_text(4)]
|
||||
element = Element[get_table_text(5)]
|
||||
birth = None
|
||||
constellation = get_table_text(7)
|
||||
cn_cv = get_table_text(8)
|
||||
jp_cv = get_table_text(9)
|
||||
en_cv = get_table_text(10)
|
||||
kr_cv = get_table_text(11)
|
||||
description = get_table_text(-3)
|
||||
ascension = CharacterAscension(
|
||||
level=[
|
||||
target[0]
|
||||
for i in table_rows[-2].find_all("a")
|
||||
if (target := re.findall(r"/(.*)/", i.attrs["href"])) # 过滤掉错误的材料(honey网页的bug)
|
||||
],
|
||||
skill=[re.findall(r"/(.*)/", i.attrs["href"])[0] for i in table_rows[-1].find_all("a")],
|
||||
)
|
||||
stats = []
|
||||
for row in tables[2].find_all("tr")[1:]:
|
||||
cells = row.find_all("td")
|
||||
stats.append(
|
||||
CharacterState(
|
||||
level=cells[0].text,
|
||||
HP=cells[1].text,
|
||||
ATK=cells[2].text,
|
||||
DEF=cells[3].text,
|
||||
CR=cells[4].text,
|
||||
CD=cells[5].text,
|
||||
bonus=cells[6].text,
|
||||
)
|
||||
)
|
||||
return Character(
|
||||
id=id_,
|
||||
name=name,
|
||||
title=title,
|
||||
occupation=occupation,
|
||||
association=association,
|
||||
weapon_type=weapon_type,
|
||||
element=element,
|
||||
birth=birth,
|
||||
constellation=constellation,
|
||||
cn_cv=cn_cv,
|
||||
jp_cv=jp_cv,
|
||||
rarity=rarity,
|
||||
en_cv=en_cv,
|
||||
kr_cv=kr_cv,
|
||||
description=description,
|
||||
ascension=ascension,
|
||||
stats=stats,
|
||||
)
|
||||
def get_by_name(self, name: str) -> Optional[Avatar]:
|
||||
return self.all_avatars_name.get(name)
|
||||
|
||||
@classmethod
|
||||
async def get_url_by_name(cls, name: str) -> Optional[URL]:
|
||||
# 重写此函数的目的是处理主角名字的 ID
|
||||
_map = {"荧": "playergirl_007", "空": "playerboy_005"}
|
||||
if (id_ := _map.get(name)) is not None:
|
||||
return await cls.get_url_by_id(id_)
|
||||
return await super(Character, cls).get_url_by_name(name)
|
||||
|
||||
@property
|
||||
def icon(self) -> CharacterIcon:
|
||||
return CharacterIcon(
|
||||
icon=str(HONEY_HOST.join(f"/img/{self.id}_icon.webp")),
|
||||
side=str(HONEY_HOST.join(f"/img/{self.id}_side_icon.webp")),
|
||||
gacha=str(HONEY_HOST.join(f"/img/{self.id}_gacha_card.webp")),
|
||||
splash=str(HONEY_HOST.join(f"/img/{self.id}_gacha_splash.webp")),
|
||||
)
|
||||
def get_name_list(self) -> List[str]:
|
||||
return list(self.all_avatars_name.keys())
|
||||
|
46
modules/wiki/equipment_suit.py
Normal file
@ -0,0 +1,46 @@
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from modules.wiki.base import WikiModel
|
||||
from modules.wiki.models.equipment_suit import EquipmentSuit as EquipmentSuitModel
|
||||
|
||||
|
||||
class EquipmentSuit(WikiModel):
|
||||
equipment_suit_url = WikiModel.BASE_URL + "equipment_suits.json"
|
||||
equipment_suit_path = WikiModel.BASE_PATH / "equipment_suits.json"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.all_equipment_suits: List[EquipmentSuitModel] = []
|
||||
self.all_equipment_suits_map: Dict[int, EquipmentSuitModel] = {}
|
||||
self.all_equipment_suits_name: Dict[str, EquipmentSuitModel] = {}
|
||||
|
||||
def clear_class_data(self) -> None:
|
||||
self.all_equipment_suits.clear()
|
||||
self.all_equipment_suits_map.clear()
|
||||
self.all_equipment_suits_name.clear()
|
||||
|
||||
async def refresh(self):
|
||||
datas = await self.remote_get(self.equipment_suit_url)
|
||||
await self.dump(datas.json(), self.equipment_suit_path)
|
||||
await self.read()
|
||||
|
||||
async def read(self):
|
||||
if not self.equipment_suit_path.exists():
|
||||
await self.refresh()
|
||||
return
|
||||
datas = await WikiModel.read(self.equipment_suit_path)
|
||||
self.clear_class_data()
|
||||
for data in datas:
|
||||
m = EquipmentSuitModel(**data)
|
||||
self.all_equipment_suits.append(m)
|
||||
self.all_equipment_suits_map[m.id] = m
|
||||
self.all_equipment_suits_name[m.name] = m
|
||||
|
||||
def get_by_id(self, cid: int) -> Optional[EquipmentSuitModel]:
|
||||
return self.all_equipment_suits_map.get(cid)
|
||||
|
||||
def get_by_name(self, name: str) -> Optional[EquipmentSuitModel]:
|
||||
return self.all_equipment_suits_name.get(name)
|
||||
|
||||
def get_name_list(self) -> List[str]:
|
||||
return list(self.all_equipment_suits_name.keys())
|
@ -1,81 +0,0 @@
|
||||
import re
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
|
||||
from modules.wiki.base import HONEY_HOST, WikiModel
|
||||
|
||||
__all__ = ["Material"]
|
||||
|
||||
WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
|
||||
|
||||
|
||||
class Material(WikiModel):
|
||||
# noinspection PyUnresolvedReferences
|
||||
"""武器、角色培养素材
|
||||
|
||||
Attributes:
|
||||
type: 类型
|
||||
weekdays: 每周开放的时间
|
||||
source: 获取方式
|
||||
description: 描述
|
||||
"""
|
||||
type: str
|
||||
source: Optional[List[str]] = None
|
||||
weekdays: Optional[List[int]] = None
|
||||
description: str
|
||||
|
||||
@staticmethod
|
||||
def scrape_urls() -> List[URL]:
|
||||
weapon = [HONEY_HOST.join(f"fam_wep_{i}/?lang=CHS") for i in ["primary", "secondary", "common"]]
|
||||
talent = [HONEY_HOST.join(f"fam_talent_{i}/?lang=CHS") for i in ["book", "boss", "common", "reward"]]
|
||||
return weapon + talent
|
||||
|
||||
@classmethod
|
||||
async def get_name_list(cls, *, with_url: bool = False) -> List[Union[str, Tuple[str, URL]]]:
|
||||
return list(sorted(set(await super(Material, cls).get_name_list(with_url=with_url)), key=lambda x: x[0]))
|
||||
|
||||
@classmethod
|
||||
async def _parse_soup(cls, soup: BeautifulSoup) -> "Material":
|
||||
"""解析突破素材页"""
|
||||
soup = soup.select(".wp-block-post-content")[0]
|
||||
tables = soup.find_all("table")
|
||||
table_rows = tables[0].find_all("tr")
|
||||
|
||||
def get_table_row(target: str):
|
||||
"""一个便捷函数,用于返回对应表格头的对应行的最后一个单元格中的文本"""
|
||||
for row in table_rows:
|
||||
if target in row.find("td").text:
|
||||
return row.find_all("td")[-1]
|
||||
return None
|
||||
|
||||
def get_table_text(row_num: int) -> str:
|
||||
"""一个便捷函数,用于返回表格对应行的最后一个单元格中的文本"""
|
||||
return table_rows[row_num].find_all("td")[-1].text.replace("\xa0", "")
|
||||
|
||||
id_ = re.findall(r"/img/(.*?)\.webp", str(table_rows[0]))[0]
|
||||
name = get_table_text(0)
|
||||
rarity = len(table_rows[3].find_all("img"))
|
||||
type_ = get_table_text(1)
|
||||
if (item_source := get_table_row("Item Source")) is not None:
|
||||
item_source = list(
|
||||
# filter 在这里的作用是过滤掉为空的数据
|
||||
filter(lambda x: x, item_source.encode_contents().decode().split("<br/>"))
|
||||
)
|
||||
if (alter_source := get_table_row("Alternative Item")) is not None:
|
||||
alter_source = list(
|
||||
# filter 在这里的作用是过滤掉为空的数据
|
||||
filter(lambda x: x, alter_source.encode_contents().decode().split("<br/>"))
|
||||
)
|
||||
source = list(sorted(set((item_source or []) + (alter_source or []))))
|
||||
if (weekdays := get_table_row("Weekday")) is not None:
|
||||
weekdays = [*(WEEKDAYS.index(weekdays.text.replace("\xa0", "").split(",")[0]) + 3 * i for i in range(2)), 6]
|
||||
description = get_table_text(-1)
|
||||
return Material(
|
||||
id=id_, name=name, rarity=rarity, type=type_, description=description, source=source, weekdays=weekdays
|
||||
)
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
return str(HONEY_HOST.join(f"/img/{self.id}.webp"))
|
0
modules/wiki/models/__init__.py
Normal file
38
modules/wiki/models/avatar.py
Normal file
@ -0,0 +1,38 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .enums import ZZZElementType, ZZZSpeciality, ZZZRank
|
||||
|
||||
|
||||
class Avatar(BaseModel, frozen=False):
|
||||
id: int
|
||||
""" 角色ID """
|
||||
name: str
|
||||
""" 中文名称 """
|
||||
name_en: str
|
||||
""" 英文名称 """
|
||||
name_full: str
|
||||
""" 中文全称 """
|
||||
name_short: str
|
||||
""" 英文简称 """
|
||||
rank: ZZZRank = ZZZRank.NULL
|
||||
""" 星级 """
|
||||
element: ZZZElementType
|
||||
""" 元素 """
|
||||
speciality: ZZZSpeciality
|
||||
""" 特性 """
|
||||
icon: List[str] = ["", "", ""]
|
||||
""" 图标 """
|
||||
|
||||
@property
|
||||
def icon_(self) -> str:
|
||||
return self.icon[0]
|
||||
|
||||
@property
|
||||
def normal(self) -> str:
|
||||
return self.icon[1]
|
||||
|
||||
@property
|
||||
def gacha(self) -> str:
|
||||
return self.icon[2]
|
24
modules/wiki/models/buddy.py
Normal file
@ -0,0 +1,24 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .enums import ZZZRank
|
||||
|
||||
|
||||
class Buddy(BaseModel):
|
||||
id: int
|
||||
""""邦布ID"""
|
||||
name: str
|
||||
"""名称"""
|
||||
name_en: str
|
||||
"""英文名称"""
|
||||
icon: str = ""
|
||||
"""图标"""
|
||||
rank: ZZZRank = ZZZRank.NULL
|
||||
""" 星级 """
|
||||
|
||||
@property
|
||||
def webp(self) -> str:
|
||||
return self.icon if self.icon.endswith("webp") else ""
|
||||
|
||||
@property
|
||||
def png(self) -> str:
|
||||
return self.icon if self.icon.endswith("png") else ""
|
49
modules/wiki/models/enums.py
Normal file
@ -0,0 +1,49 @@
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
|
||||
class ZZZElementType(IntEnum):
|
||||
"""ZZZ element type."""
|
||||
|
||||
NULL = 1
|
||||
""" 空 """
|
||||
PHYSICAL = 200
|
||||
""" 物理 """
|
||||
FIRE = 201
|
||||
""" 火 """
|
||||
ICE = 202
|
||||
""" 冰 """
|
||||
ELECTRIC = 203
|
||||
""" 电 """
|
||||
ETHER = 205
|
||||
""" 以太 """
|
||||
|
||||
|
||||
class ZZZSpeciality(IntEnum):
|
||||
"""ZZZ agent compatible speciality."""
|
||||
|
||||
ATTACK = 1
|
||||
""" 强攻 """
|
||||
STUN = 2
|
||||
""" 击破 """
|
||||
ANOMALY = 3
|
||||
""" 异常 """
|
||||
SUPPORT = 4
|
||||
""" 支援 """
|
||||
DEFENSE = 5
|
||||
""" 防护 """
|
||||
|
||||
|
||||
class ZZZRank(str, Enum):
|
||||
"""ZZZ Rank"""
|
||||
|
||||
S = "S"
|
||||
A = "A"
|
||||
B = "B"
|
||||
C = "C"
|
||||
D = "D"
|
||||
NULL = "NULL"
|
||||
|
||||
@property
|
||||
def int(self):
|
||||
value_map = {"S": 5, "A": 4, "B": 3, "C": 2, "D": 1, "NULL": 0}
|
||||
return value_map[self.value]
|
22
modules/wiki/models/equipment_suit.py
Normal file
@ -0,0 +1,22 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .enums import ZZZRank
|
||||
|
||||
|
||||
class EquipmentSuit(BaseModel):
|
||||
id: int
|
||||
"""驱动盘套装ID"""
|
||||
name: str
|
||||
"""套装名称"""
|
||||
name_en: str
|
||||
"""英文套装名称"""
|
||||
icon: str = ""
|
||||
"""套装图标"""
|
||||
desc_2: str
|
||||
"""2套描述"""
|
||||
desc_4: str
|
||||
"""4套描述"""
|
||||
story: str
|
||||
"""套装故事"""
|
||||
rank: ZZZRank = ZZZRank.NULL
|
||||
""" 星级 """
|
18
modules/wiki/models/weapon.py
Normal file
@ -0,0 +1,18 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .enums import ZZZRank
|
||||
|
||||
|
||||
class Weapon(BaseModel):
|
||||
id: int
|
||||
""""武器ID"""
|
||||
name: str
|
||||
"""名称"""
|
||||
name_en: str
|
||||
"""英文名称"""
|
||||
description: str
|
||||
"""描述"""
|
||||
icon: str = ""
|
||||
"""图标"""
|
||||
rank: ZZZRank
|
||||
"""稀有度"""
|
@ -124,7 +124,6 @@ class Association(Enum):
|
||||
Inazuma = "稻妻"
|
||||
Liyue = "璃月"
|
||||
Mondstadt = "蒙德"
|
||||
Fontaine = "枫丹"
|
||||
|
||||
@classmethod
|
||||
def convert(cls, string: str) -> Optional[Self]:
|
||||
|
69
modules/wiki/raider.py
Normal file
@ -0,0 +1,69 @@
|
||||
import asyncio
|
||||
from typing import List, Dict
|
||||
|
||||
from metadata.shortname import roleToName, weaponToName
|
||||
from modules.wiki.base import WikiModel
|
||||
|
||||
|
||||
class Raider(WikiModel):
|
||||
raider_url = "https://raw.githubusercontent.com/PaiGramTeam/zzz-atlas/master"
|
||||
raider_path = WikiModel.BASE_PATH / "raiders"
|
||||
raider_role_path = WikiModel.BASE_PATH / "raiders" / "role"
|
||||
raider_guide_for_role_path = WikiModel.BASE_PATH / "raiders" / "guide_for_role"
|
||||
raider_light_cone_path = WikiModel.BASE_PATH / "raiders" / "weapon"
|
||||
raider_relic_path = WikiModel.BASE_PATH / "raiders" / "relic"
|
||||
raider_info_path = WikiModel.BASE_PATH / "raiders" / "path.json"
|
||||
raider_role_path.mkdir(parents=True, exist_ok=True)
|
||||
raider_guide_for_role_path.mkdir(parents=True, exist_ok=True)
|
||||
raider_light_cone_path.mkdir(parents=True, exist_ok=True)
|
||||
raider_relic_path.mkdir(parents=True, exist_ok=True)
|
||||
name_map = {
|
||||
"角色": "role",
|
||||
"音擎": "weapon",
|
||||
"角色攻略": "guide_for_role",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.all_role_raiders: List[str] = []
|
||||
self.all_guide_for_role_raiders: List[str] = []
|
||||
self.all_light_cone_raiders: List[str] = []
|
||||
self.all_relic_raiders: List[str] = []
|
||||
|
||||
def clear_class_data(self) -> None:
|
||||
self.all_role_raiders.clear()
|
||||
self.all_guide_for_role_raiders.clear()
|
||||
self.all_light_cone_raiders.clear()
|
||||
self.all_relic_raiders.clear()
|
||||
|
||||
async def refresh_task(self, name: str, path: str = "", start: str = ""):
|
||||
photo = await self.remote_get(f"{self.raider_url}{path}")
|
||||
await self.save_file(photo.content, self.raider_path / start / f"{name}.png")
|
||||
|
||||
async def refresh(self):
|
||||
datas = await self.remote_get(self.raider_url + "/path.json")
|
||||
data = datas.json()
|
||||
new_data = {}
|
||||
for key, start in self.name_map.items():
|
||||
new_data[start] = list(data[key].keys())
|
||||
tasks = []
|
||||
for name, path in data[key].items():
|
||||
if key in {"角色", "角色攻略"}:
|
||||
name = roleToName(name)
|
||||
else:
|
||||
name = weaponToName(name)
|
||||
tasks.append(self.refresh_task(name, path, start))
|
||||
await asyncio.gather(*tasks)
|
||||
await self.dump(new_data, self.raider_info_path)
|
||||
await self.read()
|
||||
|
||||
async def read(self):
|
||||
if not self.raider_info_path.exists():
|
||||
await self.refresh()
|
||||
return
|
||||
datas: Dict[str, List] = await WikiModel.read(self.raider_info_path) # noqa
|
||||
self.clear_class_data()
|
||||
self.all_role_raiders.extend(datas["role"])
|
||||
self.all_guide_for_role_raiders.extend(datas["guide_for_role"])
|
||||
self.all_light_cone_raiders.extend(datas["weapon"])
|
||||
# self.all_relic_raiders.extend(datas["relic"])
|
@ -1,146 +1,46 @@
|
||||
import itertools
|
||||
import re
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from httpx import URL
|
||||
|
||||
from modules.wiki.base import HONEY_HOST, Model, WikiModel
|
||||
from modules.wiki.other import AttributeType, WeaponType
|
||||
|
||||
__all__ = ["Weapon", "WeaponAffix", "WeaponAttribute"]
|
||||
|
||||
|
||||
class WeaponAttribute(Model):
|
||||
"""武器词条"""
|
||||
|
||||
type: AttributeType
|
||||
value: str
|
||||
|
||||
|
||||
class WeaponAffix(Model):
|
||||
"""武器技能
|
||||
|
||||
Attributes:
|
||||
name: 技能名
|
||||
description: 技能描述
|
||||
|
||||
"""
|
||||
|
||||
name: str
|
||||
description: List[str]
|
||||
|
||||
|
||||
class WeaponState(Model):
|
||||
level: str
|
||||
ATK: float
|
||||
bonus: Optional[str]
|
||||
|
||||
|
||||
class WeaponIcon(Model):
|
||||
icon: str
|
||||
awakened: str
|
||||
gacha: str
|
||||
from modules.wiki.base import WikiModel
|
||||
from modules.wiki.models.weapon import Weapon as WeaponModel
|
||||
|
||||
|
||||
class Weapon(WikiModel):
|
||||
"""武器
|
||||
weapon_url = WikiModel.BASE_URL + "weapons.json"
|
||||
weapon_path = WikiModel.BASE_PATH / "weapons.json"
|
||||
|
||||
Attributes:
|
||||
weapon_type: 武器类型
|
||||
attack: 基础攻击力
|
||||
attribute:
|
||||
affix: 武器技能
|
||||
description: 描述
|
||||
ascension: 突破材料
|
||||
story: 武器故事
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.all_weapons: List[WeaponModel] = []
|
||||
self.all_weapons_map: Dict[int, WeaponModel] = {}
|
||||
self.all_weapons_name: Dict[str, WeaponModel] = {}
|
||||
|
||||
weapon_type: WeaponType
|
||||
attack: float
|
||||
attribute: Optional[WeaponAttribute]
|
||||
affix: Optional[WeaponAffix]
|
||||
description: str
|
||||
ascension: List[str]
|
||||
story: Optional[str]
|
||||
def clear_class_data(self) -> None:
|
||||
self.all_weapons.clear()
|
||||
self.all_weapons_map.clear()
|
||||
self.all_weapons_name.clear()
|
||||
|
||||
stats: List[WeaponState]
|
||||
async def refresh(self):
|
||||
datas = await self.remote_get(self.weapon_url)
|
||||
await self.dump(datas.json(), self.weapon_path)
|
||||
await self.read()
|
||||
|
||||
@staticmethod
|
||||
def scrape_urls() -> List[URL]:
|
||||
return [HONEY_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__]
|
||||
async def read(self):
|
||||
if not self.weapon_path.exists():
|
||||
await self.refresh()
|
||||
return
|
||||
datas = await WikiModel.read(self.weapon_path)
|
||||
self.clear_class_data()
|
||||
for data in datas:
|
||||
m = WeaponModel(**data)
|
||||
self.all_weapons.append(m)
|
||||
self.all_weapons_map[m.id] = m
|
||||
self.all_weapons_name[m.name] = m
|
||||
|
||||
@classmethod
|
||||
async def _parse_soup(cls, soup: BeautifulSoup) -> "Weapon":
|
||||
"""解析武器页"""
|
||||
soup = soup.select(".wp-block-post-content")[0]
|
||||
tables = soup.find_all("table")
|
||||
table_rows = tables[0].find_all("tr")
|
||||
def get_by_id(self, cid: int) -> Optional[WeaponModel]:
|
||||
return self.all_weapons_map.get(cid)
|
||||
|
||||
def get_table_text(row_num: int) -> str:
|
||||
"""一个快捷函数,用于返回表格对应行的最后一个单元格中的文本"""
|
||||
return table_rows[row_num].find_all("td")[-1].text.replace("\xa0", "")
|
||||
def get_by_name(self, name: str) -> Optional[WeaponModel]:
|
||||
return self.all_weapons_name.get(name)
|
||||
|
||||
def find_table(select: str):
|
||||
"""一个快捷函数,用于寻找对应表格头的表格"""
|
||||
return list(filter(lambda x: select in " ".join(x.attrs["class"]), tables))
|
||||
|
||||
id_ = re.findall(r"/img/(.*?)_gacha", str(table_rows[0]))[0]
|
||||
weapon_type = WeaponType[get_table_text(1).split(",")[-1].strip()]
|
||||
name = get_table_text(0)
|
||||
rarity = len(table_rows[2].find_all("img"))
|
||||
attack = float(get_table_text(4))
|
||||
ascension = [re.findall(r"/(.*)/", tag.attrs["href"])[0] for tag in table_rows[-1].find_all("a")]
|
||||
if rarity > 2: # 如果是 3 星及其以上的武器
|
||||
attribute = WeaponAttribute(
|
||||
type=AttributeType.convert(tables[2].find("thead").find("tr").find_all("td")[2].text.split(" ")[1]),
|
||||
value=get_table_text(6),
|
||||
)
|
||||
affix = WeaponAffix(
|
||||
name=get_table_text(7), description=[i.find_all("td")[1].text for i in tables[3].find_all("tr")[1:]]
|
||||
)
|
||||
description = get_table_text(9)
|
||||
if story_table := find_table("quotes"):
|
||||
story = story_table[0].text.strip()
|
||||
else:
|
||||
story = None
|
||||
else: # 如果是 2 星及其以下的武器
|
||||
attribute = affix = None
|
||||
description = get_table_text(5)
|
||||
story = tables[-1].text.strip()
|
||||
stats = []
|
||||
for row in tables[2].find_all("tr")[1:]:
|
||||
cells = row.find_all("td")
|
||||
if rarity > 2:
|
||||
stats.append(WeaponState(level=cells[0].text, ATK=cells[1].text, bonus=cells[2].text))
|
||||
else:
|
||||
stats.append(WeaponState(level=cells[0].text, ATK=cells[1].text))
|
||||
return Weapon(
|
||||
id=id_,
|
||||
name=name,
|
||||
rarity=rarity,
|
||||
attack=attack,
|
||||
attribute=attribute,
|
||||
affix=affix,
|
||||
weapon_type=weapon_type,
|
||||
story=story,
|
||||
stats=stats,
|
||||
description=description,
|
||||
ascension=ascension,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_name_list(cls, *, with_url: bool = False) -> List[Union[str, Tuple[str, URL]]]:
|
||||
# 重写此函数的目的是名字去重,例如单手剑页面中有三个 “「一心传」名刀”
|
||||
name_list = [i async for i in cls._name_list_generator(with_url=with_url)]
|
||||
if with_url:
|
||||
return [(i[0], list(i[1])[0][1]) for i in itertools.groupby(name_list, lambda x: x[0])]
|
||||
return [i[0] for i in itertools.groupby(name_list, lambda x: x)]
|
||||
|
||||
@property
|
||||
def icon(self) -> WeaponIcon:
|
||||
return WeaponIcon(
|
||||
icon=str(HONEY_HOST.join(f"/img/{self.id}.webp")),
|
||||
awakened=str(HONEY_HOST.join(f"/img/{self.id}_awaken_icon.webp")),
|
||||
gacha=str(HONEY_HOST.join(f"/img/{self.id}_gacha_icon.webp")),
|
||||
)
|
||||
def get_name_list(self) -> List[str]:
|
||||
return list(self.all_weapons_name.keys())
|
||||
|
4
pdm.lock
@ -2212,11 +2212,11 @@ name = "simnet"
|
||||
version = "0.1.22"
|
||||
requires_python = "<4.0,>=3.8"
|
||||
git = "https://github.com/PaiGramTeam/SIMNet"
|
||||
revision = "05fcb568d6c1fe44a4f917c996198bfe62a00053"
|
||||
revision = "074939d8818e6073be4a918b25d7deadd43a5b7b"
|
||||
summary = "Modern API wrapper for Genshin Impact & Honkai: Star Rail built on asyncio and pydantic."
|
||||
groups = ["default"]
|
||||
dependencies = [
|
||||
"httpx<1.0.0,>=0.25.0",
|
||||
"httpx>=0.25.0",
|
||||
"pydantic<2.0.0,>=1.10.7",
|
||||
]
|
||||
|
||||
|
@ -212,7 +212,7 @@ class BindAccountPlugin(Plugin.Conversation):
|
||||
return ConversationHandler.END
|
||||
try:
|
||||
async with self.helper.public_genshin(user.id, region=region, uid=player_id) as client:
|
||||
player_stats = await client.get_genshin_user(player_id)
|
||||
player_stats = await client.get_zzz_user(player_id)
|
||||
except TooManyRequestPublicCookies:
|
||||
await message.reply_text("用户查询次数过多,请稍后重试", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
@ -21,6 +21,11 @@ class SetCommandPlugin(Plugin):
|
||||
user_command = [
|
||||
BotCommand("cancel", "取消操作(解决一切玄学问题)"),
|
||||
BotCommand("help_raw", "查看文本帮助"),
|
||||
# gacha_log 相关
|
||||
BotCommand("signal_log", "查看调频记录"),
|
||||
BotCommand("signal_log_import", "导入调频记录"),
|
||||
BotCommand("signal_log_export", "导出调频记录"),
|
||||
BotCommand("signal_log_delete", "删除调频记录"),
|
||||
# Cookie 查询类
|
||||
BotCommand("sign", "米游社绝区零每日签到"),
|
||||
BotCommand("dailynote_tasks", "自动便笺提醒"),
|
||||
@ -33,6 +38,9 @@ class SetCommandPlugin(Plugin):
|
||||
group_command = [
|
||||
# 通用
|
||||
BotCommand("help", "帮助"),
|
||||
# Wiki 类
|
||||
BotCommand("weapon", "查看音擎图鉴"),
|
||||
BotCommand("avatar", "查询角色攻略"),
|
||||
# UID 查询类
|
||||
BotCommand("stats", "玩家统计查询"),
|
||||
# Cookie 查询类
|
||||
|
19
plugins/admin/wiki.py
Normal file
@ -0,0 +1,19 @@
|
||||
from telegram import Update
|
||||
from telegram.ext import CallbackContext
|
||||
|
||||
from core.plugin import Plugin, handler
|
||||
from core.services.wiki.services import WikiService
|
||||
|
||||
|
||||
class WikiPlugin(Plugin):
|
||||
"""有关WIKI操作"""
|
||||
|
||||
def __init__(self, wiki_service: WikiService):
|
||||
self.wiki_service = wiki_service
|
||||
|
||||
@handler.command("refresh_wiki", block=False, admin=True)
|
||||
async def refresh_wiki(self, update: Update, _: CallbackContext):
|
||||
message = update.effective_message
|
||||
msg = await message.reply_text("正在刷新Wiki缓存,请稍等")
|
||||
await self.wiki_service.refresh_wiki()
|
||||
await msg.edit_text("刷新Wiki缓存成功")
|
@ -1,27 +1,31 @@
|
||||
import asyncio
|
||||
from typing import Awaitable, Dict, List, cast, Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
from telegram import (
|
||||
InlineQuery,
|
||||
InlineQueryResultArticle,
|
||||
InlineQueryResultPhoto,
|
||||
InlineQueryResultCachedPhoto,
|
||||
InlineQueryResultCachedDocument,
|
||||
InputTextMessageContent,
|
||||
Update,
|
||||
InlineQueryResultsButton,
|
||||
InlineKeyboardButton,
|
||||
InlineKeyboardMarkup,
|
||||
InlineKeyboardButton,
|
||||
InlineQueryResultPhoto,
|
||||
)
|
||||
from telegram.constants import ParseMode
|
||||
from telegram.error import BadRequest
|
||||
from telegram.ext import CallbackContext, ContextTypes
|
||||
|
||||
from core.dependence.assets import AssetsService
|
||||
from core.plugin import Plugin, handler
|
||||
from core.services.cookies import CookiesService
|
||||
from core.services.players import PlayersService
|
||||
from core.services.search.services import SearchServices
|
||||
from core.services.wiki.services import WikiService
|
||||
from gram_core.config import config
|
||||
from gram_core.plugin.methods.inline_use_data import IInlineUseData
|
||||
from gram_core.services.cookies import CookiesService
|
||||
from gram_core.services.players import PlayersService
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
@ -30,12 +34,20 @@ class Inline(Plugin):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
asset_service: AssetsService,
|
||||
search_service: SearchServices,
|
||||
wiki_service: WikiService,
|
||||
cookies_service: CookiesService,
|
||||
players_service: PlayersService,
|
||||
):
|
||||
self.asset_service = asset_service
|
||||
self.wiki_service = wiki_service
|
||||
self.weapons_list: List[Dict[str, str]] = []
|
||||
self.characters_list: List[Dict[str, str]] = []
|
||||
self.characters_material_list: List[Dict[str, str]] = []
|
||||
self.characters_guide_list: List[Dict[str, str]] = []
|
||||
self.light_cone_list: List[Dict[str, str]] = []
|
||||
self.relics_list: List[Dict[str, str]] = []
|
||||
self.refresh_task: List[Awaitable] = []
|
||||
self.search_service = search_service
|
||||
self.cookies_service = cookies_service
|
||||
@ -44,6 +56,84 @@ class Inline(Plugin):
|
||||
self.inline_use_data_map: Dict[str, IInlineUseData] = {}
|
||||
self.img_url = "https://i.dawnlab.me/b1bdf9cc3061d254f038e557557694bc.jpg"
|
||||
|
||||
async def initialize(self):
|
||||
async def task_light_cone():
|
||||
logger.info("Inline 模块正在获取武器列表")
|
||||
light_cone_datas: Dict[str, str] = {}
|
||||
light_cone_datas_name: Dict[str, str] = {}
|
||||
for light_cone in self.asset_service.weapon.data:
|
||||
light_cone_datas[light_cone.name] = light_cone.icon
|
||||
light_cone_datas_name[str(light_cone.id)] = light_cone.name
|
||||
# 武器列表
|
||||
for lid in self.wiki_service.raider.all_light_cone_raiders:
|
||||
if lid not in light_cone_datas_name:
|
||||
continue
|
||||
light_cone = light_cone_datas_name[lid]
|
||||
if light_cone in light_cone_datas:
|
||||
self.light_cone_list.append({"name": light_cone, "icon": light_cone_datas[light_cone]})
|
||||
else:
|
||||
logger.warning(f"未找到武器 {light_cone} 的图标,inline 不显示此武器")
|
||||
logger.success("Inline 模块获取武器列表完成")
|
||||
|
||||
async def task_relics():
|
||||
logger.info("Inline 模块正在获取驱动盘列表")
|
||||
relics_datas: Dict[str, str] = {}
|
||||
relics_datas_name: Dict[str, str] = {}
|
||||
for relics in self.wiki_service.equipment_suit.all_equipment_suits:
|
||||
relics_datas[relics.name] = relics.icon
|
||||
relics_datas_name[str(relics.id)] = relics.name
|
||||
for rid in self.wiki_service.raider.all_relic_raiders:
|
||||
if rid not in relics_datas_name:
|
||||
continue
|
||||
relics = relics_datas_name[rid]
|
||||
if relics in relics_datas:
|
||||
self.relics_list.append({"name": relics, "icon": relics_datas[relics]})
|
||||
else:
|
||||
logger.warning(f"未找到驱动盘 {relics} 的图标,inline 不显示此驱动盘")
|
||||
logger.success("Inline 模块获取驱动盘列表完成")
|
||||
|
||||
async def task_characters():
|
||||
logger.info("Inline 模块正在获取角色列表")
|
||||
datas: Dict[str, str] = {}
|
||||
datas_name: Dict[str, str] = {}
|
||||
for character in self.asset_service.avatar.data:
|
||||
datas[character.name] = character.normal
|
||||
datas_name[str(character.id)] = character.name
|
||||
|
||||
def get_character(_cid: str) -> str:
|
||||
if _cid in datas_name:
|
||||
return datas_name[_cid]
|
||||
|
||||
# 角色攻略
|
||||
for cid in self.wiki_service.raider.all_role_raiders:
|
||||
character = get_character(cid)
|
||||
if not character:
|
||||
continue
|
||||
if character in datas:
|
||||
self.characters_list.append({"name": character, "icon": datas[character]})
|
||||
else:
|
||||
for key, value in datas.items():
|
||||
if character.startswith(key) or character.endswith(key):
|
||||
self.characters_list.append({"name": character, "icon": value})
|
||||
break
|
||||
# 角色攻略
|
||||
for cid in self.wiki_service.raider.all_guide_for_role_raiders:
|
||||
character = get_character(cid)
|
||||
if not character:
|
||||
continue
|
||||
if character in datas:
|
||||
self.characters_guide_list.append({"name": character, "icon": datas[character]})
|
||||
else:
|
||||
for key, value in datas.items():
|
||||
if character.startswith(key) or character.endswith(key):
|
||||
self.characters_guide_list.append({"name": character, "icon": value})
|
||||
break
|
||||
logger.success("Inline 模块获取角色列表成功")
|
||||
|
||||
self.refresh_task.append(asyncio.create_task(task_characters()))
|
||||
self.refresh_task.append(asyncio.create_task(task_light_cone()))
|
||||
self.refresh_task.append(asyncio.create_task(task_relics()))
|
||||
|
||||
async def init_inline_use_data(self):
|
||||
if self.inline_use_data:
|
||||
return
|
||||
@ -159,20 +249,20 @@ class Inline(Plugin):
|
||||
results_list = []
|
||||
args = query.split(" ")
|
||||
if args[0] == "":
|
||||
temp_data = [
|
||||
("武器图鉴查询", "输入武器名称即可查询武器图鉴"),
|
||||
("角色攻略查询", "输入角色名即可查询角色攻略图鉴"),
|
||||
# ("角色图鉴查询", "输入角色名即可查询角色图鉴"),
|
||||
# ("角色培养素材查询", "输入角色名即可查询角色培养素材图鉴"),
|
||||
# ("驱动盘套装查询", "输入驱动盘套装名称即可查询驱动盘套装图鉴"),
|
||||
]
|
||||
for i in temp_data:
|
||||
results_list.append(
|
||||
InlineQueryResultArticle(
|
||||
id=str(uuid4()),
|
||||
title="武器图鉴查询",
|
||||
description="输入武器名称即可查询武器图鉴",
|
||||
input_message_content=InputTextMessageContent("武器图鉴查询"),
|
||||
)
|
||||
)
|
||||
results_list.append(
|
||||
InlineQueryResultArticle(
|
||||
id=str(uuid4()),
|
||||
title="角色攻略查询",
|
||||
description="输入角色名即可查询角色攻略",
|
||||
input_message_content=InputTextMessageContent("角色攻略查询"),
|
||||
title=i[0],
|
||||
description=i[1],
|
||||
input_message_content=InputTextMessageContent(i[0]),
|
||||
)
|
||||
)
|
||||
results_list.append(
|
||||
@ -188,33 +278,31 @@ class Inline(Plugin):
|
||||
elif args[0] == "功能":
|
||||
return
|
||||
else:
|
||||
if args[0] == "查看武器列表并查询":
|
||||
for weapon in self.weapons_list:
|
||||
name = weapon["name"]
|
||||
icon = weapon["icon"]
|
||||
results_list.append(
|
||||
InlineQueryResultArticle(
|
||||
id=str(uuid4()),
|
||||
title=name,
|
||||
description=f"查看武器列表并查询 {name}",
|
||||
thumbnail_url=icon,
|
||||
input_message_content=InputTextMessageContent(
|
||||
f"武器查询{name}", parse_mode=ParseMode.MARKDOWN_V2
|
||||
),
|
||||
)
|
||||
)
|
||||
elif args[0] == "查看角色攻略列表并查询":
|
||||
for character in self.characters_list:
|
||||
if args[0] in [
|
||||
# "查看角色攻略列表并查询",
|
||||
"查看角色图鉴列表并查询",
|
||||
"查看音擎列表并查询",
|
||||
# "查看驱动盘套装列表并查询",
|
||||
# "查看角色培养素材列表并查询",
|
||||
]:
|
||||
temp_data = {
|
||||
# "查看角色攻略列表并查询": (self.characters_list, "角色攻略查询"),
|
||||
"查看角色图鉴列表并查询": (self.characters_guide_list, "角色图鉴查询"),
|
||||
# "查看角色培养素材列表并查询": (self.characters_material_list, "角色培养素材查询"),
|
||||
"查看音擎列表并查询": (self.light_cone_list, "音擎图鉴查询"),
|
||||
# "查看驱动盘套装列表并查询": (self.relics_list, "驱动盘套装查询"),
|
||||
}[args[0]]
|
||||
for character in temp_data[0]:
|
||||
name = character["name"]
|
||||
icon = character["icon"]
|
||||
results_list.append(
|
||||
InlineQueryResultArticle(
|
||||
id=str(uuid4()),
|
||||
title=name,
|
||||
description=f"查看角色攻略列表并查询 {name}",
|
||||
description=f"{args[0]} {name}",
|
||||
thumbnail_url=icon,
|
||||
input_message_content=InputTextMessageContent(
|
||||
f"角色攻略查询{name}", parse_mode=ParseMode.MARKDOWN_V2
|
||||
f"{temp_data[1]}{name}", parse_mode=ParseMode.MARKDOWN_V2
|
||||
),
|
||||
)
|
||||
)
|
||||
@ -226,19 +314,19 @@ class Inline(Plugin):
|
||||
id=str(uuid4()),
|
||||
title=f"当前查询内容为 {args[0]}",
|
||||
description="如果无查看图片描述 这是正常的 客户端问题",
|
||||
thumbnail_url="https://www.miyoushe.com/_nuxt/img/game-ys.dfc535b.jpg",
|
||||
thumbnail_url="https://www.miyoushe.com/_nuxt/img/game-sr.4f80911.jpg",
|
||||
input_message_content=InputTextMessageContent(
|
||||
f"当前查询内容为 {args[0]}\n如果无查看图片描述 这是正常的 客户端问题"
|
||||
),
|
||||
)
|
||||
)
|
||||
for simple_search_result in simple_search_results:
|
||||
if simple_search_result.photo_file_id:
|
||||
description = simple_search_result.description
|
||||
if len(description) >= 10:
|
||||
description = description[:10]
|
||||
results_list.append(
|
||||
InlineQueryResultCachedPhoto(
|
||||
item = None
|
||||
if simple_search_result.photo_file_id:
|
||||
item = InlineQueryResultCachedPhoto(
|
||||
id=str(uuid4()),
|
||||
title=simple_search_result.title,
|
||||
photo_file_id=simple_search_result.photo_file_id,
|
||||
@ -246,15 +334,24 @@ class Inline(Plugin):
|
||||
caption=simple_search_result.caption,
|
||||
parse_mode=simple_search_result.parse_mode,
|
||||
)
|
||||
elif simple_search_result.document_file_id:
|
||||
item = InlineQueryResultCachedDocument(
|
||||
id=str(uuid4()),
|
||||
title=simple_search_result.title,
|
||||
document_file_id=simple_search_result.document_file_id,
|
||||
description=description,
|
||||
caption=simple_search_result.caption,
|
||||
parse_mode=simple_search_result.parse_mode,
|
||||
)
|
||||
|
||||
if item:
|
||||
results_list.append(item)
|
||||
if not results_list:
|
||||
results_list.append(
|
||||
InlineQueryResultArticle(
|
||||
id=str(uuid4()),
|
||||
title="好像找不到问题呢",
|
||||
description="这个问题我也不知道,因为我就是个应急食品。",
|
||||
input_message_content=InputTextMessageContent("这个问题我也不知道,因为我就是个应急食品。"),
|
||||
description="这个问题我也不知道。",
|
||||
input_message_content=InputTextMessageContent("这个问题我也不知道。"),
|
||||
)
|
||||
)
|
||||
try:
|
||||
|
@ -8,7 +8,6 @@ from typing import Optional, Tuple, List, TYPE_CHECKING
|
||||
from httpx import TimeoutException
|
||||
from simnet import Game
|
||||
from simnet.errors import BadRequest as SimnetBadRequest, AlreadyClaimed, InvalidCookies, TimedOut as SimnetTimedOut
|
||||
from simnet.utils.player import recognize_genshin_server
|
||||
from sqlalchemy.orm.exc import StaleDataError
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.constants import ParseMode
|
||||
@ -114,9 +113,6 @@ class SignSystem(Plugin):
|
||||
title: Optional[str] = "签到结果",
|
||||
) -> str:
|
||||
if is_sleep:
|
||||
if recognize_genshin_server(client.player_id) in ("cn_gf01", "cn_qd01"):
|
||||
await asyncio.sleep(random.randint(10, 300)) # nosec
|
||||
else:
|
||||
await asyncio.sleep(random.randint(0, 3)) # nosec
|
||||
try:
|
||||
rewards = await client.get_monthly_rewards(game=Game.GENSHIN, lang="zh-cn")
|
||||
@ -246,7 +242,7 @@ class SignSystem(Plugin):
|
||||
logger.warning("UID[%s] 已经签到", client.player_id)
|
||||
if is_raise:
|
||||
raise error
|
||||
result = "今天旅行者已经签到过了~"
|
||||
result = "今天绳匠已经签到过了~"
|
||||
except SimnetBadRequest as error:
|
||||
logger.warning("UID %s 签到失败,API返回信息为 %s", client.player_id, str(error))
|
||||
if is_raise:
|
||||
@ -256,7 +252,7 @@ class SignSystem(Plugin):
|
||||
result = "OK"
|
||||
else:
|
||||
logger.info("UID[%s] 已经签到", client.player_id)
|
||||
result = "今天旅行者已经签到过了~"
|
||||
result = "今天绳匠已经签到过了~"
|
||||
logger.info("UID[%s] 签到结果 %s", client.player_id, result)
|
||||
reward = rewards[daily_reward_info.claimed_rewards - (1 if daily_reward_info.signed_in else 0)]
|
||||
today = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
@ -300,7 +296,7 @@ class SignSystem(Plugin):
|
||||
text = "自动签到执行失败,Cookie无效"
|
||||
sign_db.status = TaskStatusEnum.INVALID_COOKIES
|
||||
except AlreadyClaimed:
|
||||
text = "今天旅行者已经签到过了~"
|
||||
text = "今天绳匠已经签到过了~"
|
||||
sign_db.status = TaskStatusEnum.ALREADY_CLAIMED
|
||||
except SimnetBadRequest as exc:
|
||||
text = f"自动签到执行失败,API返回信息为 {str(exc)}"
|
||||
|
86
plugins/zzz/avatars.py
Normal file
@ -0,0 +1,86 @@
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.ext import CallbackContext, filters
|
||||
|
||||
from core.plugin import Plugin, handler
|
||||
from core.services.game.services import GameCacheService
|
||||
from core.services.search.models import StrategyEntry
|
||||
from core.services.search.services import SearchServices
|
||||
from core.services.wiki.services import WikiService
|
||||
from metadata.shortname import roleToName, roleToTag
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class AvatarsPlugin(Plugin):
|
||||
"""角色图鉴查询"""
|
||||
|
||||
KEYBOARD = [
|
||||
[InlineKeyboardButton(text="查看角色图鉴列表并查询", switch_inline_query_current_chat="查看角色图鉴列表并查询")]
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cache_service: GameCacheService = None,
|
||||
wiki_service: WikiService = None,
|
||||
search_service: SearchServices = None,
|
||||
):
|
||||
self.cache_service = cache_service
|
||||
self.wiki_service = wiki_service
|
||||
self.search_service = search_service
|
||||
|
||||
@handler.command(command="avatar", block=False)
|
||||
@handler.message(filters=filters.Regex("^角色图鉴查询(.*)"), block=False)
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
message = update.effective_message
|
||||
args = self.get_args(context)
|
||||
if len(args) >= 1:
|
||||
character_name = args[0]
|
||||
else:
|
||||
reply_message = await message.reply_text(
|
||||
"请回复你要查询的图鉴的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||
)
|
||||
if filters.ChatType.GROUPS.filter(reply_message):
|
||||
self.add_delete_message_job(message)
|
||||
self.add_delete_message_job(reply_message)
|
||||
return
|
||||
character_name = roleToName(character_name)
|
||||
file_path = self.wiki_service.raider.raider_guide_for_role_path / f"{character_name}.png"
|
||||
if not file_path.exists():
|
||||
reply_message = await message.reply_text(
|
||||
f"没有找到 {character_name} 的图鉴", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||
)
|
||||
if filters.ChatType.GROUPS.filter(reply_message):
|
||||
self.add_delete_message_job(message)
|
||||
self.add_delete_message_job(reply_message)
|
||||
return
|
||||
self.log_user(update, logger.info, "查询角色图鉴命令请求 || 参数 %s", character_name)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
caption = "From 米游社@听语惊花"
|
||||
if file_id := await self.cache_service.get_avatar_cache(character_name):
|
||||
await message.reply_document(
|
||||
document=file_id,
|
||||
caption=caption,
|
||||
filename=f"{character_name}.png",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
else:
|
||||
reply_photo = await message.reply_document(
|
||||
document=open(file_path, "rb"),
|
||||
caption=caption,
|
||||
filename=f"{character_name}.png",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
if reply_photo.document:
|
||||
tags = roleToTag(character_name)
|
||||
photo_file_id = reply_photo.document.file_id
|
||||
await self.cache_service.set_avatar_cache(character_name, photo_file_id)
|
||||
entry = StrategyEntry(
|
||||
key=f"plugin:avatar:{character_name}",
|
||||
title=character_name,
|
||||
description=f"{character_name} 角色图鉴",
|
||||
tags=tags,
|
||||
caption=caption,
|
||||
parse_mode="HTML",
|
||||
document_file_id=photo_file_id,
|
||||
)
|
||||
await self.search_service.add_entry(entry)
|
596
plugins/zzz/signal_log.py
Normal file
@ -0,0 +1,596 @@
|
||||
from functools import partial
|
||||
from io import BytesIO
|
||||
from typing import Optional, TYPE_CHECKING, List, Union, Tuple, Dict
|
||||
|
||||
from simnet import ZZZClient, Region
|
||||
from simnet.models.zzz.wish import ZZZBannerType
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import ConversationHandler, 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 gram_core.basemodel import RegionEnum
|
||||
from gram_core.config import config
|
||||
from gram_core.plugin.methods.inline_use_data import IInlineUseData
|
||||
from modules.gacha_log.const import ZZZGF_VERSION, GACHA_TYPE_LIST_REVERSE
|
||||
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 modules.gacha_log.migrate import GachaLogMigrate
|
||||
from modules.gacha_log.models import GachaLogInfo
|
||||
from plugins.tools.genshin import PlayerNotFoundError
|
||||
from utils.const import RESOURCE_DIR
|
||||
from utils.log import logger
|
||||
|
||||
try:
|
||||
import ujson as jsonlib
|
||||
|
||||
except ImportError:
|
||||
import json as jsonlib
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Update, Message, User, Document
|
||||
from telegram.ext import ContextTypes
|
||||
from gram_core.services.players.models import Player
|
||||
from gram_core.services.template.models import RenderResult
|
||||
|
||||
INPUT_URL, INPUT_FILE, CONFIRM_DELETE = range(10100, 10103)
|
||||
WAITING = f"小{config.notice.bot_name}正在从服务器获取数据,请稍后"
|
||||
WISHLOG_NOT_FOUND = f"{config.notice.bot_name}没有找到你的调频记录,快来私聊{config.notice.bot_name}导入吧~"
|
||||
|
||||
|
||||
class WishLogPlugin(Plugin.Conversation):
|
||||
"""调频记录导入/导出/分析"""
|
||||
|
||||
IMPORT_HINT = (
|
||||
"<b>开始导入祈愿历史记录:请通过 https://zzz.rng.moe/en/tracker/import 获取调频记录链接后发送给我"
|
||||
"(非 zzz.rng.moe 导出的文件数据)</b>\n\n"
|
||||
f"> 你还可以向{config.notice.bot_name}发送从其他工具导出的 ZZZGF {ZZZGF_VERSION} 标准的记录文件\n"
|
||||
"> 在绑定 Cookie 时添加 stoken 可能有特殊效果哦(仅限国服)\n"
|
||||
"<b>注意:导入的数据将会与旧数据进行合并。</b>"
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
template_service: TemplateService,
|
||||
players_service: PlayersService,
|
||||
assets: AssetsService,
|
||||
cookie_service: CookiesService,
|
||||
):
|
||||
self.template_service = template_service
|
||||
self.players_service = players_service
|
||||
self.assets_service = assets
|
||||
self.cookie_service = cookie_service
|
||||
self.gacha_log = GachaLog()
|
||||
self.wish_photo = None
|
||||
|
||||
async def get_player_id(self, user_id: int, player_id: int, offset: int) -> int:
|
||||
"""获取绑定的游戏ID"""
|
||||
logger.debug("尝试获取已绑定的绝区零账号")
|
||||
player = await self.players_service.get_player(user_id, player_id=player_id, offset=offset)
|
||||
if player is None:
|
||||
raise PlayerNotFoundError(user_id)
|
||||
return player.player_id
|
||||
|
||||
async def _refresh_user_data(
|
||||
self, user: "User", player_id: int, data: dict = None, authkey: str = None, verify_uid: bool = True
|
||||
) -> str:
|
||||
"""刷新用户数据
|
||||
:param user: 用户
|
||||
:param data: 数据
|
||||
:param authkey: 认证密钥
|
||||
:return: 返回信息
|
||||
"""
|
||||
try:
|
||||
logger.debug("尝试获取已绑定的绝区零账号")
|
||||
if authkey:
|
||||
new_num = await self.gacha_log.get_gacha_log_data(user.id, player_id, authkey)
|
||||
return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条调频记录"
|
||||
if data:
|
||||
new_num = await self.gacha_log.import_gacha_log_data(user.id, player_id, data, verify_uid)
|
||||
return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条调频记录"
|
||||
except GachaLogNotFound:
|
||||
return WISHLOG_NOT_FOUND
|
||||
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 config.notice.user_not_found
|
||||
|
||||
async def import_from_file(
|
||||
self, user: "User", player_id: int, 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("文件格式错误,请发送符合 ZZZGF 标准的调频记录文件")
|
||||
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("文件解析失败,请检查文件是否符合 ZZZGF 标准")
|
||||
return
|
||||
except (KeyError, IndexError, ValueError):
|
||||
await message.reply_text("文件解析失败,请检查文件编码是否正确或符合 ZZZGF 标准")
|
||||
return
|
||||
except Exception as exc:
|
||||
logger.error("文件解析失败 %s", repr(exc))
|
||||
await message.reply_text("文件解析失败,请检查文件是否符合 ZZZGF 标准")
|
||||
return
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
reply = await message.reply_text("文件解析成功,正在导入数据", reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
try:
|
||||
text = await self._refresh_user_data(user, player_id, data=data, verify_uid=file_type == "json")
|
||||
except Exception as exc: # pylint: disable=W0703
|
||||
logger.error("文件解析失败 %s", repr(exc))
|
||||
text = "文件解析失败,请检查文件是否符合 ZZZGF 标准"
|
||||
await reply.edit_text(text)
|
||||
|
||||
async def can_gen_authkey(self, user_id: int, player_id: int) -> bool:
|
||||
player_info = await self.players_service.get_player(user_id, region=RegionEnum.HYPERION, player_id=player_id)
|
||||
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
|
||||
and next((value for key, value in cookies.data.items() if key in ["ltuid", "login_uid"]), None)
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def gen_authkey(self, uid: int) -> Optional[str]:
|
||||
player_info = await self.players_service.get_player(uid, region=RegionEnum.HYPERION)
|
||||
if player_info is not None:
|
||||
cookies = await self.cookie_service.get(uid, 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
|
||||
async with ZZZClient(
|
||||
cookies=cookies.data, region=Region.CHINESE, lang="zh-cn", player_id=player_info.player_id
|
||||
) as client:
|
||||
return await client.get_authkey_by_stoken("webview_gacha")
|
||||
|
||||
@conversation.entry_point
|
||||
@handler.command(command="signal_log_import", filters=filters.ChatType.PRIVATE, block=False)
|
||||
@handler.message(filters=filters.Regex("^导入调频记录(.*)") & filters.ChatType.PRIVATE, block=False)
|
||||
@handler.command(command="start", filters=filters.Regex("signal_log_import$"), block=False)
|
||||
async def command_start(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> int:
|
||||
uid, offset = self.get_real_uid_or_offset(update)
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
player_id = await self.get_player_id(user.id, uid, offset)
|
||||
context.chat_data["uid"] = player_id
|
||||
logger.info("用户 %s[%s] 导入调频记录命令请求", user.full_name, user.id)
|
||||
keyboard = None
|
||||
if await self.can_gen_authkey(user.id, player_id):
|
||||
keyboard = ReplyKeyboardMarkup([["自动导入"], ["退出"]], one_time_keyboard=True)
|
||||
await message.reply_text(self.IMPORT_HINT, parse_mode="html", reply_markup=keyboard)
|
||||
return INPUT_URL
|
||||
|
||||
@conversation.state(state=INPUT_URL)
|
||||
@handler.message(filters=~filters.COMMAND, block=False)
|
||||
async def import_data_from_message(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> int:
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
player_id = context.chat_data["uid"]
|
||||
if message.document:
|
||||
await self.import_from_file(user, player_id, message)
|
||||
return ConversationHandler.END
|
||||
if not message.text:
|
||||
await message.reply_text("请发送文件或链接")
|
||||
return INPUT_URL
|
||||
if message.text == "自动导入":
|
||||
authkey = await self.gen_authkey(user.id)
|
||||
if not authkey:
|
||||
await message.reply_text(
|
||||
"自动生成 authkey 失败,请尝试通过其他方式导入。", reply_markup=ReplyKeyboardRemove()
|
||||
)
|
||||
return ConversationHandler.END
|
||||
elif message.text == "退出":
|
||||
await message.reply_text("取消导入跃迁记录", reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
else:
|
||||
authkey = from_url_get_authkey(message.text)
|
||||
reply = await message.reply_text(WAITING, reply_markup=ReplyKeyboardRemove())
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
text = await self._refresh_user_data(user, player_id, authkey=authkey)
|
||||
self.add_delete_message_job(reply, delay=1)
|
||||
await message.reply_text(text, reply_markup=ReplyKeyboardRemove())
|
||||
return ConversationHandler.END
|
||||
|
||||
@conversation.entry_point
|
||||
@handler.command(command="signal_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: "ContextTypes.DEFAULT_TYPE") -> int:
|
||||
uid, offset = self.get_real_uid_or_offset(update)
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
logger.info("用户 %s[%s] 删除调频记录命令请求", user.full_name, user.id)
|
||||
try:
|
||||
player_id = await self.get_player_id(user.id, uid, offset)
|
||||
context.chat_data["uid"] = player_id
|
||||
except PlayerNotFoundError:
|
||||
logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id)
|
||||
await message.reply_text(config.notice.user_not_found)
|
||||
return ConversationHandler.END
|
||||
_, status = await self.gacha_log.load_history_info(str(user.id), str(player_id), 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: "ContextTypes.DEFAULT_TYPE") -> 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.command(command="signal_log_force_delete", block=False, admin=True)
|
||||
async def command_signal_log_force_delete(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"):
|
||||
uid, offset = self.get_real_uid_or_offset(update)
|
||||
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")
|
||||
player_id = await self.get_player_id(cid, uid, offset)
|
||||
_, status = await self.gacha_log.load_history_info(str(cid), str(player_id), only_status=True)
|
||||
if not status:
|
||||
await message.reply_text("该用户还没有导入调频记录")
|
||||
return
|
||||
status = await self.gacha_log.remove_history_info(str(cid), str(player_id))
|
||||
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.command(command="signal_log_export", filters=filters.ChatType.PRIVATE, block=False)
|
||||
@handler.message(filters=filters.Regex("^导出调频记录(.*)") & filters.ChatType.PRIVATE, block=False)
|
||||
async def command_start_export(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
|
||||
uid, offset = self.get_real_uid_or_offset(update)
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
logger.info("用户 %s[%s] 导出调频记录命令请求", user.full_name, user.id)
|
||||
try:
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
player_id = await self.get_player_id(user.id, uid, offset)
|
||||
path = await self.gacha_log.gacha_log_to_zzzgf(str(user.id), str(player_id))
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT)
|
||||
await message.reply_document(
|
||||
document=open(path, "rb+"), caption=f"调频记录导出文件 - ZZZGF {ZZZGF_VERSION}"
|
||||
)
|
||||
except GachaLogNotFound:
|
||||
logger.info("未找到用户 %s[%s] 的调频记录", user.full_name, user.id)
|
||||
buttons = [
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
"点我导入", url=create_deep_linked_url(context.bot.username, "signal_log_import")
|
||||
)
|
||||
]
|
||||
]
|
||||
await message.reply_text(WISHLOG_NOT_FOUND, 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(config.notice.user_not_found)
|
||||
|
||||
async def rander_wish_log_analysis(
|
||||
self, user_id: int, player_id: int, pool_type: ZZZBannerType
|
||||
) -> Union[str, "RenderResult"]:
|
||||
data = await self.gacha_log.get_analysis(user_id, player_id, pool_type, self.assets_service)
|
||||
if isinstance(data, str):
|
||||
return data
|
||||
await self.add_theme_data(data, player_id)
|
||||
png_data = await self.template_service.render(
|
||||
"zzz/gacha_log/gacha_log.html",
|
||||
data,
|
||||
full_page=True,
|
||||
file_type=FileType.DOCUMENT if len(data.get("fiveLog")) > 300 else FileType.PHOTO,
|
||||
query_selector=".body_box",
|
||||
)
|
||||
return png_data
|
||||
|
||||
@staticmethod
|
||||
def gen_button(user_id: int, uid: int, info: "GachaLogInfo") -> List[List[InlineKeyboardButton]]:
|
||||
buttons = []
|
||||
pools = []
|
||||
skip_pools = []
|
||||
for k, v in info.item_list.items():
|
||||
if k in skip_pools:
|
||||
continue
|
||||
if not v:
|
||||
continue
|
||||
pools.append(k)
|
||||
# 2 个一组
|
||||
for i in range(0, len(pools), 2):
|
||||
row = []
|
||||
for pool in pools[i : i + 2]:
|
||||
for k, v in {"log": "", "count": "(按卡池)"}.items():
|
||||
row.append(
|
||||
InlineKeyboardButton(
|
||||
f"{pool.replace('祈愿', '')}{v}",
|
||||
callback_data=f"get_wish_log|{user_id}|{uid}|{k}|{pool}",
|
||||
)
|
||||
)
|
||||
buttons.append(row)
|
||||
buttons.append([InlineKeyboardButton("五星调频统计", callback_data=f"get_wish_log|{user_id}|{uid}|count|five")])
|
||||
return buttons
|
||||
|
||||
async def wish_log_pool_choose(self, user_id: int, player_id: int, message: "Message"):
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
gacha_log, status = await self.gacha_log.load_history_info(str(user_id), str(player_id))
|
||||
if not status:
|
||||
raise GachaLogNotFound
|
||||
buttons = self.gen_button(user_id, player_id, gacha_log)
|
||||
if isinstance(self.wish_photo, str):
|
||||
photo = self.wish_photo
|
||||
else:
|
||||
photo = open("resources/img/wish.jpg", "rb")
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
reply_message = await message.reply_photo(
|
||||
photo=photo,
|
||||
caption="请选择你要查询的卡池",
|
||||
reply_markup=InlineKeyboardMarkup(buttons),
|
||||
)
|
||||
if reply_message.photo:
|
||||
self.wish_photo = reply_message.photo[-1].file_id
|
||||
|
||||
async def wish_log_pool_send(self, user_id: int, uid: int, pool_type: "ZZZBannerType", message: "Message"):
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
png_data = await self.rander_wish_log_analysis(user_id, uid, pool_type)
|
||||
if isinstance(png_data, str):
|
||||
reply = await message.reply_text(png_data)
|
||||
if filters.ChatType.GROUPS.filter(message):
|
||||
self.add_delete_message_job(reply)
|
||||
self.add_delete_message_job(message)
|
||||
else:
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
if png_data.file_type == FileType.DOCUMENT:
|
||||
await png_data.reply_document(message, filename="调频统计.png")
|
||||
else:
|
||||
await png_data.reply_photo(message)
|
||||
|
||||
@handler.command(command="signal_log", block=False)
|
||||
@handler.message(filters=filters.Regex("^调频记录?(光锥|角色|常驻|新手)$"), block=False)
|
||||
async def command_start_analysis(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None:
|
||||
user_id = await self.get_real_user_id(update)
|
||||
uid, offset = self.get_real_uid_or_offset(update)
|
||||
message = update.effective_message
|
||||
pool_type = None
|
||||
if args := self.get_args(context):
|
||||
if "角色" in args:
|
||||
pool_type = ZZZBannerType.CHARACTER
|
||||
elif "武器" in args:
|
||||
pool_type = ZZZBannerType.WEAPON
|
||||
elif "常驻" in args:
|
||||
pool_type = ZZZBannerType.STANDARD
|
||||
elif "邦布" in args:
|
||||
pool_type = ZZZBannerType.BANGBOO
|
||||
self.log_user(update, logger.info, "调频记录命令请求 || 参数 %s", pool_type.name if pool_type else None)
|
||||
try:
|
||||
player_id = await self.get_player_id(user_id, uid, offset)
|
||||
if pool_type is None:
|
||||
await self.wish_log_pool_choose(user_id, player_id, message)
|
||||
else:
|
||||
await self.wish_log_pool_send(user_id, player_id, pool_type, message)
|
||||
except GachaLogNotFound:
|
||||
self.log_user(update, logger.info, "未找到调频记录")
|
||||
buttons = [
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
"点我导入", url=create_deep_linked_url(context.bot.username, "signal_log_import")
|
||||
)
|
||||
]
|
||||
]
|
||||
await message.reply_text(
|
||||
WISHLOG_NOT_FOUND,
|
||||
reply_markup=InlineKeyboardMarkup(buttons),
|
||||
)
|
||||
|
||||
@handler.callback_query(pattern=r"^get_wish_log\|", block=False)
|
||||
async def get_wish_log(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None:
|
||||
callback_query = update.callback_query
|
||||
user = callback_query.from_user
|
||||
message = callback_query.message
|
||||
|
||||
async def get_wish_log_callback(
|
||||
callback_query_data: str,
|
||||
) -> Tuple[str, str, int, int]:
|
||||
_data = callback_query_data.split("|")
|
||||
_user_id = int(_data[1])
|
||||
_uid = int(_data[2])
|
||||
_t = _data[3]
|
||||
_result = _data[4]
|
||||
logger.debug(
|
||||
"callback_query_data函数返回 result[%s] user_id[%s] uid[%s] show_type[%s]",
|
||||
_result,
|
||||
_user_id,
|
||||
_uid,
|
||||
_t,
|
||||
)
|
||||
return _result, _t, _user_id, _uid
|
||||
|
||||
try:
|
||||
pool, show_type, user_id, uid = await get_wish_log_callback(callback_query.data)
|
||||
except IndexError:
|
||||
await callback_query.answer("按钮数据已过期,请重新获取。", show_alert=True)
|
||||
self.add_delete_message_job(message, delay=1)
|
||||
return
|
||||
if user.id != user_id:
|
||||
await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True)
|
||||
return
|
||||
if show_type == "count":
|
||||
await self.get_wish_log_count(update, user_id, uid, pool)
|
||||
else:
|
||||
await self.get_wish_log_log(update, user_id, uid, pool)
|
||||
|
||||
async def get_wish_log_log(self, update: "Update", user_id: int, uid: int, pool: str):
|
||||
callback_query = update.callback_query
|
||||
message = callback_query.message
|
||||
|
||||
pool_type = GACHA_TYPE_LIST_REVERSE.get(pool)
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
try:
|
||||
png_data = await self.rander_wish_log_analysis(user_id, uid, pool_type)
|
||||
except GachaLogNotFound:
|
||||
png_data = "未找到调频记录"
|
||||
if isinstance(png_data, str):
|
||||
await callback_query.answer(png_data, show_alert=True)
|
||||
self.add_delete_message_job(message, delay=1)
|
||||
else:
|
||||
await callback_query.answer(text="正在渲染图片中 请稍等 请不要重复点击按钮", show_alert=False)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
if png_data.file_type == FileType.DOCUMENT:
|
||||
await png_data.reply_document(message, filename="调频统计.png")
|
||||
self.add_delete_message_job(message, delay=1)
|
||||
else:
|
||||
await png_data.edit_media(message)
|
||||
|
||||
async def get_wish_log_count(self, update: "Update", user_id: int, uid: int, pool: str):
|
||||
callback_query = update.callback_query
|
||||
message = callback_query.message
|
||||
|
||||
all_five = pool == "five"
|
||||
group = filters.ChatType.GROUPS.filter(message)
|
||||
pool_type = GACHA_TYPE_LIST_REVERSE.get(pool)
|
||||
await message.reply_chat_action(ChatAction.TYPING)
|
||||
try:
|
||||
if all_five:
|
||||
png_data = await self.gacha_log.get_all_five_analysis(user_id, uid, self.assets_service)
|
||||
else:
|
||||
png_data = await self.gacha_log.get_pool_analysis(user_id, uid, pool_type, self.assets_service, group)
|
||||
except GachaLogNotFound:
|
||||
png_data = "未找到调频记录"
|
||||
if isinstance(png_data, str):
|
||||
await callback_query.answer(png_data, show_alert=True)
|
||||
self.add_delete_message_job(message, delay=1)
|
||||
else:
|
||||
await self.add_theme_data(png_data, uid)
|
||||
await callback_query.answer(text="正在渲染图片中 请稍等 请不要重复点击按钮", show_alert=False)
|
||||
document = False
|
||||
if png_data["hasMore"] and not group:
|
||||
document = True
|
||||
png_data["hasMore"] = False
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT if document else ChatAction.UPLOAD_PHOTO)
|
||||
png = await self.template_service.render(
|
||||
"zzz/gacha_count/gacha_count.html",
|
||||
png_data,
|
||||
full_page=True,
|
||||
query_selector=".body_box",
|
||||
file_type=FileType.DOCUMENT if document else FileType.PHOTO,
|
||||
)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
if document:
|
||||
await png.reply_document(message, filename="调频统计.png")
|
||||
self.add_delete_message_job(message, delay=1)
|
||||
else:
|
||||
await png.edit_media(message)
|
||||
|
||||
async def add_theme_data(self, data: Dict, player_id: int):
|
||||
res = RESOURCE_DIR / "img"
|
||||
data["avatar"] = (res / "avatar.png").as_uri()
|
||||
data["background"] = (res / "home.png").as_uri()
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
async def get_migrate_data(
|
||||
old_user_id: int, new_user_id: int, old_players: List["Player"]
|
||||
) -> Optional[GachaLogMigrate]:
|
||||
return await GachaLogMigrate.create(old_user_id, new_user_id, old_players)
|
||||
|
||||
async def wish_log_use_by_inline(
|
||||
self, update: "Update", context: "ContextTypes.DEFAULT_TYPE", pool_type: "ZZZBannerType"
|
||||
):
|
||||
callback_query = update.callback_query
|
||||
user = update.effective_user
|
||||
user_id = user.id
|
||||
uid = IInlineUseData.get_uid_from_context(context)
|
||||
|
||||
self.log_user(update, logger.info, "调频记录命令请求 || 参数 %s", pool_type.name if pool_type else None)
|
||||
notice = None
|
||||
try:
|
||||
render_result = await self.rander_wish_log_analysis(user_id, uid, pool_type)
|
||||
if isinstance(render_result, str):
|
||||
notice = render_result
|
||||
else:
|
||||
await render_result.edit_inline_media(callback_query, filename="调频统计.png")
|
||||
except GachaLogNotFound:
|
||||
self.log_user(update, logger.info, "未找到调频记录")
|
||||
notice = "未找到调频记录"
|
||||
if notice:
|
||||
await callback_query.answer(notice, show_alert=True)
|
||||
|
||||
async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]:
|
||||
types = {
|
||||
"代理人": ZZZBannerType.CHARACTER,
|
||||
"音擎": ZZZBannerType.WEAPON,
|
||||
"邦布": ZZZBannerType.BANGBOO,
|
||||
"常驻": ZZZBannerType.STANDARD,
|
||||
}
|
||||
data = []
|
||||
for k, v in types.items():
|
||||
data.append(
|
||||
IInlineUseData(
|
||||
text=f"{k}调频",
|
||||
hash=f"signal_log_{v.value}",
|
||||
callback=partial(self.wish_log_use_by_inline, pool_type=v),
|
||||
player=True,
|
||||
)
|
||||
)
|
||||
return data
|
@ -10,6 +10,7 @@ from core.services.template.models import RenderResult
|
||||
from core.services.template.services import TemplateService
|
||||
from gram_core.plugin.methods.inline_use_data import IInlineUseData
|
||||
from plugins.tools.genshin import GenshinHelper
|
||||
from utils.const import RESOURCE_DIR
|
||||
from utils.log import logger
|
||||
from utils.uid import mask_number
|
||||
|
||||
@ -60,9 +61,6 @@ class PlayerStatsPlugins(Plugin):
|
||||
uid = client.player_id
|
||||
user_info = await client.get_zzz_user(uid)
|
||||
|
||||
# 因为需要替换线上图片地址为本地地址,先克隆数据,避免修改原数据
|
||||
user_info = user_info.copy(deep=True)
|
||||
|
||||
data = {
|
||||
"uid": mask_number(uid),
|
||||
"stats": user_info.stats,
|
||||
@ -74,11 +72,11 @@ class PlayerStatsPlugins(Plugin):
|
||||
("式舆防卫战防线", "cur_period_zone_layer_count"),
|
||||
("获得邦布数", "buddy_num"),
|
||||
],
|
||||
"style": random.choice(["mondstadt", "liyue"]), # nosec
|
||||
"style": "main", # nosec
|
||||
}
|
||||
|
||||
await self.add_theme_data(data, uid)
|
||||
return await self.template_service.render(
|
||||
"zzz/stats/stats.jinja2",
|
||||
"zzz/stats/stats.html",
|
||||
data,
|
||||
{"width": 650, "height": 400},
|
||||
full_page=True,
|
||||
@ -113,6 +111,12 @@ class PlayerStatsPlugins(Plugin):
|
||||
return
|
||||
await render_result.edit_inline_media(callback_query)
|
||||
|
||||
async def add_theme_data(self, data, player_id: int):
|
||||
res = RESOURCE_DIR / "img"
|
||||
data["avatar"] = (res / "avatar.png").as_uri()
|
||||
data["background"] = (res / "home.png").as_uri()
|
||||
return data
|
||||
|
||||
async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]:
|
||||
return [
|
||||
IInlineUseData(
|
||||
|
86
plugins/zzz/weapon.py
Normal file
@ -0,0 +1,86 @@
|
||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.ext import CallbackContext, filters
|
||||
|
||||
from core.plugin import Plugin, handler
|
||||
from core.services.game.services import GameCacheService
|
||||
from core.services.search.models import StrategyEntry
|
||||
from core.services.search.services import SearchServices
|
||||
from core.services.wiki.services import WikiService
|
||||
from metadata.shortname import weaponToTag, weaponToName
|
||||
from utils.log import logger
|
||||
|
||||
|
||||
class WeaponPlugin(Plugin):
|
||||
"""音擎图鉴查询"""
|
||||
|
||||
KEYBOARD = [
|
||||
[InlineKeyboardButton(text="查看音擎列表并查询", switch_inline_query_current_chat="查看音擎列表并查询")]
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cache_service: GameCacheService = None,
|
||||
wiki_service: WikiService = None,
|
||||
search_service: SearchServices = None,
|
||||
):
|
||||
self.cache_service = cache_service
|
||||
self.wiki_service = wiki_service
|
||||
self.search_service = search_service
|
||||
|
||||
@handler.command(command="weapon", block=False)
|
||||
@handler.message(filters=filters.Regex("^音擎图鉴查询(.*)"), block=False)
|
||||
async def command_start(self, update: Update, context: CallbackContext) -> None:
|
||||
message = update.effective_message
|
||||
args = self.get_args(context)
|
||||
if len(args) >= 1:
|
||||
weapon_name = args[0]
|
||||
else:
|
||||
reply_message = await message.reply_text(
|
||||
"请回复你要查询的音擎名称", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||
)
|
||||
if filters.ChatType.GROUPS.filter(reply_message):
|
||||
self.add_delete_message_job(message)
|
||||
self.add_delete_message_job(reply_message)
|
||||
return
|
||||
weapon_name = weaponToName(weapon_name)
|
||||
file_path = self.wiki_service.raider.raider_light_cone_path / f"{weapon_name}.png"
|
||||
if not file_path.exists():
|
||||
reply_message = await message.reply_text(
|
||||
f"没有找到 {weapon_name} 的图鉴", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)
|
||||
)
|
||||
if filters.ChatType.GROUPS.filter(reply_message):
|
||||
self.add_delete_message_job(message)
|
||||
self.add_delete_message_job(reply_message)
|
||||
return
|
||||
self.log_user(update, logger.info, "查询音擎图鉴命令请求 || 参数 %s", weapon_name)
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
caption = "From 米游社@听语惊花"
|
||||
if file_id := await self.cache_service.get_weapon_cache(weapon_name):
|
||||
await message.reply_photo(
|
||||
photo=file_id,
|
||||
caption=caption,
|
||||
filename=f"{weapon_name}.png",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
else:
|
||||
reply_photo = await message.reply_photo(
|
||||
photo=open(file_path, "rb"),
|
||||
caption=caption,
|
||||
filename=f"{weapon_name}.png",
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
if reply_photo.photo:
|
||||
tags = weaponToTag(weapon_name)
|
||||
photo_file_id = reply_photo.photo[0].file_id
|
||||
await self.cache_service.set_weapon_cache(weapon_name, photo_file_id)
|
||||
entry = StrategyEntry(
|
||||
key=f"plugin:strategy:{weapon_name}",
|
||||
title=weapon_name,
|
||||
description=f"{weapon_name} 音擎图鉴",
|
||||
tags=tags,
|
||||
caption=caption,
|
||||
parse_mode="HTML",
|
||||
photo_file_id=photo_file_id,
|
||||
)
|
||||
await self.search_service.add_entry(entry)
|
@ -88,7 +88,7 @@ rich==13.7.1
|
||||
sentry-sdk==2.7.1
|
||||
setuptools==70.2.0
|
||||
shellingham==1.5.4
|
||||
simnet @ git+https://github.com/PaiGramTeam/SIMNet@05fcb568d6c1fe44a4f917c996198bfe62a00053
|
||||
simnet @ git+https://github.com/PaiGramTeam/SIMNet@074939d8818e6073be4a918b25d7deadd43a5b7b
|
||||
six==1.16.0
|
||||
smmap==5.0.1
|
||||
sniffio==1.3.1
|
||||
|
BIN
resources/img/avatar.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
resources/img/home.png
Normal file
After Width: | Height: | Size: 407 KiB |
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 423 KiB |
47
resources/zzz/gacha_count/example.html
Normal 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>
|
226
resources/zzz/gacha_count/gacha_count.css
Normal file
@ -0,0 +1,226 @@
|
||||
@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;
|
||||
}
|
||||
|
||||
.header {
|
||||
/*background: #e0dad3 url(../gacha_log/img/starrail.png) no-repeat right;*/
|
||||
box-shadow: 0 0 8px #72a2ae79;
|
||||
background-size: cover;
|
||||
background-position: top;
|
||||
}
|
||||
|
||||
.frame {
|
||||
border-color: #cdbea8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 16px;
|
||||
width: 1286px;
|
||||
color: #1E1F20;
|
||||
transform: scale(1.5);
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 1286px;
|
||||
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 .starrail_logo {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 30px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
58
resources/zzz/gacha_count/gacha_count.html
Normal file
@ -0,0 +1,58 @@
|
||||
<!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="./../../genshin/abyss/background/roleStarBg5.png" as="image">
|
||||
<link rel="preload" href="./../../genshin/abyss/background/roleStarBg4.png" as="image">
|
||||
<link rel="preload" href="../gacha_log/img/starrail.png" as="image" />
|
||||
<script src="../../js/tailwindcss-3.1.8.js"></script>
|
||||
<title></title>
|
||||
</head>
|
||||
<body id="container" class="body_box">
|
||||
<div class="container">
|
||||
<div class="info_box">
|
||||
<div class="header p-2 rounded-xl mb-6" style='background-image: url("{{ background }}")'>
|
||||
<div class="frame p-4 rounded-lg border-solid border-2 flex items-center">
|
||||
<img class="w-16 h-16 rounded-full mr-4" src="{{ avatar }}" alt="Avatar">
|
||||
<div>
|
||||
<h2 class="font-bold italic">ID: {{ uid }}</h2>
|
||||
<h2 class="italic">
|
||||
跃迁统计-{{ typeName }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% for val in pool %}
|
||||
<div class="pool_box">
|
||||
<div class="title_box">
|
||||
<div class="name_box">
|
||||
<div class="title text-lg font-bold"><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 x Generated By PamGram</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
77
resources/zzz/gacha_log/example.html
Normal 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>
|
345
resources/zzz/gacha_log/gacha_log.css
Normal file
@ -0,0 +1,345 @@
|
||||
@font-face {
|
||||
font-family: "tttgbnumber";
|
||||
src: url("./../../fonts/tttgbnumber.ttf");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.header {
|
||||
/*background: #e0dad3 url(./img/starrail.png) no-repeat right;*/
|
||||
box-shadow: 0 0 8px #72a2ae79;
|
||||
background-size: cover;
|
||||
background-position: top;
|
||||
}
|
||||
|
||||
.frame {
|
||||
border-color: #cdbea8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 18px;
|
||||
color: #1e1f20;
|
||||
font-family: PingFangSC-Medium, PingFang SC, sans-serif;
|
||||
transform: scale(1.5);
|
||||
transform-origin: 0 0;
|
||||
width: 980px
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 980px;
|
||||
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 .starrail_logo {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 30px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.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!important;
|
||||
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;
|
||||
}
|
83
resources/zzz/gacha_log/gacha_log.html
Normal file
@ -0,0 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="gacha_log.css" />
|
||||
<link type="text/css" href="../../styles/public.css" rel="stylesheet" />
|
||||
<link rel="preload" href="./img/starrail.png" as="image" />
|
||||
<script src="../../js/tailwindcss-3.1.8.js"></script>
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body id="container" class="body_box">
|
||||
<div class="container">
|
||||
<div class="info_box">
|
||||
<div class="header p-2 rounded-xl mb-6" style='background-image: url("{{ background }}")'>
|
||||
<div class="frame p-4 rounded-lg border-solid border-2 flex items-center">
|
||||
<img class="w-16 h-16 rounded-full mr-4" src="{{ avatar }}" alt="Avatar">
|
||||
<div>
|
||||
<h2 class="font-bold italic">ID: {{ uid }}</h2>
|
||||
<h2 class="italic">
|
||||
{{ allNum }}抽
|
||||
<span class="label text-neutral-600 label_{{type}}"
|
||||
>{{ typeName }}</span
|
||||
>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</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 x Generated By PamGram</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
resources/zzz/gacha_log/img/starrail.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
resources/zzz/gacha_log/img/提纳里.png
Normal file
After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 399 KiB |
Before Width: | Height: | Size: 342 KiB |
@ -1,62 +0,0 @@
|
||||
:root {
|
||||
--primary: #ffeabd;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f5f6fb;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-image: url(../background/liyue.png);
|
||||
box-shadow: 0 0 16px rgb(255 233 144 / 50%);
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: #9c433d;
|
||||
box-shadow: 0 0 16px rgb(255 233 144 / 50%);
|
||||
}
|
||||
|
||||
.box-title {
|
||||
background-color: rgb(255, 200, 122, 0.1);
|
||||
--tw-ring-color: #ff9966;
|
||||
}
|
||||
|
||||
.pointer-bar {
|
||||
width: 95%;
|
||||
height: 8px;
|
||||
display: inline-block;
|
||||
background-color: rgb(0, 0, 0, 0.1);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.pointer-progress-bar {
|
||||
border-radius: 0.25rem;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, #f5efcd, #f8eabd, #ffdf90);
|
||||
}
|
||||
|
||||
.name {
|
||||
background: linear-gradient(to bottom, #ffffff, #ffeabd, #ffdf90);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.uid {
|
||||
color: var(--primary);
|
||||
background: linear-gradient(to right, rgb(0, 0, 0, 0), #cc6666, rgb(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.about {
|
||||
background-color: #e0dad3;
|
||||
color: #8a4d30;
|
||||
}
|
||||
|
||||
.box-stats {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.box-stats-label {
|
||||
color: var(--primary) !important;
|
||||
opacity: 0.65;
|
||||
}
|
@ -1,256 +0,0 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Title</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<link href="./liyue.css" rel="stylesheet" />
|
||||
<link type="text/css" href="../../../styles/public.css" rel="stylesheet" />
|
||||
<script src="../../../js/tailwindcss-3.1.8.js"></script>
|
||||
</head>
|
||||
<body class="text-neutral-600">
|
||||
<div class="mx-auto max-w-[600px] py-8">
|
||||
<div class="header p-6 flex mb-8 rounded-xl bg-cover justify-between">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<h1 class="text-4xl italic name mb-2 px-2">
|
||||
小何
|
||||
<span class="text-lg">lv.58</span>
|
||||
</h1>
|
||||
<h1 class="italic uid px-10">UID - 125324176</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box pt-4 rounded-xl overflow-hidden">
|
||||
<div>
|
||||
<h2 class="box-title text-center text-xl ring text-neutral-100 p-1">
|
||||
数据总览
|
||||
</h2>
|
||||
<div class="p-6 grid grid-cols-4 gap-4 text-center">
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">493</div>
|
||||
<div class="text-neutral-400 box-stats-label">活跃天数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">536</div>
|
||||
<div class="text-neutral-400 box-stats-label">成就达成数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">38</div>
|
||||
<div class="text-neutral-400 box-stats-label">获取角色数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">12-3</div>
|
||||
<div class="text-neutral-400 box-stats-label">深境螺旋</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">227</div>
|
||||
<div class="text-neutral-400 box-stats-label">解锁传送点</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">41</div>
|
||||
<div class="text-neutral-400 box-stats-label">解锁秘境</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">58</div>
|
||||
<div class="text-neutral-400 box-stats-label">奇馈宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">128</div>
|
||||
<div class="text-neutral-400 box-stats-label">华丽宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">316</div>
|
||||
<div class="text-neutral-400 box-stats-label">珍贵宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">1184</div>
|
||||
<div class="text-neutral-400 box-stats-label">精致宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">1594</div>
|
||||
<div class="text-neutral-400 box-stats-label">普通宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">65</div>
|
||||
<div class="text-neutral-400 box-stats-label">风神瞳</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">131</div>
|
||||
<div class="text-neutral-400 box-stats-label">岩神瞳</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">180</div>
|
||||
<div class="text-neutral-400 box-stats-label">雷神瞳</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">80</div>
|
||||
<div class="text-neutral-400 box-stats-label">草神瞳</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="box-title text-center text-xl ring text-neutral-100 p-1">
|
||||
世界探索
|
||||
</h2>
|
||||
<div class="p-6 grid grid-cols-4 gap-4 text-center text-neutral-100">
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="
|
||||
background-image: url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Xumi.png');
|
||||
"
|
||||
>
|
||||
<img
|
||||
class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Xumi.png"
|
||||
/>
|
||||
<div class="text-sm w-full truncate">须弥</div>
|
||||
<div class="text-xs">28.0%</div>
|
||||
<div class="pointer-bar">
|
||||
<div class="pointer-progress-bar" style="width: 28%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="
|
||||
background-image: url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_ChasmsMaw.png');
|
||||
"
|
||||
>
|
||||
<img
|
||||
class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_ChasmsMaw.png"
|
||||
/>
|
||||
<div class="text-sm w-full truncate">层岩巨渊·地下矿区</div>
|
||||
<div class="text-xs">98.7%</div>
|
||||
<div class="pointer-bar">
|
||||
<div class="pointer-progress-bar" style="width: 98.7%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="
|
||||
background-image: url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_ChasmsMaw.png');
|
||||
"
|
||||
>
|
||||
<img
|
||||
class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_ChasmsMaw.png"
|
||||
/>
|
||||
<div class="text-sm w-full truncate">层岩巨渊</div>
|
||||
<div class="text-xs">92.9%</div>
|
||||
<div class="pointer-bar">
|
||||
<div class="pointer-progress-bar" style="width: 92.9%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="
|
||||
background-image: url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Enkanomiya.png');
|
||||
"
|
||||
>
|
||||
<img
|
||||
class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Enkanomiya.png"
|
||||
/>
|
||||
<div class="text-sm w-full truncate">渊下宫</div>
|
||||
<div class="text-xs">98.3%</div>
|
||||
<div class="pointer-bar">
|
||||
<div class="pointer-progress-bar" style="width: 98.3%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="
|
||||
background-image: url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Daoqi.png');
|
||||
"
|
||||
>
|
||||
<img
|
||||
class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Daoqi.png"
|
||||
/>
|
||||
<div class="text-sm w-full truncate">稻妻</div>
|
||||
<div class="text-xs">100.0%</div>
|
||||
<div class="pointer-bar">
|
||||
<div class="pointer-progress-bar" style="width: 100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="
|
||||
background-image: url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Dragonspine.png');
|
||||
"
|
||||
>
|
||||
<img
|
||||
class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Dragonspine.png"
|
||||
/>
|
||||
<div class="text-sm w-full truncate">龙脊雪山</div>
|
||||
<div class="text-xs">83.6%</div>
|
||||
<div class="pointer-bar">
|
||||
<div class="pointer-progress-bar" style="width: 83.6%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="
|
||||
background-image: url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Liyue.png');
|
||||
"
|
||||
>
|
||||
<img
|
||||
class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Liyue.png"
|
||||
/>
|
||||
<div class="text-sm w-full truncate">璃月</div>
|
||||
<div class="text-xs">95.9%</div>
|
||||
<div class="pointer-bar">
|
||||
<div class="pointer-progress-bar" style="width: 95.9%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="
|
||||
background-image: url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Mengde.png');
|
||||
"
|
||||
>
|
||||
<img
|
||||
class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Mengde.png"
|
||||
/>
|
||||
<div class="text-sm w-full truncate">蒙德</div>
|
||||
<div class="text-xs">100.0%</div>
|
||||
<div class="pointer-bar">
|
||||
<div class="pointer-progress-bar" style="width: 100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="about text-center leading-8 text-xs opacity-50">
|
||||
所有数据会有一小时延迟 以游戏内为准 此处仅供参考
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,49 +0,0 @@
|
||||
body {
|
||||
background-color: #f5f6fb;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-image: url(../background/mondstadt.png);
|
||||
box-shadow: 0 0 8px rgb(123 242 248 / 50%);
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: #fdfdf3;
|
||||
box-shadow: 0 0 8px rgb(123 242 248 / 50%);
|
||||
}
|
||||
|
||||
.box-title {
|
||||
background-color: #43849abb;
|
||||
--tw-ring-color: #43849a;
|
||||
}
|
||||
|
||||
.pointer-bar {
|
||||
width: 95%;
|
||||
height: 8px;
|
||||
display: inline-block;
|
||||
background-color: rgb(0, 0, 0, 0.2);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.pointer-progress-bar {
|
||||
border-radius: 0.25rem;
|
||||
height: 100%;
|
||||
background: #fff6e2;
|
||||
}
|
||||
|
||||
.name {
|
||||
background: linear-gradient(to bottom, #66bbee, #5ddddd, #55dddd);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.uid {
|
||||
color: #fff;
|
||||
background: linear-gradient(to right, rgb(0, 0, 0, 0), #5ddddd, rgb(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.about {
|
||||
background-color: #e0dad3;
|
||||
color: #8a4d30;
|
||||
}
|
@ -1,261 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Title</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<link href="./mondstadt.css" rel="stylesheet" />
|
||||
<link type="text/css" href="../../../styles/public.css" rel="stylesheet" />
|
||||
<script src="../../../js/tailwindcss-3.1.8.js"></script>
|
||||
</head>
|
||||
<body class="text-neutral-600">
|
||||
<div class="mx-auto max-w-[600px] py-8">
|
||||
<div class="header p-6 flex mb-8 rounded-xl bg-cover justify-between">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<h1 class="text-4xl italic name mb-2 px-2">
|
||||
小何
|
||||
<span class="text-lg">lv.58</span>
|
||||
</h1>
|
||||
<h1 class="italic uid px-10">UID - 125324176</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box pt-4 rounded-xl overflow-hidden">
|
||||
<div>
|
||||
<h2 class="box-title text-center text-xl ring text-neutral-100 p-1">
|
||||
数据总览
|
||||
</h2>
|
||||
<div class="p-6 grid grid-cols-4 gap-4 text-center">
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">491</div>
|
||||
<div class="text-neutral-400">活跃天数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">536</div>
|
||||
<div class="text-neutral-400">成就达成数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">38</div>
|
||||
<div class="text-neutral-400">获取角色数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">12-3</div>
|
||||
<div class="text-neutral-400">深境螺旋</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">227</div>
|
||||
<div class="text-neutral-400">解锁传送点</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">41</div>
|
||||
<div class="text-neutral-400">解锁秘境</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">58</div>
|
||||
<div class="text-neutral-400">奇馈宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">127</div>
|
||||
<div class="text-neutral-400">华丽宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">316</div>
|
||||
<div class="text-neutral-400">珍贵宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">1180</div>
|
||||
<div class="text-neutral-400">精致宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">1591</div>
|
||||
<div class="text-neutral-400">普通宝箱数</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">65</div>
|
||||
<div class="text-neutral-400">风神瞳</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">131</div>
|
||||
<div class="text-neutral-400">岩神瞳</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">180</div>
|
||||
<div class="text-neutral-400">雷神瞳</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="text-xl">79</div>
|
||||
<div class="text-neutral-400">草神瞳</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="box-title text-center text-xl ring text-neutral-100 p-1">
|
||||
世界探索
|
||||
</h2>
|
||||
<div class="p-6 grid grid-cols-4 gap-4 text-center text-neutral-100">
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="background-image:
|
||||
url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Xumi.png');"
|
||||
>
|
||||
<img class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Xumi.png" />
|
||||
<div class="text-sm w-full truncate">须弥</div>
|
||||
<div class="text-xs">26.0%</div>
|
||||
<div class="pointer-bar">
|
||||
<div
|
||||
class="pointer-progress-bar"
|
||||
style="width: 26.0%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="background-image:
|
||||
url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_ChasmsMaw.png');"
|
||||
>
|
||||
<img class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_ChasmsMaw.png" />
|
||||
<div class="text-sm w-full truncate">层岩巨渊·地下矿区</div>
|
||||
<div class="text-xs">98.7%</div>
|
||||
<div class="pointer-bar">
|
||||
<div
|
||||
class="pointer-progress-bar"
|
||||
style="width: 98.7%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="background-image:
|
||||
url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_ChasmsMaw.png');"
|
||||
>
|
||||
<img class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_ChasmsMaw.png" />
|
||||
<div class="text-sm w-full truncate">层岩巨渊</div>
|
||||
<div class="text-xs">92.9%</div>
|
||||
<div class="pointer-bar">
|
||||
<div
|
||||
class="pointer-progress-bar"
|
||||
style="width: 92.9%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="background-image:
|
||||
url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Enkanomiya.png');"
|
||||
>
|
||||
<img class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Enkanomiya.png" />
|
||||
<div class="text-sm w-full truncate">渊下宫</div>
|
||||
<div class="text-xs">98.3%</div>
|
||||
<div class="pointer-bar">
|
||||
<div
|
||||
class="pointer-progress-bar"
|
||||
style="width: 98.3%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="background-image:
|
||||
url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Daoqi.png');"
|
||||
>
|
||||
<img class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Daoqi.png" />
|
||||
<div class="text-sm w-full truncate">稻妻</div>
|
||||
<div class="text-xs">100.0%</div>
|
||||
<div class="pointer-bar">
|
||||
<div
|
||||
class="pointer-progress-bar"
|
||||
style="width: 100.0%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="background-image:
|
||||
url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Dragonspine.png');"
|
||||
>
|
||||
<img class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Dragonspine.png" />
|
||||
<div class="text-sm w-full truncate">龙脊雪山</div>
|
||||
<div class="text-xs">83.6%</div>
|
||||
<div class="pointer-bar">
|
||||
<div
|
||||
class="pointer-progress-bar"
|
||||
style="width: 83.6%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="background-image:
|
||||
url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Liyue.png');"
|
||||
>
|
||||
<img class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Liyue.png" />
|
||||
<div class="text-sm w-full truncate">璃月</div>
|
||||
<div class="text-xs">95.9%</div>
|
||||
<div class="pointer-bar">
|
||||
<div
|
||||
class="pointer-progress-bar"
|
||||
style="width: 95.9%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="w-full flex flex-col items-center rounded-lg p-2 space-y-1 bg-cover"
|
||||
style="background-image:
|
||||
url('https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterCover_Mengde.png');"
|
||||
>
|
||||
<img class="w-3/5"
|
||||
src="https://upload-bbs.mihoyo.com/game_record/genshin/city_icon/UI_ChapterIcon_Mengde.png" />
|
||||
<div class="text-sm w-full truncate">蒙德</div>
|
||||
<div class="text-xs">100.0%</div>
|
||||
<div class="pointer-bar">
|
||||
<div
|
||||
class="pointer-progress-bar"
|
||||
style="width: 100.0%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="about text-center leading-8 text-xs opacity-50">
|
||||
所有数据会有一小时延迟 以游戏内为准 此处仅供参考
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
resources/zzz/stats/items/star.png
Normal file
After Width: | Height: | Size: 860 B |
@ -1,62 +0,0 @@
|
||||
:root {
|
||||
--primary: #ffeabd;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f5f6fb;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-image: url(./background/liyue.png);
|
||||
box-shadow: 0 0 16px rgb(255 233 144 / 50%);
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: #9c433d;
|
||||
box-shadow: 0 0 16px rgb(255 233 144 / 50%);
|
||||
}
|
||||
|
||||
.box-title {
|
||||
background-color: rgb(255, 200, 122, 0.1);
|
||||
--tw-ring-color: #ff9966;
|
||||
}
|
||||
|
||||
.pointer-bar {
|
||||
width: 95%;
|
||||
height: 8px;
|
||||
display: inline-block;
|
||||
background-color: rgb(0, 0, 0, 0.1);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.pointer-progress-bar {
|
||||
border-radius: 0.25rem;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, #f5efcd, #f8eabd, #ffdf90);
|
||||
}
|
||||
|
||||
.name {
|
||||
background: linear-gradient(to bottom, #ffffff, #ffeabd, #ffdf90);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.uid {
|
||||
color: var(--primary);
|
||||
background: linear-gradient(to right, rgb(0, 0, 0, 0), #cc6666, rgb(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.about {
|
||||
background-color: #e0dad3;
|
||||
color: #8a4d30;
|
||||
}
|
||||
|
||||
.box-stats {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.box-stats-label {
|
||||
color: var(--primary) !important;
|
||||
opacity: 0.65;
|
||||
}
|
56
resources/zzz/stats/main.css
Normal file
@ -0,0 +1,56 @@
|
||||
body {
|
||||
background-color: #f5f6fb;
|
||||
}
|
||||
|
||||
.header {
|
||||
/*background-image: url(../../bot/help/background/header.png);*/
|
||||
box-shadow: 0 0 8px #72a2ae79;
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: #f4f2e4;
|
||||
box-shadow: 0 0 8px #72a2ae79;
|
||||
}
|
||||
|
||||
.pointer-bar {
|
||||
width: 95%;
|
||||
height: 8px;
|
||||
display: inline-block;
|
||||
background-color: rgb(0, 0, 0, 0.2);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.pointer-progress-bar {
|
||||
border-radius: 0.25rem;
|
||||
height: 100%;
|
||||
background: #fff6e2;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: #ffffee;
|
||||
text-shadow: 0 0.08em 0.1em #00000093, 0 0.1em 0.3em rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.uid {
|
||||
color: #fff;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
rgb(0, 0, 0, 0),
|
||||
#3f7587 25%,
|
||||
#3f7587 75%,
|
||||
rgb(0, 0, 0, 0)
|
||||
);
|
||||
}
|
||||
|
||||
.about {
|
||||
background-color: #e0dad3;
|
||||
color: #8a4d30;
|
||||
}
|
||||
|
||||
.frame-pic {
|
||||
border-color: #fdfdf356;
|
||||
}
|
||||
|
||||
.frame {
|
||||
border-color: #cdbea8;
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
body {
|
||||
background-color: #f5f6fb;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-image: url(background/mondstadt.png);
|
||||
box-shadow: 0 0 8px rgb(123 242 248 / 50%);
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: #fdfdf3;
|
||||
box-shadow: 0 0 8px rgb(123 242 248 / 50%);
|
||||
}
|
||||
|
||||
.box-title {
|
||||
background-color: #43849abb;
|
||||
--tw-ring-color: #43849a;
|
||||
}
|
||||
|
||||
.pointer-bar {
|
||||
width: 95%;
|
||||
height: 8px;
|
||||
display: inline-block;
|
||||
background-color: rgb(0, 0, 0, 0.2);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.pointer-progress-bar {
|
||||
border-radius: 0.25rem;
|
||||
height: 100%;
|
||||
background: #fff6e2;
|
||||
}
|
||||
|
||||
.name {
|
||||
background: linear-gradient(to bottom, #66bbee, #5ddddd, #55dddd);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.uid {
|
||||
color: #fff;
|
||||
background: linear-gradient(to right, rgb(0, 0, 0, 0), #5ddddd, rgb(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.about {
|
||||
background-color: #e0dad3;
|
||||
color: #8a4d30;
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
body {
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
|
||||
#container {
|
||||
|
||||
}
|
||||
|
||||
.account-center-header {
|
||||
padding: 10px 8px;
|
||||
background-color: rgba(225, 225, 225, 0.5);
|
||||
/*background-image: url("./0.jpg");
|
||||
background-size: cover;*/
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.user-info-1 {
|
||||
padding: 10px 8px;
|
||||
background-color: rgba(225, 225, 225, 0.5);
|
||||
}
|
||||
|
||||
.world-exploration {
|
||||
padding: 10px 8px;
|
||||
background-color: rgba(225, 225, 225, 0.5);
|
||||
}
|
||||
|
||||
.teapot {
|
||||
padding: 10px 8px;
|
||||
background-color: rgba(225, 225, 225, 0.5);
|
||||
}
|
||||
|
||||
.account-center-header-avatar {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.teapot-info-icon {
|
||||
height: 96px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.teapot-info-img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.world-exploration-info {
|
||||
border: 2px solid rgb(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.world-exploration-info-icon {
|
||||
height: 96px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.world-exploration-info-img {
|
||||
filter: brightness(0);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.world-exploration-info-hr-1 {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.world-exploration-info-pointer-bar-body {
|
||||
width: 95%;
|
||||
height: 4px;
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
background-color: rgb(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.world-exploration-info-pointer-progress-bar {
|
||||
height: 100%;
|
||||
background-color: #000000;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.background-color {
|
||||
background-color: rgb(225, 225, 225, 0.75);
|
||||
}
|
||||
|
||||
.teapot-info-name {
|
||||
background-color: rgba(225, 225, 225, 0.5);
|
||||
}
|
@ -1,183 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
|
||||
<title>Title</title>
|
||||
<link type="text/css" href="../../styles/tailwind.min.css" rel="stylesheet">
|
||||
<link type="text/css" href="./info.css" rel="stylesheet">
|
||||
<link type="text/css" href="../../styles/public.css" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
background-image: url({{background_image}});
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="background-color">
|
||||
<div class="container mx-auto px-4 py-4" id="container">
|
||||
<div class="account-center-header flex rounded-xl mb-4">
|
||||
<div class="account-center-user p-4 flex-grow">
|
||||
<div class="account-center-user-title pb-4">
|
||||
<span class="account-center-user-name text-4xl">{{name}}</span>
|
||||
</div>
|
||||
<div class="account-center-user-uid pb-4">
|
||||
<span class="account-center-user-name text-2xl">{{uid}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-center-header-avatar mr-8 mt-2 flex-shrink">
|
||||
<img class="user-avatar rounded-full " src="{{user_avatar}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-info-1 rounded-xl text-center mb-4">
|
||||
<div class="base-user-info-1 flex flex-wrap text-center">
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">活跃天数</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{action_day_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">成就达成数</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{achievement_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">获取角色数</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{avatar_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">深境螺旋</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{spiral_abyss}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">解锁传送点</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{way_point_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">解锁秘境</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{domain_number}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="base-user-info-2 flex flex-wrap text-center">
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">奇馈宝箱数</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{magic_chest_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">华丽宝箱数</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{luxurious_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">珍贵宝箱数</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{precious_chest_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">精致宝箱数</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{exquisite_chest_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">普通宝箱数</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{common_chest_number}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="base-user-info-1 flex flex-wrap text-center">
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">风神瞳</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{anemoculus_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">岩神瞳</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{geoculus_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">雷神瞳</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{electroculus_number}}</div>
|
||||
</div>
|
||||
<div class="user-base-info ml-4 p-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-xl p-1">草神瞳</div>
|
||||
<div class="user-base-info-value text-4xl p-1 ">{{dendroculi_number}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-info-2 flex ">
|
||||
<div class="world-exploration rounded-xl float-left flex-1 mr-4">
|
||||
<div class="world-exploration-title pl-2 ">
|
||||
<h1 class="text-2xl">世界探索</h1>
|
||||
</div>
|
||||
<div class="world-exploration-list pt-2 px-4">
|
||||
{% for world_exploration in world_exploration_list %}
|
||||
<div class="world-exploration-info mt-2 flex rounded-xl">
|
||||
<div class="world-exploration-info-icon flex-shrink">
|
||||
<img class="world-exploration-info-img" src="{{world_exploration.icon}}">
|
||||
</div>
|
||||
<div class="world-exploration-info-info flex-grow pt-1 pl-2 pb-1">
|
||||
<div class="world-exploration-info-name pb-1">
|
||||
<p>{{world_exploration.name}}</p>
|
||||
</div>
|
||||
<div class="world-exploration-info-hr pb-1">
|
||||
<HR class="world-exploration-info-hr-1" color=#000000 style="height: 2px">
|
||||
</div>
|
||||
<div class="world-exploration-info-s">
|
||||
<p>探索度:{{world_exploration.exploration_percentage}}%</p>
|
||||
</div>
|
||||
<div class="world-exploration-info-pointer">
|
||||
<div class="world-exploration-info-pointer-bar-body">
|
||||
<div class="world-exploration-info-pointer-progress-bar"
|
||||
style="width: {{world_exploration.exploration_percentage}}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="world-exploration-info-offerings-list flex">
|
||||
{% for offering in world_exploration.offerings %}
|
||||
<div class="world-exploration-info-other-1 flex-1">
|
||||
<p>{{offering.data}}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="teapot rounded-xl float-right flex-1">
|
||||
<div class="teapot-title pl-2">
|
||||
<h1 class="text-2xl">尘歌壶</h1>
|
||||
</div>
|
||||
<div class="teapot-info-base flex rounded-xl text-center pt-2">
|
||||
<div class="user-base-info py-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-base p-1">信任等阶级</div>
|
||||
<div class="user-base-info-value text-4xl p-1">{{teapot_level}}</div>
|
||||
</div>
|
||||
<div class="user-base-info py-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-base p-1">最高洞天仙力</div>
|
||||
<div class="user-base-info-value text-4xl p-1">{{teapot_comfort_num}}</div>
|
||||
</div>
|
||||
<div class="user-base-info py-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-base p-1">获得摆设数</div>
|
||||
<div class="user-base-info-value text-4xl p-1">{{teapot_item_num}}</div>
|
||||
</div>
|
||||
<div class="user-base-info py-2 rounded-xl flex-1">
|
||||
<div class="user-base-info-title-name text-base p-1">历史访问数</div>
|
||||
<div class="user-base-info-value text-4xl p-1">{{teapot_visit_num}}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="teapot-info-title text-center pt-2">
|
||||
<h1 class="text-base">已解锁的洞天</h1>
|
||||
</div>
|
||||
<div class="teapot-info-list flex-col px-4">
|
||||
{% for teapot in teapot_list %}
|
||||
<div class="teapot-info pt-4">
|
||||
<div class="teapot-info-icon rounded-xl relative">
|
||||
<div class="teapot-info-icon">
|
||||
<img class="teapot-info-img" src="{{teapot.icon}}">
|
||||
</div>
|
||||
<div class="teapot-info-name absolute right-0 top-0 px-2 text-base rounded-bl-lg">
|
||||
{{teapot.name}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
73
resources/zzz/stats/stats.html
Normal file
@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Title</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<link href="./{{style}}.css" rel="stylesheet"/>
|
||||
<link type="text/css" href="../../styles/public.css" rel="stylesheet"/>
|
||||
<script src="../../js/tailwindcss-3.1.8.js"></script>
|
||||
</head>
|
||||
<body class="text-neutral-600">
|
||||
<div class="mx-auto max-w-[600px] py-8">
|
||||
<div class="header p-2 rounded-xl bg-cover mb-6" style='background-image: url("{{ background }}")'>
|
||||
<div
|
||||
class="frame-pic p-4 flex items-center rounded-lg border-solid border-2"
|
||||
>
|
||||
<img class="w-16 h-16 rounded-full mr-4" src="{{ avatar }}" alt="Avatar">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<h1 class="name text-4xl italic mb-2 px-2 text-shadow">
|
||||
{{ nickname }}
|
||||
</h1>
|
||||
<h1 class="italic uid px-10">UID - {{ uid }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box p-2 rounded-xl overflow-hidden">
|
||||
<div class="frame rounded-lg border-solid border-2">
|
||||
<h2
|
||||
class="title font-semibold pt-4 text-center text-xl text-neutral-700 p-1"
|
||||
>
|
||||
<img src="./items/star.png" class="inline-block w-4"/>
|
||||
数据总览
|
||||
</h2>
|
||||
<div class="p-6 grid grid-cols-4 gap-4 text-center">
|
||||
{% for label, key in stats_labels %}
|
||||
<div class="">
|
||||
{% set value = stats[key] %}
|
||||
{% if value == "" %}
|
||||
{% set value = "-" %}
|
||||
{% endif %}
|
||||
<div class="text-xl box-stats">{{ value }}</div>
|
||||
<div class="text-neutral-400 box-stats-label">{{ label }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if rogue %}
|
||||
{% for label, key in rogue_labels %}
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">{{ rogue[key] }}</div>
|
||||
<div class="text-neutral-400 box-stats-label">{{ label }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if ledger %}
|
||||
{% for label, key in ledger_labels %}
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">{{ ledger[key] }}</div>
|
||||
<div class="text-neutral-400 box-stats-label">{{ label }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="about text-center leading-8 text-xs opacity-50">
|
||||
所有数据会有一小时延迟 以游戏内为准 此处仅供参考
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,45 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Title</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<link href="./{{style}}.css" rel="stylesheet" />
|
||||
<link type="text/css" href="../../styles/public.css" rel="stylesheet" />
|
||||
<script src="../../js/tailwindcss-3.1.8.js"></script>
|
||||
</head>
|
||||
<body class="text-neutral-600">
|
||||
<div class="mx-auto max-w-[600px] py-8">
|
||||
<div class="header p-6 flex mb-8 rounded-xl bg-cover justify-between">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<h1 class="text-4xl italic name mb-2 px-2">
|
||||
{{ nickname }}
|
||||
</h1>
|
||||
<h1 class="italic uid px-10">UID - {{ uid }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box pt-4 rounded-xl overflow-hidden">
|
||||
<div>
|
||||
<h2 class="box-title text-center text-xl ring text-neutral-100 p-1">
|
||||
数据总览
|
||||
</h2>
|
||||
<div class="p-6 grid grid-cols-4 gap-4 text-center">
|
||||
{% for label, key in stats_labels %}
|
||||
<div class="">
|
||||
<div class="text-xl box-stats">{{ stats[key] }}</div>
|
||||
<div class="text-neutral-400 box-stats-label">{{ label }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="about text-center leading-8 text-xs opacity-50">
|
||||
所有数据会有一小时延迟 以游戏内为准 此处仅供参考
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|