support: some icons

This commit is contained in:
xtaodada 2023-04-27 16:44:27 +08:00
parent 0c9f1f8b86
commit faf212844b
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
15 changed files with 233 additions and 5 deletions

View File

@ -25,6 +25,7 @@ jobs:
- name: Fetch Remote Files - name: Fetch Remote Files
run: | run: |
python main.py python main.py
python fix_data.py
- name: Commit changes - name: Commit changes
uses: EndBug/add-and-commit@v9 uses: EndBug/add-and-commit@v9

13
fix_data.py Normal file
View File

@ -0,0 +1,13 @@
import asyncio
from res_func.avatar import fix_avatar_config
from res_func.light_cone import fix_light_cone_config
async def main():
await fix_avatar_config()
await fix_light_cone_config()
if __name__ == '__main__':
asyncio.run(main())

3
func/README.md Normal file
View File

@ -0,0 +1,3 @@
# wiki 解析
通过米游社官方 wiki 解析部分数据

View File

@ -16,4 +16,4 @@ headers = {
'sec-fetch-site': 'same-site', 'sec-fetch-site': 'same-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36',
} }
client = AsyncClient(headers=headers, timeout=30) client = AsyncClient(headers=headers, timeout=120.0)

View File

@ -27,7 +27,6 @@ async def fetch_avatars(data: Children):
avatar = Avatar( avatar = Avatar(
id=content.content_id, id=content.content_id,
name=content.title, name=content.title,
icon=content.icon,
quality=m_quality, quality=m_quality,
element=m_element, element=m_element,
destiny=m_destiny, destiny=m_destiny,

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
import re import re
from pathlib import Path from pathlib import Path
from typing import List from typing import List, Dict
import aiofiles import aiofiles
import ujson import ujson
@ -15,6 +15,7 @@ from models.light_cone import LightCone, LightConePromote, LightConeItem
from models.wiki import Children from models.wiki import Children
all_light_cones: List[LightCone] = [] all_light_cones: List[LightCone] = []
all_light_cones_name: Dict[str, LightCone] = {}
async def fetch_light_cones(data: Children): async def fetch_light_cones(data: Children):
@ -32,6 +33,7 @@ async def fetch_light_cones(data: Children):
promote=[], promote=[],
) )
all_light_cones.append(light_cone) all_light_cones.append(light_cone)
all_light_cones_name[light_cone.name] = light_cone
def parse_promote(light_cone: LightCone, soup: BeautifulSoup) -> None: def parse_promote(light_cone: LightCone, soup: BeautifulSoup) -> None:
@ -109,6 +111,9 @@ async def dump_light_cones(path: Path):
async def read_light_cones(path: Path): async def read_light_cones(path: Path):
async with aiofiles.open(path, "r", encoding="utf-8") as f: async with aiofiles.open(path, "r", encoding="utf-8") as f:
data = ujson.loads(await f.read()) data = ujson.loads(await f.read())
all_light_cones.clear()
all_light_cones_name.clear()
for light_cone in data: for light_cone in data:
m = LightCone(**light_cone) m = LightCone(**light_cone)
all_light_cones.append(m) all_light_cones.append(m)
all_light_cones_name[m.name] = m

View File

@ -44,8 +44,6 @@ class Avatar(BaseModel):
"""角色ID""" """角色ID"""
name: str name: str
"""名称""" """名称"""
icon: str
"""图标"""
quality: Quality quality: Quality
"""品质""" """品质"""
destiny: Destiny destiny: Destiny

24
models/avatar_config.py Normal file
View File

@ -0,0 +1,24 @@
from typing import List
from pydantic import BaseModel
class AvatarName(BaseModel):
Hash: int
class AvatarConfig(BaseModel):
name: str = ""
AvatarID: int
AvatarName: AvatarName
AvatarVOTag: str
Release: bool
class AvatarIcon(BaseModel):
id: int
"""角色ID"""
name: str
"""名称"""
icon: List[str]
"""图标(从小到大)"""

View File

@ -0,0 +1,12 @@
from typing import List
from pydantic import BaseModel
class LightConeIcon(BaseModel):
id: int
"""光锥ID"""
name: str
"""名称"""
icon: List[str]
"""图标(从小到大)"""

3
res_func/README.md Normal file
View File

@ -0,0 +1,3 @@
# 素材解析
通过 **** 和 https://starrailstation.com 解析图片素材

0
res_func/__init__.py Normal file
View File

96
res_func/avatar.py Normal file
View File

