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_map: Dict[int, Material] = {}
all_materials_name: Dict[str, Material] = {}
async def fetch_materials(data: Children):
@ -46,6 +47,7 @@ async def fetch_materials(data: Children):
)
all_materials.append(material)
all_materials_map[material.id] = material
all_materials_name[material.name] = 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]
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_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

11
main.py
View File

@ -1,17 +1,24 @@
import asyncio
from pathlib import Path
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.mkdir(exist_ok=True)
async def main():
async def main(override: bool = True):
main_data = await get_list()
if override:
await fetch_materials(main_data[4])
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__':

View File

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