🎨 chore: use yatta
@ -4,7 +4,7 @@ from res_func.avatar import fix_avatar_config, fetch_text_map
|
|||||||
from res_func.light_cone import fix_light_cone_config
|
from res_func.light_cone import fix_light_cone_config
|
||||||
from res_func.relic import fetch_relic_config
|
from res_func.relic import fetch_relic_config
|
||||||
from res_func.relic_res import fix_set_image
|
from res_func.relic_res import fix_set_image
|
||||||
from res_func.honkai_gg.avatar import get_all_avatars
|
from res_func.yatta.avatar import get_all_avatars
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
src_dir = Path("src")
|
src_dir = Path("src")
|
||||||
data_dir = Path("data")
|
data_dir = Path("data")
|
||||||
pic_lists = ["avatar_gacha", "destiny", "element", "skill"]
|
pic_lists = ["avatar_gacha", "destiny", "element"]
|
||||||
|
|
||||||
|
|
||||||
async def move_files():
|
async def move_files():
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import aiofiles
|
|
||||||
import re
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import ujson
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from res_func.client import client
|
|
||||||
from res_func.url import avatar_honkai_url, avatar_icon_honkai_url
|
|
||||||
|
|
||||||
avatar_data = {}
|
|
||||||
|
|
||||||
|
|
||||||
async def get_all_avatar() -> List[str]:
|
|
||||||
req = await client.get(avatar_honkai_url)
|
|
||||||
html = req.text
|
|
||||||
pattern = re.compile(r'href="/characters/(.*?)">')
|
|
||||||
result = pattern.findall(html)
|
|
||||||
return [f"{avatar_honkai_url}/{i}" for i in result if i != ""]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_single_avatar(url: str) -> None:
|
|
||||||
req = await client.get(url)
|
|
||||||
html = req.text
|
|
||||||
soup = BeautifulSoup(html, "lxml")
|
|
||||||
div = soup.find_all("div", {"class": "character_skills__FL3Dn"})[-1]
|
|
||||||
pattern = re.compile(r'skillicons/(.*?)/skillicon(.*?).webp')
|
|
||||||
result = pattern.findall(str(div))
|
|
||||||
if len(result) != 6:
|
|
||||||
print(f"{url} 获取星魂图片失败")
|
|
||||||
return
|
|
||||||
urls = [f"{avatar_icon_honkai_url}/{i[0]}/skillicon{i[1]}.webp" for i in result]
|
|
||||||
avatar_data[result[0][0]] = urls
|
|
||||||
|
|
||||||
|
|
||||||
async def dump_icons():
|
|
||||||
final_data = dict(sorted(avatar_data.items(), key=lambda x: x[0]))
|
|
||||||
async with aiofiles.open("data/avatar_eidolon_icons.json", "w", encoding="utf-8") as f:
|
|
||||||
await f.write(ujson.dumps(final_data, indent=4, ensure_ascii=False))
|
|
||||||
|
|
||||||
|
|
||||||
async def get_all_avatars() -> None:
|
|
||||||
print("开始获取星魂图片")
|
|
||||||
urls = await get_all_avatar()
|
|
||||||
tasks = []
|
|
||||||
for url in urls:
|
|
||||||
tasks.append(get_single_avatar(url))
|
|
||||||
await asyncio.gather(*tasks)
|
|
||||||
# 修复开拓者
|
|
||||||
avatar_data["8002"] = avatar_data["8001"]
|
|
||||||
avatar_data["8004"] = avatar_data["8003"]
|
|
||||||
await dump_icons()
|
|
||||||
print("获取星魂图片成功")
|
|
@ -13,6 +13,6 @@ base_station_url = "https://starrailstation.com"
|
|||||||
avatar_url = f"{base_station_url}/cn/characters"
|
avatar_url = f"{base_station_url}/cn/characters"
|
||||||
light_cone_url = f"{base_station_url}/cn/equipment"
|
light_cone_url = f"{base_station_url}/cn/equipment"
|
||||||
relic_url = f"{base_station_url}/cn/relics"
|
relic_url = f"{base_station_url}/cn/relics"
|
||||||
base_honkai_url = "https://honkai.gg"
|
base_yatta_url = "https://api.yatta.top"
|
||||||
avatar_honkai_url = f"{base_honkai_url}/cn/characters"
|
avatar_yatta_url = f"{base_yatta_url}/hsr/v2/cn/avatar"
|
||||||
avatar_icon_honkai_url = f"{base_honkai_url}/images/spriteoutput/skillicons"
|
avatar_skill_url = f"{base_yatta_url}/hsr/assets/UI/skill/"
|
||||||
|
93
res_func/yatta/avatar.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
import ujson
|
||||||
|
|
||||||
|
from res_func.client import client
|
||||||
|
from res_func.url import avatar_yatta_url, avatar_skill_url
|
||||||
|
from res_func.yatta.model import YattaAvatar
|
||||||
|
|
||||||
|
avatar_data = {}
|
||||||
|
avatars_skills_icons = {}
|
||||||
|
avatars_skills_path = Path("data/skill")
|
||||||
|
avatars_skills_path.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_all_avatar() -> List[str]:
|
||||||
|
req = await client.get(avatar_yatta_url)
|
||||||
|
return list(req.json()["data"]["items"].keys())
|
||||||
|
|
||||||
|
|
||||||
|
async def get_single_avatar(url: str) -> None:
|
||||||
|
req = await client.get(url)
|
||||||
|
try:
|
||||||
|
avatar = YattaAvatar(**req.json()["data"])
|
||||||
|
except Exception:
|
||||||
|
print(f"{url} 获取星魂数据失败")
|
||||||
|
return
|
||||||
|
if len(avatar.eidolons) != 6:
|
||||||
|
print(f"{url} 获取星魂图片失败")
|
||||||
|
return
|
||||||
|
urls = [i.icon_url for i in avatar.eidolons]
|
||||||
|
avatar_data[str(avatar.id)] = urls
|
||||||
|
|
||||||
|
|
||||||
|
async def get_single_avatar_skill_icon(url: str, real_path: str) -> None:
|
||||||
|
req = await client.get(url)
|
||||||
|
try:
|
||||||
|
req.raise_for_status()
|
||||||
|
except Exception:
|
||||||
|
print(f"{url} 获取技能图片失败")
|
||||||
|
return
|
||||||
|
async with aiofiles.open(f"data/skill/{real_path}", "wb") as f:
|
||||||
|
await f.write(req.content)
|
||||||
|
if "8001" in real_path:
|
||||||
|
real_path = real_path.replace("8001", "8002")
|
||||||
|
async with aiofiles.open(f"data/skill/{real_path}", "wb") as f:
|
||||||
|
await f.write(req.content)
|
||||||
|
elif "8003" in real_path:
|
||||||
|
real_path = real_path.replace("8003", "8004")
|
||||||
|
async with aiofiles.open(f"data/skill/{real_path}", "wb") as f:
|
||||||
|
await f.write(req.content)
|
||||||
|
|
||||||
|
|
||||||
|
async def dump_icons():
|
||||||
|
final_data = dict(sorted(avatar_data.items(), key=lambda x: x[0]))
|
||||||
|
async with aiofiles.open("data/avatar_eidolon_icons.json", "w", encoding="utf-8") as f:
|
||||||
|
await f.write(ujson.dumps(final_data, indent=4, ensure_ascii=False))
|
||||||
|
|
||||||
|
|
||||||
|
async def get_all_avatars() -> None:
|
||||||
|
print("开始获取星魂图片")
|
||||||
|
avatar_ids = await get_all_avatar()
|
||||||
|
for avatar_id in avatar_ids:
|
||||||
|
await get_single_avatar(f"{avatar_yatta_url}/{avatar_id}")
|
||||||
|
await dump_icons()
|
||||||
|
print("获取星魂图片成功")
|
||||||
|
await get_all_avatars_skills_icons(avatar_ids)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_all_avatars_skills_icons(avatar_ids: List[str]):
|
||||||
|
remote_path = ["Normal", "BP", "Passive", "Maze", "Ultra"]
|
||||||
|
local_path = ["basic_atk", "skill", "talent", "technique", "ultimate"]
|
||||||
|
print("开始获取技能图片")
|
||||||
|
tasks = []
|
||||||
|
for avatar_id in avatar_ids:
|
||||||
|
if avatar_id in ["8002", "8004"]:
|
||||||
|
continue
|
||||||
|
for i in range(len(remote_path)):
|
||||||
|
tasks.append(
|
||||||
|
get_single_avatar_skill_icon(
|
||||||
|
f"{avatar_skill_url}SkillIcon_{avatar_id}_{remote_path[i]}.png",
|
||||||
|
f"{avatar_id}_{local_path[i]}.png"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
tasks.clear()
|
||||||
|
datas = [file.name.split(".")[0] for file in avatars_skills_path.glob("*")]
|
||||||
|
async with aiofiles.open(avatars_skills_path / "info.json", "w", encoding="utf-8") as f:
|
||||||
|
await f.write(json.dumps(datas, indent=4, ensure_ascii=False))
|
||||||
|
print("获取技能图片成功")
|
63
res_func/yatta/model.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, root_validator
|
||||||
|
|
||||||
|
from res_func.url import avatar_skill_url
|
||||||
|
|
||||||
|
|
||||||
|
class YattaAvatarPath(BaseModel):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class YattaAvatarTypes(BaseModel):
|
||||||
|
pathType: YattaAvatarPath
|
||||||
|
combatType: YattaAvatarPath
|
||||||
|
|
||||||
|
|
||||||
|
class YattaAvatarCV(BaseModel):
|
||||||
|
CV_CN: str
|
||||||
|
CV_JP: str
|
||||||
|
CV_KR: str
|
||||||
|
CV_EN: str
|
||||||
|
|
||||||
|
|
||||||
|
class YattaAvatarFetter(BaseModel):
|
||||||
|
faction: Optional[str]
|
||||||
|
description: Optional[str]
|
||||||
|
cv: Optional[YattaAvatarCV]
|
||||||
|
|
||||||
|
|
||||||
|
class YattaAvatarEidolon(BaseModel):
|
||||||
|
id: int
|
||||||
|
rank: int
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
icon: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon_url(self) -> str:
|
||||||
|
return f"{avatar_skill_url}{self.icon}.png"
|
||||||
|
|
||||||
|
|
||||||
|
class YattaAvatar(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
rank: int
|
||||||
|
types: YattaAvatarTypes
|
||||||
|
icon: str
|
||||||
|
release: int
|
||||||
|
route: str
|
||||||
|
fetter: YattaAvatarFetter
|
||||||
|
eidolons: List[YattaAvatarEidolon]
|
||||||
|
|
||||||
|
@root_validator(pre=True)
|
||||||
|
def validate(cls, values):
|
||||||
|
if values.get("eidolons") is None:
|
||||||
|
values["eidolons"] = []
|
||||||
|
else:
|
||||||
|
eidolons = []
|
||||||
|
for eidolon in values["eidolons"].values():
|
||||||
|
eidolons.append(eidolon)
|
||||||
|
values["eidolons"] = eidolons
|
||||||
|
return values
|
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB |