add method get_link for AssetsService

This commit is contained in:
Karako 2022-11-23 21:50:45 +08:00 committed by GitHub
parent 9135efdfc3
commit ad8a81c373
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 98 additions and 54 deletions

View File

@ -4,17 +4,18 @@ from __future__ import annotations
import asyncio import asyncio
import re import re
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from asyncio import Lock as AsyncLock
from functools import cached_property, partial from functools import cached_property, partial
from multiprocessing import RLock as Lock from multiprocessing import RLock as Lock
from pathlib import Path from pathlib import Path
from ssl import SSLZeroReturnError from ssl import SSLZeroReturnError
from typing import Awaitable, Callable, ClassVar, Dict, Optional, TYPE_CHECKING, TypeVar, Union from typing import AsyncIterator, Awaitable, Callable, ClassVar, Dict, Optional, TYPE_CHECKING, TypeVar, Union
from aiofiles import open as async_open from aiofiles import open as async_open
from aiofiles.os import remove as async_remove from aiofiles.os import remove as async_remove
from enkanetwork import Assets as EnkaAssets from enkanetwork import Assets as EnkaAssets
from enkanetwork.model.assets import CharacterAsset as EnkaCharacterAsset from enkanetwork.model.assets import CharacterAsset as EnkaCharacterAsset
from httpx import AsyncClient, HTTPError, URL from httpx import AsyncClient, HTTPError, HTTPStatusError, URL
from typing_extensions import Self from typing_extensions import Self
from core.service import Service from core.service import Service
@ -57,6 +58,8 @@ class _AssetsService(ABC):
icon_types: ClassVar[list[str]] icon_types: ClassVar[list[str]]
_client: Optional[AsyncClient] = None _client: Optional[AsyncClient] = None
_links: dict[str, str] = {}
_async_lock: AsyncLock = AsyncLock()
id: int id: int
type: str type: str
@ -131,33 +134,72 @@ class _AssetsService(ABC):
await file.write(response.content) # 保存图标 await file.write(response.content) # 保存图标
return path.resolve() return path.resolve()
async def _get_from_ambr(self, item: str) -> Path | None: # pylint: disable=W0613,R0201 async def _get_from_ambr(self, item: str) -> str | None: # pylint: disable=W0613,R0201
"""从 ambr.top 上爬取"""
return None return None
async def _get_from_enka(self, item: str) -> Path | None: # pylint: disable=W0613,R0201 async def _get_from_enka(self, item: str) -> str | None: # pylint: disable=W0613,R0201
"""从 enke.network 上爬取"""
return None return None
async def _get_from_honey(self, item: str) -> Path | None: async def _get_from_honey(self, item: str) -> str | None:
"""从 honey 获取图标""" """从 honey 上爬取"""
if (url := self.honey_name_map.get(item, None)) is not None: try:
# 先尝试下载 png 格式的图片 honey_name = self.honey_name_map.get(item, None)
path = self.path.joinpath(f"{item}.png") except IndexError:
if (result := await self._download(HONEY_HOST.join(f"img/{url}.png"), path)) is not None: return None
if honey_name is not None:
try:
result = HONEY_HOST.join(f"img/{honey_name}.png")
response = await self.client.get(result, follow_redirects=False)
response.raise_for_status()
except HTTPStatusError:
return None
if response.status_code == 200:
return result return result
path = self.path.joinpath(f"{item}.webp")
return await self._download(HONEY_HOST.join(f"img/{url}.webp"), path) return HONEY_HOST.join(f"img/{honey_name}.webp")
async def _download_url_generator(self, item: str) -> AsyncIterator[str]:
for func in map(lambda x: getattr(self, x), sorted(filter(lambda x: x.startswith("_get_from_"), dir(self)))):
if (url := await func(item)) is not None:
try:
response = await self.client.get(url := str(url))
response.raise_for_status()
if response.status_code == 200:
yield url
except HTTPStatusError:
continue
async def _get_download_url(self, item: str) -> str | None:
"""获取图标的下载链接"""
async for url in self._download_url_generator(item):
if url is not None:
return url
async def _get_img(self, overwrite: bool = False, *, item: str) -> Path | None: async def _get_img(self, overwrite: bool = False, *, item: str) -> Path | None:
"""获取图标""" """获取图标"""
path = next(filter(lambda x: x.stem == item, self.path.iterdir()), None) path = next(filter(lambda x: x.stem == item, self.path.iterdir()), None)
if not overwrite and path: if not overwrite and path: # 如果需要下载的图标存在且不覆盖( overwrite )
return path.resolve() return path.resolve()
if overwrite and path is not None and path.exists(): if path is not None and path.exists():
await async_remove(path) if overwrite: # 如果覆盖
# 依次从使用当前 assets class 中的爬虫下载图标,顺序为爬虫名的字母顺序 await async_remove(path) # 删除已存在的图标
for func in map(lambda x: getattr(self, x), sorted(filter(lambda x: x.startswith("_get_from_"), dir(self)))): else:
if (path := await func(item)) is not None:
return path return path
# 依次从使用当前 assets class 中的爬虫下载图标,顺序为爬虫名的字母顺序
async for url in self._download_url_generator(item):
if url is not None:
path = self.path.joinpath(f"{item}{Path(url).suffix}")
if (result := await self._download(url, path)) is not None:
return result
async def get_link(self, item: str) -> str | None:
async with self._async_lock:
if (result := self._links.get(item, None)) is None:
result = await self._get_download_url(item)
self._links.update({item: result})
return result
def __getattr__(self, item: str): def __getattr__(self, item: str):
"""魔法""" """魔法"""
@ -231,17 +273,15 @@ class _AvatarAssets(_AssetsService):
result._enka_api = self._enka_api result._enka_api = self._enka_api
return result return result
async def _get_from_ambr(self, item: str) -> Path | None: async def _get_from_ambr(self, item: str) -> str | None:
if item in {"icon", "side", "gacha"}: if item in {"icon", "side", "gacha"}:
url = AMBR_HOST.join(f"assets/UI/{self.game_name_map[item]}.png") return str(AMBR_HOST.join(f"assets/UI/{self.game_name_map[item]}.png"))
return await self._download(url, self.path.joinpath(f"{item}.png"))
async def _get_from_enka(self, item: str) -> Path | None: async def _get_from_enka(self, item: str) -> str | None:
path = self.path.joinpath(f"{item}.png")
item = "banner" if item == "gacha" else item item = "banner" if item == "gacha" else item
# noinspection PyUnboundLocalVariable # noinspection PyUnboundLocalVariable
if self.enka is not None and item in (data := self.enka.images.dict()).keys() and (url := data[item]["url"]): if self.enka is not None and item in (data := self.enka.images.dict()).keys() and (url := data[item]["url"]):
return await self._download(url, path) return str(url)
@cached_property @cached_property
def honey_name_map(self) -> dict[str, str]: def honey_name_map(self) -> dict[str, str]:
@ -295,11 +335,13 @@ class _WeaponAssets(_AssetsService):
result.id = target result.id = target
return result return result
async def _get_from_enka(self, item: str) -> Path | None: async def _get_from_ambr(self, item: str) -> str | None:
if item == "icon":
return str(AMBR_HOST.join(f"assets/UI/{self.game_name_map.get(item)}.png"))
async def _get_from_enka(self, item: str) -> str | None:
if item in self.game_name_map: if item in self.game_name_map:
url = ENKA_HOST.join(f"ui/{self.game_name_map.get(item)}.png") return str(ENKA_HOST.join(f"ui/{self.game_name_map.get(item)}.png"))
path = self.path.joinpath(f"{item}.png")
return await self._download(url, path)
@cached_property @cached_property
def honey_name_map(self) -> dict[str, str]: def honey_name_map(self) -> dict[str, str]:
@ -336,20 +378,21 @@ class _MaterialAssets(_AssetsService):
result.id = target result.id = target
return result return result
async def _get_from_ambr(self, item: str) -> Path | None: async def _get_from_ambr(self, item: str) -> str | None:
if item == "icon": if item == "icon":
url = AMBR_HOST.join(f"assets/UI/{self.game_name_map.get(item)}.png") return str(AMBR_HOST.join(f"assets/UI/{self.game_name_map.get(item)}.png"))
path = self.path.joinpath(f"{item}.png")
return await self._download(url, path)
async def _get_from_honey(self, item: str) -> Path | None: async def _get_from_honey(self, item: str) -> str | None:
path = self.path.joinpath(f"{item}.png") try:
url = HONEY_HOST.join(f"/img/{self.honey_name_map.get(item)}.png") result = HONEY_HOST.join(f"/img/{self.honey_name_map.get(item)}.png")
if (result := await self._download(url, path)) is None: response = await self.client.get(result, follow_redirects=False)
path = self.path.joinpath(f"{item}.webp") response.raise_for_status()
url = HONEY_HOST.join(f"/img/{self.honey_name_map.get(item)}.webp") except HTTPStatusError:
return await self._download(url, path) return None
return result if response.status_code == 200:
return result
return HONEY_HOST.join(f"/img/{self.honey_name_map.get(item)}.webp")
class _ArtifactAssets(_AssetsService): class _ArtifactAssets(_AssetsService):
@ -376,16 +419,13 @@ class _ArtifactAssets(_AssetsService):
def game_name(self) -> str: def game_name(self) -> str:
return f"UI_RelicIcon_{self.id}" return f"UI_RelicIcon_{self.id}"
async def _get_from_enka(self, item: str) -> Path | None: async def _get_from_enka(self, item: str) -> str | None:
if item in self.game_name_map: if item in self.game_name_map:
url = ENKA_HOST.join(f"ui/{self.game_name_map.get(item)}.png") return str(ENKA_HOST.join(f"ui/{self.game_name_map.get(item)}.png"))
path = self.path.joinpath(f"{item}.png")
return await self._download(url, path)
async def _get_from_ambr(self, item: str) -> Path | None: async def _get_from_ambr(self, item: str) -> str | None:
if item in self.game_name_map: if item in self.game_name_map:
url = AMBR_HOST.join(f"assets/UI/reliquary/{self.game_name_map[item]}.png") return str(AMBR_HOST.join(f"assets/UI/reliquary/{self.game_name_map[item]}.png"))
return await self._download(url, self.path.joinpath(f"{item}.png"))
@cached_property @cached_property
def game_name_map(self) -> dict[str, str]: def game_name_map(self) -> dict[str, str]:
@ -434,16 +474,14 @@ class _NamecardAssets(_AssetsService):
result.enka = DEFAULT_EnkaAssets.namecards(target) result.enka = DEFAULT_EnkaAssets.namecards(target)
return result return result
async def _get_from_ambr(self, item: str) -> Path | None: async def _get_from_ambr(self, item: str) -> str | None:
if item == "profile": if item == "profile":
url = AMBR_HOST.join(f"assets/UI/namecard/{self.game_name_map[item]}.png.png") return AMBR_HOST.join(f"assets/UI/namecard/{self.game_name_map[item]}.png.png")
return await self._download(url, self.path.joinpath(f"{item}.png"))
async def _get_from_enka(self, item: str) -> Path | None: async def _get_from_enka(self, item: str) -> str | None:
path = self.path.joinpath(f"{item}.png")
url = getattr(self.enka, {"profile": "banner"}.get(item, item), None) url = getattr(self.enka, {"profile": "banner"}.get(item, item), None)
if url is not None: if url is not None:
return await self._download(url.url, path) return str(url)
@cached_property @cached_property
def game_name_map(self) -> dict[str, str]: def game_name_map(self) -> dict[str, str]:

View File

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
import functools import functools
from typing import Any, Generic, ItemsView, Iterator, KeysView, TypeVar, Optional, ValuesView from typing import Any, Generic, ItemsView, Iterator, KeysView, Optional, TypeVar, ValuesView
import ujson as json import ujson as json
@ -60,6 +60,12 @@ class Data(dict, Generic[K, V]):
self._dict = {} self._dict = {}
super(Data, self).__init__() super(Data, self).__init__()
def __str__(self) -> str:
return self.data.__str__()
def __repr__(self) -> str:
return self.data.__repr__()
def get(self, key: K, value: Any = None) -> V | None: def get(self, key: K, value: Any = None) -> V | None:
return self.data.get(key, value) return self.data.get(key, value)