🎨 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.relic import fetch_relic_config
|
||||
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():
|
||||
|
@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
src_dir = Path("src")
|
||||
data_dir = Path("data")
|
||||
pic_lists = ["avatar_gacha", "destiny", "element", "skill"]
|
||||
pic_lists = ["avatar_gacha", "destiny", "element"]
|
||||
|
||||
|
||||
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"
|
||||
light_cone_url = f"{base_station_url}/cn/equipment"
|
||||
relic_url = f"{base_station_url}/cn/relics"
|
||||
base_honkai_url = "https://honkai.gg"
|
||||
avatar_honkai_url = f"{base_honkai_url}/cn/characters"
|
||||
avatar_icon_honkai_url = f"{base_honkai_url}/images/spriteoutput/skillicons"
|
||||
base_yatta_url = "https://api.yatta.top"
|
||||
avatar_yatta_url = f"{base_yatta_url}/hsr/v2/cn/avatar"
|
||||
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 |