Enka_Panel_Bot/gspanel/__utils__.py

222 lines
9.0 KiB
Python
Raw Normal View History

2022-09-03 14:50:36 +00:00
import asyncio
import json
from pathlib import Path
from typing import Optional, Union
from httpx import AsyncClient
from playwright.async_api import Browser, async_playwright
GROW_VALUE = { # 词条成长值
"暴击率": 3.89,
"暴击伤害": 7.77,
"元素精通": 23.31,
"攻击力百分比": 5.83,
"生命值百分比": 5.83,
"防御力百分比": 7.29,
"元素充能效率": 6.48,
"元素伤害加成": 5.825,
"物理伤害加成": 7.288,
"治疗加成": 4.487,
}
MAIN_AFFIXS = { # 可能的主词条
"3": "攻击力百分比,防御力百分比,生命值百分比,元素精通,元素充能效率".split(","), # EQUIP_SHOES
"4": "攻击力百分比,防御力百分比,生命值百分比,元素精通,元素伤害加成,物理伤害加成".split(","), # EQUIP_RING
"5": "攻击力百分比,防御力百分比,生命值百分比,元素精通,治疗加成,暴击率,暴击伤害".split(","), # EQUIP_DRESS
}
SUB_AFFIXS = "攻击力,攻击力百分比,防御力,防御力百分比,生命值,生命值百分比,元素精通,元素充能效率,暴击率,暴击伤害".split(",")
# STAR = {"QUALITY_ORANGE": 5, "QUALITY_PURPLE": 4}
ELEM = {
"Fire": "",
"Water": "",
"Wind": "",
"Electric": "",
"Grass": "",
"Ice": "",
"Rock": "",
}
POS = ["EQUIP_BRACER", "EQUIP_NECKLACE", "EQUIP_SHOES", "EQUIP_RING", "EQUIP_DRESS"]
SKILL = {"1": "a", "2": "e", "9": "q"}
DMG = {
"40": "",
"41": "",
"42": "",
"43": "",
"44": "",
"45": "",
"46": "",
}
PROP = {
"FIGHT_PROP_BASE_ATTACK": "基础攻击力",
"FIGHT_PROP_HP": "生命值",
"FIGHT_PROP_ATTACK": "攻击力",
"FIGHT_PROP_DEFENSE": "防御力",
"FIGHT_PROP_HP_PERCENT": "生命值百分比",
"FIGHT_PROP_ATTACK_PERCENT": "攻击力百分比",
"FIGHT_PROP_DEFENSE_PERCENT": "防御力百分比",
"FIGHT_PROP_CRITICAL": "暴击率",
"FIGHT_PROP_CRITICAL_HURT": "暴击伤害",
"FIGHT_PROP_CHARGE_EFFICIENCY": "元素充能效率",
"FIGHT_PROP_HEAL_ADD": "治疗加成",
"FIGHT_PROP_ELEMENT_MASTERY": "元素精通",
"FIGHT_PROP_PHYSICAL_ADD_HURT": "物理伤害加成",
"FIGHT_PROP_FIRE_ADD_HURT": "火元素伤害加成",
"FIGHT_PROP_ELEC_ADD_HURT": "雷元素伤害加成",
"FIGHT_PROP_WATER_ADD_HURT": "水元素伤害加成",
"FIGHT_PROP_WIND_ADD_HURT": "风元素伤害加成",
"FIGHT_PROP_ICE_ADD_HURT": "冰元素伤害加成",
"FIGHT_PROP_ROCK_ADD_HURT": "岩元素伤害加成",
}
_browser: Optional[Browser] = None
EXPIRE_SEC = 60 * 5
LOCAL_DIR = Path("resources")
if not (LOCAL_DIR / "cache").exists():
(LOCAL_DIR / "cache").mkdir(parents=True, exist_ok=True)
def kStr(prop: str) -> str:
"""转换词条名称为简短形式"""
return (
prop.replace("百分比", "")
.replace("元素充能", "充能")
.replace("元素伤害", "")
.replace("物理伤害", "物伤")
)
def vStr(prop: str, value: Union[int, float]) -> str:
"""转换词条数值为字符串形式"""
if prop in {"生命值", "攻击力", "防御力", "元素精通"}:
return str(value)
else:
return f"{str(round(value, 1))}%"
async def initBrowser(**kwargs) -> Optional[Browser]:
global _browser
browser = await async_playwright().start()
try:
_browser = await browser.chromium.launch(**kwargs)
return _browser
except Exception as e:
print(f"启动 Chromium 发生错误 {type(e)}{e}")
return None
async def getBrowser(**kwargs) -> Optional[Browser]:
return _browser or await initBrowser(**kwargs)
async def fetchInitRes() -> None:
"""
插件初始化资源下载通过阿里云 CDN 获取 Enka.Network API 提供的 JSON 文件HTML 模板资源文件角色词条权重配置等
- https://raw.githubusercontent.com/EnkaNetwork/API-docs/master/store/loc.json
- https://raw.githubusercontent.com/Dimbreath/GenshinData/master/TextMap/TextMapCHS.json
- https://raw.githubusercontent.com/EnkaNetwork/API-docs/master/store/characters.json
- https://raw.githubusercontent.com/monsterxcn/nonebot_plugin_gspanel/main/data/gspanel/template.json
- https://raw.githubusercontent.com/monsterxcn/nonebot_plugin_gspanel/main/data/gspanel/calc-rule.json
"""
print("正在检查面板插件所需资源...")
# 仅首次启用插件下载的文件
initRes = [
"https://cdn.monsterx.cn/bot/gspanel/font/华文中宋.TTF",
"https://cdn.monsterx.cn/bot/gspanel/font/HYWH-65W.ttf",
"https://cdn.monsterx.cn/bot/gspanel/font/NZBZ.ttf",
"https://cdn.monsterx.cn/bot/gspanel/font/tttgbnumber.ttf",
"https://cdn.monsterx.cn/bot/gspanel/imgs/bg-anemo.jpg",
"https://cdn.monsterx.cn/bot/gspanel/imgs/bg-cryo.jpg",
"https://cdn.monsterx.cn/bot/gspanel/imgs/bg-dendro.jpg",
"https://cdn.monsterx.cn/bot/gspanel/imgs/bg-electro.jpg",
"https://cdn.monsterx.cn/bot/gspanel/imgs/bg-geo.jpg",
"https://cdn.monsterx.cn/bot/gspanel/imgs/bg-hydro.jpg",
"https://cdn.monsterx.cn/bot/gspanel/imgs/bg-pyro.jpg",
"https://cdn.monsterx.cn/bot/gspanel/imgs/card-bg.png",
"https://cdn.monsterx.cn/bot/gspanel/imgs/star.png",
"https://cdn.monsterx.cn/bot/gspanel/imgs/talent-anemo.png",
"https://cdn.monsterx.cn/bot/gspanel/imgs/talent-cryo.png",
"https://cdn.monsterx.cn/bot/gspanel/imgs/talent-dendro.png",
"https://cdn.monsterx.cn/bot/gspanel/imgs/talent-electro.png",
"https://cdn.monsterx.cn/bot/gspanel/imgs/talent-geo.png",
"https://cdn.monsterx.cn/bot/gspanel/imgs/talent-hydro.png",
"https://cdn.monsterx.cn/bot/gspanel/imgs/talent-pyro.png",
"https://cdn.monsterx.cn/bot/gspanel/style.css",
"https://cdn.monsterx.cn/bot/gspanel/tpl.html",
]
tasks = []
for r in initRes:
d = r.replace("https://cdn.monsterx.cn/bot/gspanel/", "").split("/")[0]
tasks.append(download(r, local=("" if "." in d else d)))
await asyncio.gather(*tasks)
tasks.clear()
print("首次启用所需资源检查完毕..")
# 总是尝试更新的文件
urls = [
# "https://cdn.monsterx.cn/bot/gapanel/loc.json", # 仅包含 zh-CN 语言
"https://cdn.monsterx.cn/bot/gspanel/TextMapCHS.json",
"https://cdn.monsterx.cn/bot/gspanel/characters.json",
"https://cdn.monsterx.cn/bot/gspanel/calc-rule.json",
]
tmp = {"0": {}, "1": {}, "2": {}}
async with AsyncClient(verify=False) as client:
for idx, url in enumerate(urls):
tmp[str(idx)] = (await client.get(url)).json()
(LOCAL_DIR / url.split("/")[-1]).write_text(
json.dumps(tmp[str(idx)], ensure_ascii=False, indent=2),
encoding="utf-8",
)
# 额外生成一份 {"中文名": "8 位角色 ID", ...} 配置
name2id, unknownCnt = {}, 1
for charId in tmp["1"]: # characters.json
if not tmp["1"][charId].get("NameTextMapHash"):
# 10000005-502 10000005-503 10000005-505
# 10000007-702 10000007-703 10000007-705
continue
nameTextMapHash = tmp["1"][charId]["NameTextMapHash"] # type: int
nameCn = tmp["0"].get(str(nameTextMapHash), f"未知角色{unknownCnt}")
name2id[nameCn] = charId
if nameCn == f"未知角色{unknownCnt}":
unknownCnt += 1
(LOCAL_DIR / "name2id.json").write_text(
json.dumps(name2id, ensure_ascii=False, indent=2), encoding="utf-8"
)
print("面板插件所需资源检查完毕!")
async def download(url: str, local: Union[Path, str] = "") -> Union[Path, None]:
"""
一般文件下载通常是即用即下的角色命座图片技能图片抽卡大图圣遗物图片等
* ``param url: str`` 指定下载链接
* ``param local: Union[Path, str] = ""`` 指定本地目标路径传入类型为 ``Path`` 时视为保存文件完整路径传入类型为 ``str`` 时视为保存文件子文件夹名默认下载至插件资源根目录
- ``return: Union[Path, None]`` 本地文件地址出错时返回空
"""
if not isinstance(local, Path):
d = (LOCAL_DIR / local) if local else LOCAL_DIR
if not d.exists():
d.mkdir(parents=True, exist_ok=True)
f = d / url.split("/")[-1]
else:
if not local.parent.exists():
local.parent.mkdir(parents=True, exist_ok=True)
f = local
# 本地文件存在时便不再下载JSON 文件除外
if f.exists() and ".json" not in f.name:
return f
client = AsyncClient()
retryCnt = 3
while retryCnt:
try:
async with client.stream(
"GET", url, headers={"user-agent": "NoneBot-GsPanel"}
) as res:
with open(f, "wb") as fb:
async for chunk in res.aiter_bytes():
fb.write(chunk)
return f
except Exception as e:
print(f"面板资源 {f.name} 下载出错 {type(e)}{e}")
retryCnt -= 1
await asyncio.sleep(2)
return None