support: avatars

This commit is contained in:
xtaodada 2023-04-23 23:18:42 +08:00
parent 24708edf2b
commit 30ba8afa3b
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
4 changed files with 158 additions and 26 deletions

128
func/fetch_avatars.py Normal file
View File

@ -0,0 +1,128 @@
import asyncio
import re
from pathlib import Path
from typing import List, Dict
import aiofiles
import ujson
from bs4 import BeautifulSoup
from func.client import client
from func.url import info_url
from func.fetch_materials import all_materials_map, all_materials_name
from models.enums import Quality, Element, Destiny
from models.avatar import Avatar, AvatarInfo, AvatarSoul, AvatarItem, AvatarPromote
from models.wiki import Children
all_avatars: List[Avatar] = []
all_avatars_map: Dict[int, Avatar] = {}
all_avatars_name: Dict[str, Avatar] = {}
async def fetch_avatars(data: Children):
for content in data.list:
m_element = Element(re.findall(r'属性/(.*?)\\', content.ext)[0])
m_destiny = Destiny(re.findall(r'命途/(.*?)\\', content.ext)[0])
m_quality = Quality(re.findall(r'星级/(.*?)\\', content.ext)[0])
avatar = Avatar(
id=content.content_id,
name=content.title,
quality=m_quality,
element=m_element,
destiny=m_destiny,
information=AvatarInfo(),
promote=[],
soul=[],
)
all_avatars.append(avatar)
all_avatars_map[avatar.id] = avatar
all_avatars_name[avatar.name] = avatar
def parse_promote(avatar: Avatar, soup: BeautifulSoup) -> None:
"""解析角色突破数据"""
lis = soup.find_all("li", {"class": "obc-tmpl__switch-item"})
required_levels = [0, 20, 30, 40, 50, 60, 70, 80]
max_level = [0, 30, 40, 50, 60, 70, 80, 90]
for i in range(1, 8):
promote = AvatarPromote(
required_level=required_levels[i],
max_level=max_level[i],
items=[],
)
materials = lis[i].find_all("li", {"data-target": "breach.attr.material"})
for material in materials:
try:
mid = int(re.findall(r"content/(\d+)/detail", material.find("a").get("href"))[0])
except AttributeError:
continue
name = material.find("span", {"class": "obc-tmpl__icon-text"}).text
item = all_materials_map.get(mid)
if not item:
item = all_materials_name.get(name)
try:
count = int(material.find("span", {"class": "obc-tmpl__icon-num"}).text.replace("*", ""))
except AttributeError:
count = 1
if name == "信用点":
promote.coin = count
elif item:
promote.items.append(
AvatarItem(
item=item,
count=count,
)
)
else:
print(f"unknown material: {mid}: {name}")
avatar.promote.append(promote)
async def fetch_info(avatar: Avatar):
print(f"Fetch avatar info: {avatar.id}: {avatar.name}")
params = {
'app_sn': 'sr_wiki',
'content_id': str(avatar.id),
}
resp = await client.get(info_url, params=params)
data = resp.json()["data"]["content"]["contents"][0]["text"]
soup = BeautifulSoup(data, "lxml")
items = soup.find_all("div", {"class": "obc-tmp-character__item"})
avatar.information.faction = items[2].find("div", {"class": "obc-tmp-character__value"}).text
avatar.information.occupation = items[3].find("div", {"class": "obc-tmp-character__value"}).text
parse_promote(avatar, soup)
# 星魂
table = soup.find_all("table")[-1]
trs = table.find_all("tr")[1:]
for tr in trs:
ps = tr.find_all("p")
desc = ps[2].text.strip() if len(ps) > 2 else ps[1].text.strip()
avatar.soul.append(
AvatarSoul(
name=ps[0].text.strip(),
desc=desc,
)
)
async def fetch_avatars_infos():
tasks = []
for avatar in all_avatars:
tasks.append(fetch_info(avatar))
await asyncio.gather(*tasks)
async def dump_avatars(path: Path):
data = [avatar.dict() for avatar in all_avatars]
async with aiofiles.open(path, "w", encoding="utf-8") as f:
await f.write(ujson.dumps(data, indent=4, ensure_ascii=False))
async def read_avatars(path: Path):
async with aiofiles.open(path, "r", encoding="utf-8") as f:
data = ujson.loads(await f.read())
for avatar in data:
m = Avatar(**avatar)
all_avatars.append(m)
all_avatars_map[m.id] = m
all_avatars_name[m.name] = m

