From c53de16302f2cc99e65266ac9ed8d5e71f6eebc9 Mon Sep 17 00:00:00 2001 From: xtaodada Date: Tue, 23 Apr 2024 21:04:17 +0800 Subject: [PATCH] :rewind: Revert ":bug: Fix asset service download icon" This reverts commit 4151b80c0b4ecc3b0bdbdcf01876ee23e85c66b6. --- core/dependence/assets.py | 82 ++++++++++++- metadata/scripts/honey.py | 197 ++++++++++++++++++++++++++++++ modules/wiki/base.py | 2 +- plugins/admin/refresh_metadata.py | 3 + plugins/genshin/daily/material.py | 3 +- utils/const/_url.py | 2 +- 6 files changed, 285 insertions(+), 4 deletions(-) create mode 100644 metadata/scripts/honey.py diff --git a/core/dependence/assets.py b/core/dependence/assets.py index fed55f2f..37254e2e 100644 --- a/core/dependence/assets.py +++ b/core/dependence/assets.py @@ -20,9 +20,11 @@ from typing_extensions import Self from core.base_service import BaseService from core.config import config -from metadata.genshin import AVATAR_DATA, MATERIAL_DATA, NAMECARD_DATA, WEAPON_DATA +from metadata.genshin import AVATAR_DATA, HONEY_DATA, MATERIAL_DATA, NAMECARD_DATA, WEAPON_DATA +from metadata.scripts.honey import update_honey_metadata from metadata.scripts.metadatas import update_metadata_from_ambr, update_metadata_from_github from metadata.shortname import roleToId, weaponToId +from modules.wiki.base import HONEY_HOST from utils.const import AMBR_HOST, ENKA_HOST, PROJECT_ROOT from utils.log import logger from utils.typedefs import StrOrInt, StrOrURL @@ -74,6 +76,11 @@ class _AssetsService(ABC): def game_name(self) -> str: """游戏数据中的名称""" + @cached_property + def honey_id(self) -> str: + """当前资源在 Honey Impact 所对应的 ID""" + return HONEY_DATA[self.type].get(str(self.id), [""])[0] + @property def path(self) -> Path: """当前资源的文件夹""" @@ -156,6 +163,12 @@ class _AssetsService(ABC): """从 enke.network 上获取目标链接""" yield None + async def _get_from_honey(self, item: str) -> AsyncIterator[str | None]: + """从 honey 上获取目标链接""" + if (honey_name := self.honey_name_map.get(item, None)) is not None: + yield HONEY_HOST.join(f"img/{honey_name}.png") + yield HONEY_HOST.join(f"img/{honey_name}.webp") + async def _download_url_generator(self, item: str) -> AsyncIterator[str]: # 获取当前 `AssetsService` 的所有爬虫 for func in map(lambda x: getattr(self, x), sorted(filter(lambda x: x.startswith("_get_from_"), dir(self)))): @@ -209,6 +222,11 @@ class _AssetsService(ABC): def game_name_map(self) -> dict[str, str]: """游戏中的图标名""" + @abstractmethod + @cached_property + def honey_name_map(self) -> dict[str, str]: + """来自honey的图标名""" + class _AvatarAssets(_AssetsService): enka: EnkaCharacterAsset | None @@ -236,6 +254,10 @@ class _AvatarAssets(_AssetsService): icon = avatar["icon"] return re.findall(r"UI_AvatarIcon_(.*)", icon)[0] + @cached_property + def honey_id(self) -> str: + return HONEY_DATA["avatar"].get(str(self.id), "")[0] + @cached_property def enka(self) -> Optional[EnkaCharacterAsset]: api = getattr(self, "_enka_api", None) @@ -268,6 +290,15 @@ class _AvatarAssets(_AssetsService): if (item_id := self.game_name_map.get(item)) is not None: yield str(ENKA_HOST.join(f"ui/{item_id}.png")) + @cached_property + def honey_name_map(self) -> dict[str, str]: + return { + "icon": f"{self.honey_id}_icon", + "side": f"{self.honey_id}_side_icon", + "gacha": f"{self.honey_id}_gacha_splash", + "gacha_card": f"{self.honey_id}_gacha_card", + } + @cached_property def game_name_map(self) -> dict[str, str]: return { @@ -297,6 +328,10 @@ class _WeaponAssets(_AssetsService): "gacha": f"UI_Gacha_EquipIcon_{self.game_name}", } + @cached_property + def honey_id(self) -> str: + return f"i_n{self.id}" + def __call__(self, target: StrOrInt) -> Self: temp = target result = _WeaponAssets(self.client) @@ -315,6 +350,14 @@ class _WeaponAssets(_AssetsService): if item in self.game_name_map: yield str(ENKA_HOST.join(f"ui/{self.game_name_map.get(item)}.png")) + @cached_property + def honey_name_map(self) -> dict[str, str]: + return { + "icon": f"{self.honey_id}", + "awaken": f"{self.honey_id}_awaken_icon", + "gacha": f"{self.honey_id}_gacha_icon", + } + class _MaterialAssets(_AssetsService): @cached_property @@ -325,6 +368,10 @@ class _MaterialAssets(_AssetsService): def game_name_map(self) -> dict[str, str]: return {"icon": f"UI_ItemIcon_{self.game_name}"} + @cached_property + def honey_name_map(self) -> dict[str, str]: + return {"icon": self.honey_id} + def __call__(self, target: StrOrInt) -> Self: temp = target result = _MaterialAssets(self.client) @@ -342,6 +389,10 @@ class _MaterialAssets(_AssetsService): if item == "icon": yield str(AMBR_HOST.join(f"assets/UI/{self.game_name_map.get(item)}.png")) + async def _get_from_honey(self, item: str) -> AsyncIterator[str | None]: + yield HONEY_HOST.join(f"/img/{self.honey_name_map.get(item)}.png") + yield HONEY_HOST.join(f"/img/{self.honey_name_map.get(item)}.webp") + class _ArtifactAssets(_AssetsService): flower: ICON_TYPE @@ -359,6 +410,10 @@ class _ArtifactAssets(_AssetsService): circlet: ICON_TYPE """理之冠""" + @cached_property + def honey_id(self) -> str: + return HONEY_DATA["artifact"][str(self.id)][0] + @cached_property def game_name(self) -> str: return f"UI_RelicIcon_{self.id}" @@ -382,6 +437,18 @@ class _ArtifactAssets(_AssetsService): "circlet": f"UI_RelicIcon_{self.id}_3", } + @cached_property + def honey_name_map(self) -> dict[str, str]: + first_id = int(re.findall(r"\d+", HONEY_DATA["artifact"][str(self.id)][-1])[0]) + return { + "icon": f"i_n{first_id + 30}", + "flower": f"i_n{first_id + 30}", + "plume": f"i_n{first_id + 10}", + "sands": f"i_n{first_id + 40}", + "goblet": f"i_n{first_id}", + "circlet": f"i_n{first_id + 20}", + } + class _NamecardAssets(_AssetsService): enka: EnkaCharacterAsset | None @@ -395,6 +462,10 @@ class _NamecardAssets(_AssetsService): NAME_CARD_DEFAULT: int = 210189 """默认名片 ID""" + @cached_property + def honey_id(self) -> str: + return HONEY_DATA["namecard"][str(self.id)][0] + @cached_property def game_name(self) -> str: return NAMECARD_DATA[str(self.id)]["icon"] @@ -439,6 +510,14 @@ class _NamecardAssets(_AssetsService): "profile": NAMECARD_DATA[str(self.id)]["profile"], } + @cached_property + def honey_name_map(self) -> dict[str, str]: + return { + "icon": self.honey_id, + "navbar": f"{self.honey_id}_back", + "profile": f"{self.honey_id}_profile", + } + class AssetsService(BaseService.Dependence): """asset服务 @@ -475,6 +554,7 @@ class AssetsService(BaseService.Dependence): # todo 这3个任务同时异步下载 await update_metadata_from_github(False) await update_metadata_from_ambr(False) + await update_honey_metadata(False) logger.info("刷新元数据成功") diff --git a/metadata/scripts/honey.py b/metadata/scripts/honey.py new file mode 100644 index 00000000..9b908cdd --- /dev/null +++ b/metadata/scripts/honey.py @@ -0,0 +1,197 @@ +from __future__ import annotations + +import asyncio +import re +from typing import Dict, List, Optional + +from aiofiles import open as async_open +from httpx import AsyncClient, HTTPError, Response + +from modules.wiki.base import HONEY_HOST +from utils.const import PROJECT_ROOT +from utils.log import logger +from utils.typedefs import StrOrInt + +try: + import ujson as jsonlib +except ImportError: + import json as jsonlib + +__all__ = [ + "get_avatar_data", + "get_artifact_data", + "get_material_data", + "get_namecard_data", + "get_weapon_data", + "update_honey_metadata", +] + +DATA_TYPE = Dict[StrOrInt, List[str]] +FULL_DATA_TYPE = Dict[str, DATA_TYPE] + +client = AsyncClient() + + +async def request(url: str, retry: int = 5) -> Optional[Response]: + for time in range(retry): + try: + return await client.get(url) + except HTTPError: + if time != retry - 1: + await asyncio.sleep(1) + continue + return None + except Exception as e: + raise e + + +async def get_avatar_data() -> DATA_TYPE: + result = {} + url = "https://gensh.honeyhunterworld.com/fam_chars/?lang=CHS" + response = await request(url) + chaos_data = re.findall(r"sortable_data\.push\((.*?)\);\s*sortable_cur_page", response.text)[0] + json_data = jsonlib.loads(chaos_data) # 转为 json + for data in json_data: + cid = int("10000" + re.findall(r"\d+", data[1])[0]) + honey_id = re.findall(r"/(.*?)/", data[1])[0] + name = re.findall(r">(.*)<", data[1])[0] + rarity = int(re.findall(r">(\d)<", data[2])[0]) + result[cid] = [honey_id, name, rarity] + return result + + +async def get_weapon_data() -> DATA_TYPE: + from modules.wiki.other import WeaponType + + result = {} + urls = [HONEY_HOST.join(f"fam_{i.lower()}/?lang=CHS") for i in WeaponType.__members__] + for url in urls: + response = await request(url) + chaos_data = re.findall(r"sortable_data\.push\((.*?)\);\s*sortable_cur_page", response.text)[0] + json_data = jsonlib.loads(chaos_data) # 转为 json + for data in json_data: + name = re.findall(r">(.*)<", data[1])[0] + if name in ["「一心传」名刀", "石英大剑", "琥珀玥", "黑檀弓"]: # 跳过特殊的武器 + continue + wid = int(re.findall(r"\d+", data[1])[0]) + honey_id = re.findall(r"/(.*?)/", data[1])[0] + rarity = int(re.findall(r">(\d)<", data[2])[0]) + result[wid] = [honey_id, name, rarity] + return result + + +async def get_material_data() -> DATA_TYPE: + result = {} + + weapon = [HONEY_HOST.join(f"fam_wep_{i}/?lang=CHS") for i in ["primary", "secondary", "common"]] + talent = [HONEY_HOST.join(f"fam_talent_{i}/?lang=CHS") for i in ["book", "boss", "common", "reward"]] + namecard = [HONEY_HOST.join("fam_nameplate/?lang=CHS")] + urls = weapon + talent + namecard + + response = await request("https://api.ambr.top/v2/chs/material") + ambr_data = jsonlib.loads(response.text)["data"]["items"] + + for url in urls: + response = await request(url) + chaos_data = re.findall(r"sortable_data\.push\((.*?)\);\s*sortable_cur_page", response.text)[0] + json_data = jsonlib.loads(chaos_data) # 转为 json + for data in json_data: + honey_id = re.findall(r"/(.*?)/", data[1])[0] + name = re.findall(r">(.*)<", data[1])[0] + rarity = int(re.findall(r">(\d)<", data[2])[0]) + mid = None + for mid, item in ambr_data.items(): + if name == item["name"]: + break + mid = int(mid) or int(re.findall(r"\d+", data[1])[0]) + result[mid] = [honey_id, name, rarity] + return result + + +async def get_artifact_data() -> DATA_TYPE: + async def get_first_id(_link) -> str: + _response = await request(_link) + _chaos_data = re.findall(r"sortable_data\.push\((.*?)\);\s*sortable_cur_page", _response.text)[0] + _json_data = jsonlib.loads(_chaos_data) + return re.findall(r"/(.*?)/", _json_data[-1][1])[0] + + result = {} + url = "https://gensh.honeyhunterworld.com/fam_art_set/?lang=CHS" + + response = await request("https://api.ambr.top/v2/chs/reliquary") + ambr_data = jsonlib.loads(response.text)["data"]["items"] + + response = await request(url) + chaos_data = re.findall(r"sortable_data\.push\((.*?)\);\s*sortable_cur_page", response.text)[0] + json_data = jsonlib.loads(chaos_data) # 转为 json + for data in json_data: + honey_id = re.findall(r"/(.*?)/", data[1])[0] + name = re.findall(r"alt=\"(.*?)\"", data[0])[0] + link = HONEY_HOST.join(re.findall(r'href="(.*?)"', data[0])[0]) + first_id = await get_first_id(link) + aid = None + for aid, item in ambr_data.items(): + if name == item["name"]: + break + aid = aid or re.findall(r"\d+", data[1])[0] + result[aid] = [honey_id, name, first_id] + + return result + + +async def get_namecard_data() -> DATA_TYPE: + from metadata.genshin import NAMECARD_DATA + + if not NAMECARD_DATA: + # noinspection PyProtectedMember + from metadata.genshin import Data + from metadata.scripts.metadatas import update_metadata_from_github + + await update_metadata_from_github() + # noinspection PyPep8Naming + NAMECARD_DATA = Data("namecard") + url = HONEY_HOST.join("fam_nameplate/?lang=CHS") + result = {} + + response = await request(url) + chaos_data = re.findall(r"sortable_data\.push\((.*?)\);\s*sortable_cur_page", response.text)[0] + json_data = jsonlib.loads(chaos_data) + for data in json_data: + honey_id = re.findall(r"/(.*?)/", data[1])[0] + name = re.findall(r"alt=\"(.*?)\"", data[0])[0] + try: + nid = [key for key, value in NAMECARD_DATA.items() if value["name"] == name][0] + except IndexError: # 暂不支持 beta 的名片 + continue + rarity = int(re.findall(r">(\d)<", data[2])[0]) + result[nid] = [honey_id, name, rarity] + + return result + + +async def update_honey_metadata(overwrite: bool = True) -> FULL_DATA_TYPE | None: + path = PROJECT_ROOT.joinpath("metadata/data/honey.json") + if not overwrite and path.exists(): + return + avatar_data = await get_avatar_data() + logger.success("Avatar data is done.") + weapon_data = await get_weapon_data() + logger.success("Weapon data is done.") + material_data = await get_material_data() + logger.success("Material data is done.") + artifact_data = await get_artifact_data() + logger.success("Artifact data is done.") + namecard_data = await get_namecard_data() + logger.success("Namecard data is done.") + + result = { + "avatar": avatar_data, + "weapon": weapon_data, + "material": material_data, + "artifact": artifact_data, + "namecard": namecard_data, + } + path.parent.mkdir(parents=True, exist_ok=True) + async with async_open(path, mode="w", encoding="utf-8") as file: + await file.write(jsonlib.dumps(result, ensure_ascii=False, indent=4)) + return result diff --git a/modules/wiki/base.py b/modules/wiki/base.py index ecc8f1bc..133a5a43 100644 --- a/modules/wiki/base.py +++ b/modules/wiki/base.py @@ -21,7 +21,7 @@ except ImportError: __all__ = ["Model", "WikiModel", "HONEY_HOST"] -HONEY_HOST = URL("https://genshin.honeyhunterworld.com/") +HONEY_HOST = URL("https://gensh.honeyhunterworld.com/") class Model(PydanticBaseModel): diff --git a/plugins/admin/refresh_metadata.py b/plugins/admin/refresh_metadata.py index 240c74d4..557d28a8 100644 --- a/plugins/admin/refresh_metadata.py +++ b/plugins/admin/refresh_metadata.py @@ -2,6 +2,7 @@ from telegram import Update from telegram.ext import CallbackContext from core.plugin import Plugin, handler +from metadata.scripts.honey import update_honey_metadata from metadata.scripts.metadatas import update_metadata_from_ambr, update_metadata_from_github from metadata.scripts.paimon_moe import update_paimon_moe_zh from utils.log import logger @@ -22,4 +23,6 @@ class MetadataPlugin(Plugin): await update_paimon_moe_zh() logger.info("正在从 ambr 上获取元数据") await update_metadata_from_ambr() + logger.info("正在从 honey 上获取元数据") + await update_honey_metadata() await msg.edit_text("正在刷新元数据,请耐心等待...\n完成!") diff --git a/plugins/genshin/daily/material.py b/plugins/genshin/daily/material.py index 9067ff65..93381c0b 100644 --- a/plugins/genshin/daily/material.py +++ b/plugins/genshin/daily/material.py @@ -502,7 +502,7 @@ class DailyMaterial(Plugin): """刷新来自 honey impact 的每日素材表""" for attempts in range(1, retry + 1): try: - response = await self.client.get("https://genshin.honeyhunterworld.com/?lang=CHS") + response = await self.client.get("https://gensh.honeyhunterworld.com/?lang=CHS") response.raise_for_status() except (HTTPError, SSLZeroReturnError): await asyncio.sleep(1) @@ -517,6 +517,7 @@ class DailyMaterial(Plugin): content = self.everyday_materials.json(ensure_ascii=False, separators=(",", ":")) async with aiofiles.open(DATA_FILE_PATH, "w", encoding="utf-8") as file: await file.write(content) + logger.success("每日素材刷新成功") return async def _assemble_item_from_honey_data(self, item_type: str, item_id: str) -> Optional["ItemData"]: diff --git a/utils/const/_url.py b/utils/const/_url.py index ee5c279e..60e60542 100644 --- a/utils/const/_url.py +++ b/utils/const/_url.py @@ -2,7 +2,7 @@ from httpx import URL __all__ = ("HONEY_HOST", "ENKA_HOST", "AMBR_HOST", "CELESTIA_HOST") -HONEY_HOST = URL("https://genshin.honeyhunterworld.com/") +HONEY_HOST = URL("https://gensh.honeyhunterworld.com/") ENKA_HOST = URL("https://enka.network/") AMBR_HOST = URL("https://api.ambr.top/") CELESTIA_HOST = URL("https://www.projectcelestia.com/")