@ -0,0 +1,96 @@
import asyncio
from pathlib import Path
from typing import Dict, List
import aiofiles
import ujson
from bs4 import BeautifulSoup, Tag
from func.fetch_avatars import read_avatars, all_avatars_name, dump_avatars
from .client import client
from .url import avatar_config, text_map, base_station_url, avatar_url
from models.avatar_config import AvatarConfig, AvatarIcon
async def fetch_text_map() -> Dict[str, str]:
res = await client.get(text_map)
return res.json()
async def fetch_config(text_map: Dict[str, str]) -> List[AvatarConfig]:
res = await client.get(avatar_config)
data = res.json()
datas = []
for i in data.values():
a = AvatarConfig(**i)
a.name = text_map[str(a.AvatarName.Hash)]
datas.append(a)
return datas
async def parse_station(datas, tag: Tag, avatar: AvatarConfig):
second_pic = f'{base_station_url}{tag.find("img").get("src")}'
html = await client.get(f'{base_station_url}{tag.get("href")}')
soup = BeautifulSoup(html.text, "lxml")
text = soup.find("div", {"class": "a6678 a4af5"}).get("style")
third_pic = f'{base_station_url}{text[text.find("(") + 2:text.find(")") - 1]}' if text else ""
first_pic = f'{base_station_url}{soup.find("img", {"class": "ac39b a6602"}).get("src")}'
datas.append(
AvatarIcon(
id=avatar.AvatarID,
name=avatar.name,
icon=[first_pic, second_pic, third_pic],
)
)
async def dump_icons(path: Path, datas: List[AvatarIcon]):
data = [icon.dict() for icon in datas]
data.sort(key=lambda x: x["id"])
async with aiofiles.open(path, "w", encoding="utf-8") as f:
await f.write(ujson.dumps(data, indent=4, ensure_ascii=False))
async def fetch_station(configs_map: Dict[str, AvatarConfig]) -> List[AvatarIcon]:
print("开始获取角色素材")
html = await client.get(avatar_url)
soup = BeautifulSoup(html.text, "lxml")
avatars = soup.find_all("a", {"class": "char-entry-select-option"})
tasks = []
datas: List[AvatarIcon] = []
ktz = False
for avatar in avatars:
name = avatar.find("span").get_text().strip()
if name == "开拓者" and ktz:
continue
if avatar_model := configs_map.get(name):
tasks.append(parse_station(datas, avatar, avatar_model))
if name == "开拓者":
ktz = True
else:
print(f"未找到角色 {name} 的数据")
await asyncio.gather(*tasks)
return datas
async def fix_avatar_config():
text_map_data = await fetch_text_map()
configs = await fetch_config(text_map_data)
configs_map: Dict[str, AvatarConfig] = {config.name: config for config in configs}
configs_map["开拓者"] = configs_map["{NICKNAME}"]
print(f"读取到原始数据:{list(configs_map.keys())}")
data_path = Path("data")
await read_avatars(data_path / "avatars.json")
for key, value in all_avatars_name.items():
if key.startswith("开拓者"):
config = configs_map["{NICKNAME}"]
config.name = "开拓者"
else:
config = configs_map.get(key)
if config is None:
print(f"错误:未找到角色 {key} 的配置")
continue
value.id = config.AvatarID
icons = await fetch_station(configs_map)
await dump_icons(data_path / "avatar_icons.json", icons)
await dump_avatars(data_path / "avatars.json")

3
res_func/client.py Normal file
View File

@ -0,0 +1,3 @@
from httpx import AsyncClient
client = AsyncClient(timeout=120.0)

61
res_func/light_cone.py Normal file
View File

@ -0,0 +1,61 @@
import asyncio
from pathlib import Path
from typing import Dict, List
import aiofiles
import ujson
from bs4 import BeautifulSoup, Tag
from func.fetch_light_cones import read_light_cones, all_light_cones_name, dump_light_cones
from .client import client
from .url import base_station_url, light_cone_url
from models.light_cone_config import LightConeIcon
async def parse_station(icon: LightConeIcon, tag: Tag):
html = await client.get(f'{base_station_url}{tag.get("href")}')
soup = BeautifulSoup(html.text, "lxml")
first_pic = f'{base_station_url}{soup.find("img", {"class": "standard-icon a6602"}).get("src")}'
second_pic = f'{base_station_url}{soup.find("img", {"class": "a2b16 mobile-only-elem ab8c3"}).get("src")}'
icon.icon = [first_pic, second_pic]
async def dump_icons(path: Path, datas: List[LightConeIcon]):
data = [icon.dict() for icon in datas]
data.sort(key=lambda x: x["id"])
async with aiofiles.open(path, "w", encoding="utf-8") as f:
await f.write(ujson.dumps(data, indent=4, ensure_ascii=False))
async def fetch_station() -> List[LightConeIcon]:
print("开始获取光锥素材")
html = await client.get(light_cone_url)
soup = BeautifulSoup(html.text, "lxml")
light_cones = soup.find_all("a", {"class": "a4041 ae026 a5abc"})
tasks = []
datas: List[LightConeIcon] = []
for light_cone in light_cones:
name = light_cone.find("span").get_text().strip()
url = light_cone.get("href")
nid = int(url.split("/")[-1])
if light_cone_model := all_light_cones_name.get(name):
light_cone_model.id = nid
else:
print(f"wiki 未找到光锥数据 {name} ,修复 id 失败")
icon = LightConeIcon(
id=nid,
name=name,
icon=[]
)
datas.append(icon)
tasks.append(parse_station(icon, light_cone))
await asyncio.gather(*tasks)
return datas
async def fix_light_cone_config():
data_path = Path("data")
await read_light_cones(data_path / "light_cones.json")
icons = await fetch_station()
await dump_icons(data_path / "light_cone_icons.json", icons)
await dump_light_cones(data_path / "light_cones.json")

10
res_func/url.py Normal file
View File

@ -0,0 +1,10 @@
import base64
base_data_url = base64.b64decode(
"aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0RpbWJyZWF0aC9TdGFyUmFpbERhdGEvbWFzdGVyLw=="
).decode("utf-8")
avatar_config = f"{base_data_url}ExcelOutput/AvatarConfig.json"
text_map = f"{base_data_url}TextMap/TextMapCN.json"
base_station_url = "https://starrailstation.com"
avatar_url = f"{base_station_url}/cn/characters"
light_cone_url = f"{base_station_url}/cn/equipment"