View File

@ -22,6 +22,7 @@ star_map = {
} }
all_materials: List[Material] = [] all_materials: List[Material] = []
all_materials_map: Dict[int, Material] = {} all_materials_map: Dict[int, Material] = {}
all_materials_name: Dict[str, Material] = {}
async def fetch_materials(data: Children): async def fetch_materials(data: Children):
@ -46,6 +47,7 @@ async def fetch_materials(data: Children):
) )
all_materials.append(material) all_materials.append(material)
all_materials_map[material.id] = material all_materials_map[material.id] = material
all_materials_name[material.name] = material
async def fetch_info(material: Material): async def fetch_info(material: Material):
@ -78,3 +80,13 @@ async def dump_materials(path: Path):
data = [material.dict() for material in all_materials] data = [material.dict() for material in all_materials]
async with aiofiles.open(path, "w", encoding="utf-8") as f: async with aiofiles.open(path, "w", encoding="utf-8") as f:
await f.write(ujson.dumps(data, indent=4, ensure_ascii=False)) await f.write(ujson.dumps(data, indent=4, ensure_ascii=False))
async def read_materials(path: Path):
async with aiofiles.open(path, "r", encoding="utf-8") as f:
data = ujson.loads(await f.read())
for material in data:
m = Material(**material)
all_materials.append(m)
all_materials_map[m.id] = m
all_materials_name[m.name] = m

17
main.py
View File

@ -1,17 +1,24 @@
import asyncio import asyncio
from pathlib import Path from pathlib import Path
from func.fetch_all import get_list from func.fetch_all import get_list
from func.fetch_materials import fetch_materials, fetch_materials_infos, dump_materials from func.fetch_avatars import fetch_avatars, fetch_avatars_infos, dump_avatars, read_avatars
from func.fetch_materials import fetch_materials, fetch_materials_infos, dump_materials, read_materials
data_path = Path("data") data_path = Path("data")
data_path.mkdir(exist_ok=True) data_path.mkdir(exist_ok=True)
async def main(): async def main(override: bool = True):
main_data = await get_list() main_data = await get_list()
await fetch_materials(main_data[4]) if override:
await fetch_materials_infos() await fetch_materials(main_data[4])
await dump_materials(data_path / "materials.json") await fetch_materials_infos()
await dump_materials(data_path / "materials.json")
else:
await read_materials(data_path / "materials.json")
await fetch_avatars(main_data[0])
await fetch_avatars_infos()
await dump_avatars(data_path / "avatars.json")
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -1,45 +1,30 @@
from typing import List, TYPE_CHECKING from typing import List
from pydantic import BaseModel from pydantic import BaseModel
from models.enums import Quality, Destiny, Element from models.enums import Quality, Destiny, Element
from models.material import Material
if TYPE_CHECKING:
from models.material import Material
class AvatarInfo(BaseModel): class AvatarInfo(BaseModel):
occupation: str occupation: str = ""
"""所属""" """所属"""
faction: str faction: str = ""
"""派系""" """派系"""
class AvatarItem(BaseModel): class AvatarItem(BaseModel):
item: "Material" item: Material
"""物品""" """物品"""
count: int count: int
"""数量""" """数量"""
class AvatarPromote(BaseModel): class AvatarPromote(BaseModel):
required_level: int = 0 required_level: int
"""突破所需等级""" """突破所需等级"""
promote_level: int = 0 promote_level: int = 0
"""突破等级""" """突破等级"""
max_level: int max_level: int
"""解锁的等级上限""" """解锁的等级上限"""
before_hp: int
"""突破前生命值"""
after_hp: int
"""突破后生命值"""
before_attack: int
"""突破前攻击力"""
after_attack: int
"""突破后攻击力"""
before_defense: int
"""突破前防御力"""
after_defense: int
"""突破后防御力"""
coin: int = 0 coin: int = 0
"""信用点""" """信用点"""