├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── trans-miao-resources.js ├── update-resources.py └── workflows │ ├── publish.yml │ └── update-resources.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── data └── gspanel │ ├── calc-rule.json │ ├── char-alias.json │ ├── char-data.json │ ├── font │ ├── HYWH-65W.ttf │ └── tttgbnumber.ttf │ ├── g2plot.min.js │ ├── hash-trans.json │ ├── imgs │ ├── bg-anemo.jpg │ ├── bg-cryo.jpg │ ├── bg-dendro.jpg │ ├── bg-electro.jpg │ ├── bg-geo.jpg │ ├── bg-hydro.jpg │ ├── bg-pyro.jpg │ ├── talent-anemo.png │ ├── talent-cryo.png │ ├── talent-dendro.png │ ├── talent-electro.png │ ├── talent-geo.png │ ├── talent-hydro.png │ └── talent-pyro.png │ ├── list-0.2.7.css │ ├── list-0.2.7.html │ ├── panel-0.2.7.css │ ├── panel-0.2.7.html │ ├── relic-append.json │ ├── team-0.2.20.css │ ├── team-0.2.20.html │ └── team-alias.json ├── nonebot_plugin_gspanel ├── __init__.py ├── __utils__.py ├── __version__.py ├── data_convert.py ├── data_source.py └── data_updater.py ├── poetry.lock └── pyproject.toml /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [{*.py, *.pyi, *.ipynb, *.toml}] 13 | indent_size = 4 14 | 15 | [*.md] 16 | max_line_length = off 17 | insert_final_newline = false 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 报告一个明确是 GsPanel 引起的程序错误 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | 20 | ### 运行环境 21 | 22 | 23 | - 操作系统:[例如 Ubuntu 22.04 / Windows 10 21H2] 24 | - Python 版本:[例如 3.8.10] 25 | - NoneBot2 版本:[例如 2.0.0rc3] 26 | - GsPanel 版本:[例如 0.2.19,禁止使用最新版等字样] 27 | - 其他环境版本:[根据报错位置自行决定是否有其他依赖版本需要说明。Yunzai-Bot & py-plugin 用户请说明相关环境。没有填无] 28 | 29 | 30 | ### 报错详情 31 | 32 | 33 | #### 场景 34 | 35 | - 触发命令:[例如 `面板` / `队伍伤害` / 其他] 36 | - 命令输入:[例如 `134266985` / `雷九万班@某人` / 无] 37 | - Bot 返回:[Bot 发送的文字消息内容 / 无] 38 | 39 | #### 日志 40 | 41 | ``` 42 | [此处粘贴后台日志,如果不清楚报错源头请从触发报错的消息发送开始,截至报错全部结束] 43 | ``` 44 | 45 | 46 | ### 额外信息 47 | 48 | 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 讨论 4 | url: https://github.com/monsterxcn/nonebot-plugin-gspanel/discussions 5 | about: 不适用于 Bug report & Feature request 的问题 6 | - name: nonebot-plugin-htmlrender 问题 7 | url: https://github.com/kexue-z/nonebot-plugin-htmlrender/issues?q=is%3Aissue 8 | about: Playwright 截图渲染相关 htmlrender 插件上游问题 9 | - name: py-plugin 问题 10 | url: https://gitee.com/realhuhu/py-plugin/issues?state=all 11 | about: Yunzai-Bot 用户 py-plugin 插件上游问题 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: 提出一个应当由 GsPanel 实现的有趣想法 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | 20 | ### 功能请求 21 | 22 | 23 | - 需求原因:[例如 现有的图片体积太大] 24 | - 预期功能:[例如 压缩图片体积] 25 | - 实现方案:[相关功能具体应该如何实现,针对示例可以说:使用 JPEG 图片格式、简化 HTML 模板等等。如果有参考项目可以说:参考 https://...。如果不知道可以不填] 26 | 27 | 28 | ### 额外信息 29 | 30 | 39 | -------------------------------------------------------------------------------- /.github/trans-miao-resources.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const usefulAttr = require("./artis-mark").usefulAttr; 3 | const alias = require("./alias").alias; 4 | const trans = { 5 | "hp": "生命值百分比", 6 | "atk": "攻击力百分比", 7 | "def": "防御力百分比", 8 | "cpct": "暴击率", 9 | "cdmg": "暴击伤害", 10 | "mastery": "元素精通", 11 | "dmg": "元素伤害加成", 12 | "phy": "物理伤害加成", 13 | "recharge": "元素充能效率", 14 | "heal": "治疗加成" 15 | } 16 | const attrRes = {} 17 | const aliasRes = {} 18 | 19 | Object.entries(usefulAttr).forEach(([k, v]) => { 20 | const weights = {} 21 | Object.entries(v).forEach(([propK, propV]) => { 22 | if (propV === 0) return 23 | weights[trans[propK]] = propV 24 | }) 25 | attrRes[k] = weights 26 | }) 27 | Object.entries(alias).forEach(([k, v]) => { 28 | aliasRes[k] = v.split(",") 29 | }) 30 | 31 | const rules = JSON.stringify(attrRes, null, 2); 32 | const names = JSON.stringify(aliasRes, null, 2); 33 | fs.writeFile("miao-calc-rule.json", rules, function (e, res) { 34 | if (e) console.log("error", e); 35 | }) 36 | fs.writeFile("miao-char-alias.json", names, function (e, res) { 37 | if (e) console.log("error", e); 38 | }) 39 | -------------------------------------------------------------------------------- /.github/update-resources.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | from pathlib import Path 4 | from urllib import parse, request 5 | from urllib.error import HTTPError 6 | 7 | 8 | def read(fn: str): 9 | return json.loads(Path(f"./{fn}").read_text(encoding="UTF-8")) 10 | 11 | 12 | def write(fn: str, content): 13 | Path(f"./{fn}").write_text( 14 | json.dumps(content, ensure_ascii=False, indent=2), encoding="UTF-8" 15 | ) 16 | 17 | 18 | AvatarDetail = read("Avatar.json") 19 | TextMapCHS = read("TextMapCHS.json") 20 | Avatar = read("AvatarExcelConfigData.json") 21 | AvatarSkillRaw = read("AvatarSkillExcelConfigData.json") 22 | AvatarSkillDepotRaw = read("AvatarSkillDepotExcelConfigData.json") 23 | AvatarTalentRaw = read("AvatarTalentExcelConfigData.json") 24 | AvatarCostumeRaw = read("AvatarCostumeExcelConfigData.json") 25 | Weapons = read("WeaponExcelConfigData.json") 26 | Reliquary = read("ReliquaryExcelConfigData.json") 27 | ReliquaryAffix = read("ReliquaryAffixExcelConfigData.json") 28 | ReliquarySet = read("EquipAffixExcelConfigData.json") 29 | 30 | AvatarDetail = {i["Id"]: i for i in AvatarDetail} 31 | AvatarSkill = {i["id"]: i for i in AvatarSkillRaw} 32 | AvatarSkillDepot = {i["id"]: i for i in AvatarSkillDepotRaw} 33 | AvatarTalent = {i["talentId"]: i for i in AvatarTalentRaw} 34 | AvatarCostume = {i["itemId"]: i for i in AvatarCostumeRaw if i.get("itemId")} 35 | 36 | OldCharData = read("../data/gspanel/char-data.json") 37 | OldCharAlias = read("../data/gspanel/char-alias.json") 38 | OldCalcRules = read("../data/gspanel/calc-rule.json") 39 | MiaoCharAlias = read("miao-char-alias.json") 40 | MiaoCalcRules = read("miao-calc-rule.json") 41 | MIAO_REPO = "https://raw.githubusercontent.com/yoimiya-kokomi/miao-plugin" 42 | MIAO_SPEC = MIAO_REPO + "/master/resources/meta-gs/character/{}/artis.js" 43 | PROP_TRANS = { 44 | "生命值百分比": "hp", 45 | "攻击力百分比": "atk", 46 | "防御力百分比": "def", 47 | "暴击率": "cpct", 48 | "暴击伤害": "cdmg", 49 | "元素精通": "mastery", 50 | "元素伤害加成": "dmg", 51 | "物理伤害加成": "phy", 52 | "元素充能效率": "recharge", 53 | "治疗加成": "heal", 54 | } 55 | PRE, SUF = "\033[32m", "\033[0m" 56 | 57 | 58 | def gnrtCharJson(): 59 | AvatarDictionary = {} 60 | blacklist = [ 61 | 10000001, 62 | 11000008, 63 | 11000009, 64 | 11000010, 65 | 11000011, 66 | 11000013, 67 | 11000017, 68 | 11000018, 69 | 11000019, 70 | 11000025, 71 | 11000026, 72 | 11000027, 73 | 11000028, 74 | 11000030, 75 | 11000031, 76 | 11000032, 77 | 11000033, 78 | 11000034, 79 | 11000035, 80 | 11000036, 81 | 11000037, 82 | 11000038, 83 | 11000039, 84 | 11000040, 85 | 11000041, 86 | 11000042, 87 | 11000043, 88 | 11000044, 89 | 11000045, 90 | 11000046, 91 | ] 92 | haveCostume = [list(i.values())[5] for i in AvatarCostumeRaw if i.get("itemId")] 93 | print(f"拥有时装角色列表: {haveCostume}") 94 | 95 | for avatarData in Avatar: 96 | hs = avatarData["nameTextMapHash"] 97 | if avatarData["id"] in [10000005, 10000007]: 98 | print(f"角色 {TextMapCHS.get(str(hs), '未知')} 是旅行者被跳过") 99 | continue 100 | if avatarData["id"] in blacklist: 101 | print(f"角色 {TextMapCHS.get(str(hs), '未知')} 在黑名单中被跳过") 102 | continue 103 | avatarID = avatarData["id"] 104 | depot = AvatarSkillDepot[avatarData["skillDepotId"]] 105 | if not depot.get("energySkill"): 106 | print(f"角色 {TextMapCHS.get(str(hs), '未知')} 没有技能数据被跳过") 107 | continue 108 | AvatarDictionary[avatarID] = { 109 | "Element": AvatarSkill[depot["energySkill"]]["costElemType"], 110 | "Name": str(avatarData["iconName"]).split("_")[-1], 111 | "NameCN": TextMapCHS.get(str(hs), "未知"), 112 | "Slogan": AvatarDetail[avatarID]["FetterInfo"]["Title"], 113 | "NameTextMapHash": hs, 114 | "QualityType": avatarData["qualityType"], 115 | "iconName": avatarData["iconName"], 116 | "SideIconName": avatarData["sideIconName"], 117 | "Base": { 118 | "hpBase": avatarData["hpBase"], 119 | "attackBase": avatarData["attackBase"], 120 | "defenseBase": avatarData["defenseBase"], 121 | }, 122 | "Consts": [AvatarTalent[talent]["icon"] for talent in depot["talents"]], 123 | } 124 | skills = [*depot["skills"][:2], depot["energySkill"]] 125 | AvatarDictionary[avatarID]["SkillOrder"] = skills 126 | AvatarDictionary[avatarID]["Skills"] = { 127 | str(skill): AvatarSkill[skill]["skillIcon"] for skill in skills 128 | } 129 | AvatarDictionary[avatarID]["ProudMap"] = { 130 | str(skill): AvatarSkill[skill]["proudSkillGroupId"] for skill in skills 131 | } 132 | if avatarID in haveCostume: 133 | AvatarDictionary[avatarID]["Costumes"] = {} 134 | costumes = [ 135 | i 136 | for i in AvatarCostumeRaw 137 | if list(dict(i).values())[5] == avatarID and i.get("sideIconName") 138 | ] 139 | print( 140 | "角色 {} 有 {} 件时装:{}".format( 141 | TextMapCHS.get(str(hs), "未知"), 142 | len(costumes), 143 | "/".join(x["sideIconName"].split("_")[-1] for x in costumes), 144 | ) 145 | ) 146 | AvatarDictionary[avatarID]["Costumes"] = { 147 | str(list(dict(costume).values())[0]): { 148 | "sideIconName": costume["sideIconName"], 149 | "icon": "UI_AvatarIcon_" + costume["sideIconName"].split("_")[-1], 150 | "art": "UI_Costume_" + costume["sideIconName"].split("_")[-1], 151 | "avatarId": list(dict(costume).values())[5], 152 | } 153 | for costume in costumes 154 | if costume.get("sideIconName") 155 | } 156 | 157 | print( 158 | "{}角色数据更新完成!{}{}".format( 159 | PRE, 160 | "无事发生" 161 | if len(OldCharData) == len(AvatarDictionary) 162 | else "本次更新 {} 位新角色:{}".format( 163 | len(AvatarDictionary) - len(OldCharData), 164 | "、".join( 165 | AvatarDictionary[k]["NameCN"] 166 | for k in list(AvatarDictionary.keys())[ 167 | -(len(AvatarDictionary) - len(OldCharData)) : 168 | ] 169 | ), 170 | ), 171 | SUF, 172 | ) 173 | ) 174 | write("char-data.json", AvatarDictionary) 175 | 176 | 177 | def gnrtAliasJson(): 178 | AliasDictionary = {} 179 | CharData = read("char-data.json") # after gnrtCharJson() 180 | 181 | for charId, charInfo in CharData.items(): 182 | name = charInfo["NameCN"] 183 | if OldCharAlias.get(name): 184 | AliasDictionary[name] = OldCharAlias[name] 185 | if MiaoCharAlias.get(name): 186 | extra = [a for a in MiaoCharAlias[name] if a not in OldCharAlias[name]] 187 | print(f"角色「{name}」可选别名:{'、'.join(extra)}") 188 | elif MiaoCharAlias.get(name): 189 | newCharAlias = [ 190 | a for a in MiaoCharAlias[name] if not re.match(r"^[a-zA-Z\s]+$", a) 191 | ] 192 | AliasDictionary[name] = newCharAlias 193 | print(f"新增角色「{name}」别名:{'、'.join(newCharAlias)}") 194 | else: 195 | AliasDictionary[name] = [] 196 | print(f"角色「{name}」还没有别名数据!") 197 | 198 | print(f"{PRE}角色别名更新完成!{SUF}") 199 | write("char-alias.json", AliasDictionary) 200 | 201 | 202 | def gnrtRuleJson(): 203 | RuleDictionary = {} 204 | CharData = read("char-data.json") # after gnrtCharJson() 205 | 206 | oldRulesKeys = {} 207 | for ruleKey, ruleInfo in OldCalcRules.items(): 208 | name = ruleKey.split("-")[0] 209 | oldRulesKeys[name] = oldRulesKeys.get(name, []) + [ruleKey] 210 | 211 | for charId, charInfo in CharData.items(): 212 | name = charInfo["NameCN"] 213 | rulesGot = [] 214 | 215 | if OldCalcRules.get(name): 216 | RuleDictionary[name] = OldCalcRules[name] 217 | rulesGot.append(name) 218 | if MiaoCalcRules.get(name): 219 | RuleDictionary[name].update(MiaoCalcRules[name]) 220 | if RuleDictionary[name] != OldCalcRules[name]: 221 | print(f"角色「{name}」评分规则变动") 222 | elif MiaoCalcRules.get(name): 223 | RuleDictionary[name] = MiaoCalcRules[name] 224 | rulesGot.append(name) 225 | print(f"新增角色「{name}」评分规则") 226 | else: 227 | print(f"角色「{name}」还没有评分规则数据!") 228 | 229 | try: 230 | url = parse.quote(MIAO_SPEC.format(name), safe=":/") 231 | response = request.urlopen(url) 232 | content = response.read().decode("UTF-8") 233 | pattern = r"return rule\('(.+?)', (\{.+?\})\)" 234 | matches = re.findall(pattern, content) 235 | for ruleName, ruleStr in matches: 236 | ruleName = f"{name}-{ruleName.split('-')[-1]}" 237 | ruleInfo = json.loads(re.sub(r"(\w+):", r'"\1":', ruleStr)) 238 | RuleDictionary[ruleName] = { 239 | k: ruleInfo[v] 240 | for k, v in PROP_TRANS.items() 241 | if ruleInfo.get(PROP_TRANS[k]) 242 | } 243 | rulesGot.append(ruleName) 244 | if ruleName in oldRulesKeys.get(name, []): 245 | if RuleDictionary[ruleName] != OldCalcRules[ruleName]: 246 | print(f"角色「{name}」{ruleName.split('-')[-1]}评分规则变动") 247 | else: 248 | print(f"新增角色「{name}」{ruleName.split('-')[-1]}评分规则") 249 | except HTTPError as e: 250 | if str(e) == "HTTP Error 404: Not Found": 251 | pass 252 | 253 | for ruleName in [r for r in oldRulesKeys.get(name, []) if r not in rulesGot]: 254 | RuleDictionary[ruleName] = OldCalcRules[ruleName] 255 | print(f"角色「{name}」{ruleName.split('-')[-1]}评分规则继承") 256 | 257 | print( 258 | "{}角色评分规则更新完成!{}{}".format( 259 | PRE, 260 | "无事发生" 261 | if len(RuleDictionary) == len(OldCalcRules) 262 | else f"新增 {len(RuleDictionary) - len(OldCalcRules)} 条", 263 | SUF, 264 | ) 265 | ) 266 | write("calc-rule.json", RuleDictionary) 267 | 268 | 269 | def gnrtTransJson(): 270 | TranslateDictionary = {} 271 | for avatarData in Avatar: 272 | hs = str(avatarData.get("nameTextMapHash", 0)) 273 | if TextMapCHS.get(hs): 274 | TranslateDictionary[hs] = TextMapCHS[hs] 275 | for weapon in Weapons: 276 | hs = str(weapon.get("nameTextMapHash", 0)) 277 | if TextMapCHS.get(hs): 278 | TranslateDictionary[hs] = TextMapCHS[hs] 279 | for set in ReliquarySet: 280 | hs = str(set.get("nameTextMapHash", 0)) 281 | if str(set.get("openConfig")).startswith("Rel") and TextMapCHS.get(hs): 282 | TranslateDictionary[hs] = TextMapCHS[hs] 283 | for reliquary in Reliquary: 284 | hs = str(reliquary.get("nameTextMapHash", 0)) 285 | if TextMapCHS.get(hs): 286 | TranslateDictionary[hs] = TextMapCHS[hs] 287 | 288 | print(f"{PRE}文本翻译更新完成!{SUF}") 289 | write("hash-trans.json", TranslateDictionary) 290 | 291 | 292 | def gnrtAppendJson() -> None: 293 | PropDictionary = {str(x["id"]): x["propType"] for x in ReliquaryAffix} 294 | print(f"{PRE}圣遗物词条更新完成!{SUF}") 295 | write("relic-append.json", PropDictionary) 296 | 297 | 298 | gnrtCharJson() 299 | gnrtAliasJson() 300 | gnrtRuleJson() 301 | gnrtTransJson() 302 | gnrtAppendJson() 303 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish distributions 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | name: Publish distributions 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | 14 | - name: Install poetry 15 | run: pipx install poetry 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: '3.8' 21 | cache: 'poetry' 22 | 23 | - name: Install dependencies 24 | run: poetry install 25 | 26 | - name: Build package 27 | run: poetry build 28 | 29 | - name: Publish package 30 | uses: pypa/gh-action-pypi-publish@release/v1 31 | with: 32 | user: __token__ 33 | password: ${{ secrets.PYPI_API_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/update-resources.yml: -------------------------------------------------------------------------------- 1 | name: Update resources 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | update: 8 | name: Update resources 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repo 12 | uses: actions/checkout@master 13 | 14 | - name: Setup node.js 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 16.x 18 | 19 | - name: Setup python 20 | uses: actions/setup-python@v1 21 | with: 22 | python-version: 3.8 23 | 24 | - name: Update files 25 | env: 26 | GAMEDATA: "https://gitlab.com/Dimbreath/AnimeGameData/-/raw/master/" 27 | run: | 28 | cd .github 29 | wget -q "${GAMEDATA}/ExcelBinOutput/AvatarCostumeExcelConfigData.json" 30 | wget -q "${GAMEDATA}/ExcelBinOutput/AvatarExcelConfigData.json" 31 | wget -q "${GAMEDATA}/ExcelBinOutput/AvatarSkillExcelConfigData.json" 32 | wget -q "${GAMEDATA}/ExcelBinOutput/AvatarSkillDepotExcelConfigData.json" 33 | wget -q "${GAMEDATA}/ExcelBinOutput/AvatarTalentExcelConfigData.json" 34 | wget -q "${GAMEDATA}/ExcelBinOutput/EquipAffixExcelConfigData.json" 35 | wget -q "${GAMEDATA}/ExcelBinOutput/ReliquaryExcelConfigData.json" 36 | wget -q "${GAMEDATA}/ExcelBinOutput/ReliquaryAffixExcelConfigData.json" 37 | wget -q "${GAMEDATA}/ExcelBinOutput/WeaponExcelConfigData.json" 38 | wget -q "${GAMEDATA}/TextMap/TextMapCHS.json" 39 | wget -q https://github.com/DGP-Studio/Snap.Metadata/raw/main/Genshin/CHS/Avatar.json 40 | wget -q https://github.com/yoimiya-kokomi/miao-plugin/raw/master/resources/meta-gs/artifact/artis-mark.js 41 | wget -q https://github.com/yoimiya-kokomi/miao-plugin/raw/master/resources/meta-gs/character/alias.js 42 | sed -i 's/export const usefulAttr =/module.exports = { usefulAttr:/' artis-mark.js 43 | sed -i '$s/$/ }/' artis-mark.js 44 | sed -i 's/export const alias =/module.exports = { alias:/' alias.js 45 | sed -i '$s/$/ }/' alias.js 46 | node trans-miao-resources.js 47 | python update-resources.py 48 | mv -f calc-rule.json ../data/gspanel/calc-rule.json 49 | mv -f char-alias.json ../data/gspanel/char-alias.json 50 | mv -f char-data.json ../data/gspanel/char-data.json 51 | mv -f hash-trans.json ../data/gspanel/hash-trans.json 52 | mv -f relic-append.json ../data/gspanel/relic-append.json 53 | 54 | - name: Upload files 55 | uses: tvrcgo/upload-to-oss@master 56 | with: 57 | key-id: ${{ secrets.OSS_KEY_ID }} 58 | key-secret: ${{ secrets.OSS_KEY_SECRET }} 59 | region: oss-cn-shanghai 60 | bucket: monsterx 61 | assets: | 62 | data/gspanel/calc-rule.json:/bot/gspanel/calc-rule.json 63 | data/gspanel/char-alias.json:/bot/gspanel/char-alias.json 64 | data/gspanel/char-data.json:/bot/gspanel/char-data.json 65 | data/gspanel/hash-trans.json:/bot/gspanel/hash-trans.json 66 | data/gspanel/relic-append.json:/bot/gspanel/relic-append.json 67 | 68 | - name: Commit changes 69 | uses: EndBug/add-and-commit@v9 70 | with: 71 | author_name: github-actions[bot] 72 | author_email: github-actions[bot]@users.noreply.github.com 73 | message: ':wrench: 自动更新游戏数据' 74 | add: | 75 | 'data/gspanel/calc-rule.json' 76 | 'data/gspanel/char-alias.json' 77 | 'data/gspanel/char-data.json' 78 | 'data/gspanel/hash-trans.json' 79 | 'data/gspanel/relic-append.json' 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .github/artis-mark.js 2 | .github/gamedata 3 | .venv 4 | test 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | ci: 2 | autofix_commit_msg: ":rotating_light: [pre-commit] 自动修复" 3 | autofix_prs: true 4 | autoupdate_branch: master 5 | autoupdate_schedule: monthly 6 | autoupdate_commit_msg: ":arrow_up: [pre-commit] 自动升级" 7 | 8 | repos: 9 | 10 | - repo: https://github.com/hadialqattan/pycln 11 | rev: v2.1.3 12 | hooks: 13 | - id: pycln 14 | args: [--config, pyproject.toml] 15 | 16 | - repo: https://github.com/pycqa/isort 17 | rev: 5.12.0 18 | hooks: 19 | - id: isort 20 | 21 | - repo: https://github.com/psf/black 22 | rev: 23.1.0 23 | hooks: 24 | - id: black 25 | 26 | - repo: https://github.com/pycqa/flake8 27 | rev: 6.0.0 28 | hooks: 29 | - id: flake8 30 | additional_dependencies: [Flake8-pyproject] 31 | 32 | - repo: https://github.com/asottile/pyupgrade 33 | rev: v3.3.1 34 | hooks: 35 | - id: pyupgrade 36 | args: [--py38-plus] 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.completeFunctionParens": true, 3 | "python.analysis.typeCheckingMode": "basic", 4 | "python.analysis.diagnosticSeverityOverrides": { 5 | "reportPrivateImportUsage": "warning" 6 | }, 7 | "python.formatting.provider": "black", 8 | "python.languageServer": "Pylance", 9 | "[python]": { 10 | "editor.codeActionsOnSave": { 11 | "source.organizeImports": true, 12 | "source.fixAll": true 13 | } 14 | }, 15 | "editor.formatOnSave": true, 16 | "files.watcherExclude": { 17 | "**/.git/objects/**": true, 18 | "**/.git/subtree-cache/**": true, 19 | "**/.venv/**": true, 20 | }, 21 | "git.autofetch": true, 22 | "git.enableSmartCommit": true, 23 | "python.analysis.diagnosticMode": "workspace", 24 | "python.linting.flake8Enabled": true, 25 | "python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe", 26 | "python.formatting.blackPath": "${workspaceFolder}/.venv/Scripts/black.exe", 27 | "python.linting.flake8Path": "${workspaceFolder}/.venv/Scripts/flake8.exe", 28 | "python.formatting.blackArgs": [ 29 | "--line-length=88" 30 | ], 31 | "python.linting.flake8Args": [ 32 | "--ignore=W391,W292,W503,E203" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 monsterxcn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

NoneBot Plugin GsPanel


2 | 3 | 4 |

🤖 用于展示原神游戏内角色展柜数据的 NoneBot2 插件


5 | 6 | 7 |

8 | license 9 | pypi 10 | python 11 | QQ Chat Group
12 | Code style: black 13 | Imports: isort 14 | Lint: flake8 15 | pre-commit 16 |


17 | 18 | 19 | | ![琴](https://user-images.githubusercontent.com/22407052/201662130-2b3bdcd3-acaa-4b59-9c88-3e50fa1887f3.PNG) | ![刻晴](https://user-images.githubusercontent.com/22407052/201661930-f9ecdfe0-e278-4641-a012-cf090da6b6c7.PNG) | ![妮露](https://user-images.githubusercontent.com/22407052/201667744-decfdf25-c889-4a65-bbe0-94e194fe8d82.PNG) | 20 | |:--:|:--:|:--:| 21 | 22 | 23 | ## 安装方法 24 | 25 | 26 | 如果你正在使用 2.0.0.beta1 以上版本 NoneBot2,推荐使用以下命令安装: 27 | 28 | 29 | ```bash 30 | # 从 nb_cli 安装 31 | nb plugin install nonebot-plugin-gspanel 32 | ``` 33 | 34 | 35 | > Yunzai [@realhuhu/py-plugin](https://github.com/realhuhu/py-plugin) 插件用户安装方法请查看 [#38](https://github.com/monsterxcn/nonebot-plugin-gspanel/discussions/38),插件不保证完全可用,请尽量自行解决相关问题。 36 | 37 | 38 | ## 使用须知 39 | 40 | 41 | - 插件的圣遗物评分计算规则、卡片样式均来自 [@yoimiya-kokomi/miao-plugin](https://github.com/yoimiya-kokomi/miao-plugin)。插件移植时对 **评分规则** 主要做了以下修改: 42 | 43 | + 以角色生命值、攻击力、防御力的实际基础值进行词条得分计算,导致固定值的生命值、攻击力、防御力词条评分相较原版有小幅度波动 44 | + 于面板数据区域展示圣遗物评分使用的词条权重规则,插件尚未自定义词条权重规则的角色使用默认规则(攻击力 `75`、暴击率 `100`、暴击伤害 `100`) 45 | + 于面板数据区域展示角色最高的伤害加成数据,该属性与角色实际伤害属性不一致时区别显示词条权重规则 46 | + 对元素属性异常的空之杯进行评分惩罚,扣除该圣遗物总分的 50%(最大扣除比例) 47 | 48 | - 插件返回「暂时无法访问面板数据接口..」可能的原因有:Bot 与 [Enka.Network](https://enka.network/) 的连接不稳定;[Enka.Network](https://enka.network/) 服务器暂时故障等。 49 | 50 | - 插件首次生成某个角色的面板图片时,会尝试从 [Enka.Network](https://enka.network/) 下载该角色的抽卡大图、命座图片、技能图片、圣遗物及武器图片等素材图片,生成面板图片的时间由 Bot 与 [Enka.Network](https://enka.network/) 的连接质量决定。素材图片下载至本地后将不再从远程下载,生成面板图片的时间将大幅缩短。 51 | 52 | - 一般来说,插件安装完成后无需设置环境变量,只需重启 Bot 即可开始使用。你也可以在 NoneBot2 当前使用的 `.env` 文件中添加下表给出的环境变量,对插件进行更多配置。环境变量修改后需要重启 Bot 才能生效。 53 | 54 | | 环境变量 | 必需 | 默认 | 说明 | 55 | |:-------|:----:|:-----|:----| 56 | | `gspanel_alias` | 否 | `["面板"]` | 插件响应词别名,多个别名按 `["面面", "板板"]` 格式填写 | 57 | | `gspanel_scale` | 否 | `1.5` | 浏览器缩放比例,此值越大返回图片的分辨率越高 | 58 | | `resources_dir` | 否 | `/path/to/bot/data/` | 插件数据缓存目录的父文件夹,包含 `gspanel` 文件夹的上级文件夹路径 | 59 | | `resources_mirror` | 否 | `https://enka.network/ui/` | 素材图片下载镜像,需提供 `UI_Talent_S_Nilou_01.png` 形式的图片地址,可选镜像:
`https://api.ambr.top/assets/UI/`(安柏计划)
`https://cdn.monsterx.cn/genshin/`(插件作者) | 60 | 61 | - 插件图片生成采用 [@kexue-z/nonebot-plugin-htmlrender](https://github.com/kexue-z/nonebot-plugin-htmlrender),若插件自动安装运行 Chromium 所需的额外依赖失败,请参考 [@SK-415/HarukaBot](https://haruka-bot.sk415.icu/faq.html#playwright-%E4%BE%9D%E8%B5%96%E4%B8%8D%E5%85%A8) 给出的以下解决方案: 62 | 63 | + Ubuntu:`python3 -m playwright install-deps` 64 | + CentOS(仅供参考):`yum install -y atk at-spi2-atk cups-libs libxkbcommon libXcomposite libXdamage libXrandr mesa-libgbm gtk3` 65 | + 其他非 Ubuntu 系统:[@microsoft/playwright/issues](https://github.com/microsoft/playwright/issues) 66 | 67 | 其他 Playwright 相关问题也请尽量自行解决,或者前往 [@kexue-z/nonebot-plugin-htmlrender/issues](https://github.com/kexue-z/nonebot-plugin-htmlrender) / [@microsoft/playwright/issues](https://github.com/microsoft/playwright/issues) 搜索提问。~~你硬要问我的话,大概也只能得到一句「哇嘎拉乃哟」~~ 68 | 69 | 70 | ## 命令说明 71 | 72 | 73 | ### 角色面板 74 | 75 | 76 | 插件响应以 `panel` / `面板` 开头的消息,下面仅以 `面板` 为例: 77 | 78 | 79 | *\* 如果定义了环境变量 `gspanel_alias` 则以环境变量定义的命令别名为准,默认情况下该环境变量会使插件响应 `面板` 开头的消息。* 80 | 81 | 82 | - `面板绑定100123456` / `面板绑定100123456 @某人` / `面板绑定2334556789 100123456` 83 | 84 | 绑定 UID `100123456` 至发送此指令的 QQ,QQ 已被绑定过则会更新绑定的 UID。 85 | 86 | Bot 管理员可以通过在此指令后紧跟 `2334556789` 或附带 `@某人` 的方式将 UID `100123456` 绑定至指定的 QQ。 87 | 88 | - `面板` / `面板@某人` / `面板100123456` 89 | 90 | 查找 QQ 绑定的 UID / UID `100123456` 角色展柜中展示的所有角色(图片)。 91 | 92 | - `面板夜兰` / `面板夜兰@某人` / `面板夜兰100123456` / `面板100123456夜兰` 93 | 94 | 查找 QQ 绑定的 UID / UID `100123456` 的夜兰面板(图片)。 95 | 96 | 97 | *\* 所有指令都可以用空格将关键词分割开来,如果你喜欢的话。* 98 | 99 | 100 | ### 队伍伤害 101 | 102 | 103 | 插件响应以 `teamdmg` / `队伍伤害` 开头的消息,下面仅以 `队伍伤害` 为例: 104 | 105 | 106 | - `队伍伤害` / `队伍伤害100123456` / `队伍伤害@某人` 107 | 108 | 查找指定 UID 角色展柜中前四个角色组成的队伍伤害。 109 | 110 | 默认隐藏了伤害过程表格,如需查看具体伤害过程可以使用 `队伍伤害详情` / `队伍伤害过程` / `队伍伤害全图` 来强制显示全部数据(并不是单独返回伤害过程表格)。 111 | 112 | 当仅发送 `队伍伤害` 时将尝试使用发送此指令的 QQ 绑定的 UID;附带 9 位数字时尝试使用该 UID;附带 `@某人` 时将尝试使用指定 QQ 绑定的 UID。 113 | 114 | - `队伍伤害雷九万班` / `队伍伤害 雷神 九条 万叶 班尼特` / `队伍伤害雷神 九条 万叶 班尼特@某人` 115 | 116 | 查找雷电将军、九条裟罗、枫原万叶、班尼特组成的队伍伤害。注意角色名之间必须使用空格分开。含有 **旅行者** 的配队暂时无法查询。队伍角色只要使用 `面板` 指令查询过或者正在展柜中摆放即可配队(即所有查询过的角色都有缓存,使用 `面板` 指令查看所有可用的角色)。 117 | 118 | 为此形式的命令指定 UID 方式与上面相同。 119 | 120 | 队伍别名支持可能不全请见谅,如果有十分流行的配队未能支持请前往 [discussions](https://github.com/monsterxcn/nonebot-plugin-gspanel/discussions) 提出。 121 | 122 | 123 | *\* 队伍伤害为 **实验性功能**,计算结果可能存在问题。欢迎附带详细日志提交 issue 帮助改进此功能。* 124 | 125 | 126 | ## 特别鸣谢 127 | 128 | 129 | [@nonebot/nonebot2](https://github.com/nonebot/nonebot2/) | [@Mrs4s/go-cqhttp](https://github.com/Mrs4s/go-cqhttp) | [@yoimiya-kokomi/miao-plugin](https://github.com/yoimiya-kokomi/miao-plugin) | [@kexue-z/nonebot-plugin-htmlrender](https://github.com/kexue-z/nonebot-plugin-htmlrender) | [@UIGF-org/UIGF-API](https://github.com/UIGF-org/UIGF-API) | [@DGP-Studio/Snap.Metadata](https://github.com/DGP-Studio/Snap.Metadata) | [Enka.Network](https://enka.network/) | [Miniprogram Teyvat Helper](#) | [@MiniGrayGay](https://github.com/MiniGrayGay) 130 | -------------------------------------------------------------------------------- /data/gspanel/calc-rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "神里绫华": { 3 | "攻击力百分比": 85, 4 | "暴击率": 100, 5 | "暴击伤害": 100, 6 | "元素伤害加成": 100, 7 | "元素充能效率": 30 8 | }, 9 | "神里绫华-精通": { 10 | "攻击力百分比": 75, 11 | "暴击率": 100, 12 | "暴击伤害": 100, 13 | "元素精通": 75, 14 | "元素伤害加成": 100, 15 | "元素充能效率": 30 16 | }, 17 | "琴": { 18 | "攻击力百分比": 85, 19 | "暴击率": 50, 20 | "暴击伤害": 50, 21 | "元素伤害加成": 80, 22 | "物理伤害加成": 100, 23 | "元素充能效率": 55, 24 | "治疗加成": 100 25 | }, 26 | "丽莎": { 27 | "攻击力百分比": 75, 28 | "暴击率": 100, 29 | "暴击伤害": 100, 30 | "元素精通": 75, 31 | "元素伤害加成": 100 32 | }, 33 | "芭芭拉": { 34 | "生命值百分比": 100, 35 | "攻击力百分比": 50, 36 | "暴击率": 50, 37 | "暴击伤害": 50, 38 | "元素伤害加成": 80, 39 | "元素充能效率": 55, 40 | "治疗加成": 100 41 | }, 42 | "芭芭拉-暴力": { 43 | "生命值百分比": 50, 44 | "攻击力百分比": 75, 45 | "暴击率": 100, 46 | "暴击伤害": 100, 47 | "元素精通": 75, 48 | "元素伤害加成": 100, 49 | "元素充能效率": 30, 50 | "治疗加成": 50 51 | }, 52 | "凯亚": { 53 | "攻击力百分比": 75, 54 | "暴击率": 100, 55 | "暴击伤害": 100, 56 | "元素伤害加成": 100, 57 | "物理伤害加成": 100, 58 | "元素充能效率": 30 59 | }, 60 | "迪卢克": { 61 | "攻击力百分比": 75, 62 | "暴击率": 100, 63 | "暴击伤害": 100, 64 | "元素精通": 75, 65 | "元素伤害加成": 100 66 | }, 67 | "雷泽": { 68 | "攻击力百分比": 75, 69 | "暴击率": 100, 70 | "暴击伤害": 100, 71 | "元素伤害加成": 100, 72 | "物理伤害加成": 100 73 | }, 74 | "安柏": { 75 | "攻击力百分比": 75, 76 | "暴击率": 100, 77 | "暴击伤害": 100, 78 | "元素精通": 75, 79 | "元素伤害加成": 100, 80 | "物理伤害加成": 100 81 | }, 82 | "温迪": { 83 | "攻击力百分比": 75, 84 | "暴击率": 80, 85 | "暴击伤害": 80, 86 | "元素精通": 75, 87 | "元素伤害加成": 100, 88 | "元素充能效率": 55 89 | }, 90 | "温迪-充能": { 91 | "攻击力百分比": 75, 92 | "暴击率": 100, 93 | "暴击伤害": 100, 94 | "元素精通": 75, 95 | "元素伤害加成": 100, 96 | "元素充能效率": 100 97 | }, 98 | "香菱": { 99 | "攻击力百分比": 75, 100 | "暴击率": 100, 101 | "暴击伤害": 100, 102 | "元素精通": 75, 103 | "元素伤害加成": 100, 104 | "元素充能效率": 55 105 | }, 106 | "北斗": { 107 | "攻击力百分比": 75, 108 | "暴击率": 100, 109 | "暴击伤害": 100, 110 | "元素精通": 75, 111 | "元素伤害加成": 100, 112 | "元素充能效率": 55 113 | }, 114 | "行秋": { 115 | "攻击力百分比": 75, 116 | "暴击率": 100, 117 | "暴击伤害": 100, 118 | "元素伤害加成": 100, 119 | "元素充能效率": 75 120 | }, 121 | "行秋-蒸发": { 122 | "攻击力百分比": 75, 123 | "暴击率": 100, 124 | "暴击伤害": 100, 125 | "元素精通": 75, 126 | "元素伤害加成": 100, 127 | "元素充能效率": 75 128 | }, 129 | "魈": { 130 | "攻击力百分比": 75, 131 | "暴击率": 100, 132 | "暴击伤害": 100, 133 | "元素伤害加成": 100, 134 | "元素充能效率": 55 135 | }, 136 | "凝光": { 137 | "攻击力百分比": 75, 138 | "暴击率": 100, 139 | "暴击伤害": 100, 140 | "元素伤害加成": 100, 141 | "元素充能效率": 30 142 | }, 143 | "可莉": { 144 | "攻击力百分比": 75, 145 | "暴击率": 100, 146 | "暴击伤害": 100, 147 | "元素精通": 75, 148 | "元素伤害加成": 100, 149 | "元素充能效率": 30 150 | }, 151 | "可莉-纯火": { 152 | "攻击力百分比": 85, 153 | "暴击率": 100, 154 | "暴击伤害": 100, 155 | "元素伤害加成": 100, 156 | "元素充能效率": 55 157 | }, 158 | "钟离": { 159 | "生命值百分比": 80, 160 | "攻击力百分比": 75, 161 | "暴击率": 100, 162 | "暴击伤害": 100, 163 | "元素伤害加成": 100, 164 | "物理伤害加成": 50, 165 | "元素充能效率": 55 166 | }, 167 | "钟离-血牛": { 168 | "生命值百分比": 100, 169 | "攻击力百分比": 30, 170 | "暴击率": 41, 171 | "暴击伤害": 41, 172 | "元素充能效率": 30 173 | }, 174 | "菲谢尔": { 175 | "攻击力百分比": 75, 176 | "暴击率": 100, 177 | "暴击伤害": 100, 178 | "元素精通": 75, 179 | "元素伤害加成": 100, 180 | "物理伤害加成": 60 181 | }, 182 | "班尼特": { 183 | "生命值百分比": 100, 184 | "攻击力百分比": 50, 185 | "暴击率": 50, 186 | "暴击伤害": 50, 187 | "元素伤害加成": 80, 188 | "元素充能效率": 75, 189 | "治疗加成": 100 190 | }, 191 | "达达利亚": { 192 | "攻击力百分比": 75, 193 | "暴击率": 100, 194 | "暴击伤害": 100, 195 | "元素精通": 75, 196 | "元素伤害加成": 100, 197 | "元素充能效率": 30 198 | }, 199 | "诺艾尔": { 200 | "攻击力百分比": 50, 201 | "防御力百分比": 90, 202 | "暴击率": 100, 203 | "暴击伤害": 100, 204 | "元素伤害加成": 100, 205 | "元素充能效率": 70 206 | }, 207 | "七七": { 208 | "攻击力百分比": 75, 209 | "暴击率": 30, 210 | "暴击伤害": 30, 211 | "元素伤害加成": 100, 212 | "物理伤害加成": 100, 213 | "元素充能效率": 55, 214 | "治疗加成": 100 215 | }, 216 | "重云": { 217 | "攻击力百分比": 75, 218 | "暴击率": 100, 219 | "暴击伤害": 100, 220 | "元素精通": 75, 221 | "元素伤害加成": 100, 222 | "元素充能效率": 55 223 | }, 224 | "甘雨": { 225 | "攻击力百分比": 75, 226 | "暴击率": 100, 227 | "暴击伤害": 100, 228 | "元素精通": 75, 229 | "元素伤害加成": 100 230 | }, 231 | "甘雨-永冻": { 232 | "攻击力百分比": 75, 233 | "暴击率": 100, 234 | "暴击伤害": 100, 235 | "元素伤害加成": 100, 236 | "元素充能效率": 55 237 | }, 238 | "阿贝多": { 239 | "防御力百分比": 100, 240 | "暴击率": 100, 241 | "暴击伤害": 100, 242 | "元素伤害加成": 100 243 | }, 244 | "迪奥娜": { 245 | "生命值百分比": 100, 246 | "攻击力百分比": 50, 247 | "暴击率": 50, 248 | "暴击伤害": 50, 249 | "元素伤害加成": 80, 250 | "元素充能效率": 90, 251 | "治疗加成": 100 252 | }, 253 | "莫娜": { 254 | "攻击力百分比": 75, 255 | "暴击率": 100, 256 | "暴击伤害": 100, 257 | "元素精通": 75, 258 | "元素伤害加成": 100, 259 | "元素充能效率": 75 260 | }, 261 | "刻晴": { 262 | "攻击力百分比": 75, 263 | "暴击率": 100, 264 | "暴击伤害": 100, 265 | "元素精通": 75, 266 | "元素伤害加成": 100, 267 | "物理伤害加成": 100 268 | }, 269 | "刻晴-精通": { 270 | "攻击力百分比": 75, 271 | "暴击率": 100, 272 | "暴击伤害": 100, 273 | "元素精通": 75, 274 | "元素伤害加成": 100 275 | }, 276 | "砂糖": { 277 | "攻击力百分比": 50, 278 | "暴击率": 50, 279 | "暴击伤害": 50, 280 | "元素精通": 100, 281 | "元素伤害加成": 80, 282 | "元素充能效率": 75 283 | }, 284 | "辛焱": { 285 | "攻击力百分比": 75, 286 | "防御力百分比": 75, 287 | "暴击率": 100, 288 | "暴击伤害": 100, 289 | "元素伤害加成": 100, 290 | "物理伤害加成": 100 291 | }, 292 | "罗莎莉亚": { 293 | "攻击力百分比": 75, 294 | "暴击率": 100, 295 | "暴击伤害": 100, 296 | "元素伤害加成": 70, 297 | "物理伤害加成": 80, 298 | "元素充能效率": 30 299 | }, 300 | "胡桃": { 301 | "生命值百分比": 80, 302 | "攻击力百分比": 50, 303 | "暴击率": 100, 304 | "暴击伤害": 100, 305 | "元素精通": 75, 306 | "元素伤害加成": 100 307 | }, 308 | "胡桃-核爆": { 309 | "生命值百分比": 90, 310 | "攻击力百分比": 50, 311 | "暴击伤害": 100, 312 | "元素精通": 90, 313 | "元素伤害加成": 100 314 | }, 315 | "枫原万叶": { 316 | "攻击力百分比": 50, 317 | "暴击率": 50, 318 | "暴击伤害": 50, 319 | "元素精通": 100, 320 | "元素伤害加成": 80, 321 | "元素充能效率": 55 322 | }, 323 | "枫原万叶-满命": { 324 | "攻击力百分比": 75, 325 | "暴击率": 100, 326 | "暴击伤害": 100, 327 | "元素精通": 100, 328 | "元素伤害加成": 100, 329 | "元素充能效率": 55 330 | }, 331 | "烟绯": { 332 | "攻击力百分比": 75, 333 | "暴击率": 100, 334 | "暴击伤害": 100, 335 | "元素精通": 75, 336 | "元素伤害加成": 100, 337 | "元素充能效率": 30 338 | }, 339 | "宵宫": { 340 | "攻击力百分比": 75, 341 | "暴击率": 100, 342 | "暴击伤害": 100, 343 | "元素精通": 75, 344 | "元素伤害加成": 100 345 | }, 346 | "宵宫-纯火": { 347 | "攻击力百分比": 85, 348 | "暴击率": 100, 349 | "暴击伤害": 100, 350 | "元素伤害加成": 100 351 | }, 352 | "宵宫-精通": { 353 | "攻击力百分比": 75, 354 | "暴击率": 100, 355 | "暴击伤害": 100, 356 | "元素精通": 100, 357 | "元素伤害加成": 100 358 | }, 359 | "托马": { 360 | "生命值百分比": 100, 361 | "攻击力百分比": 50, 362 | "暴击率": 50, 363 | "暴击伤害": 50, 364 | "元素精通": 75, 365 | "元素伤害加成": 80, 366 | "元素充能效率": 75 367 | }, 368 | "优菈": { 369 | "攻击力百分比": 75, 370 | "暴击率": 100, 371 | "暴击伤害": 100, 372 | "元素伤害加成": 40, 373 | "物理伤害加成": 100, 374 | "元素充能效率": 55 375 | }, 376 | "优菈-核爆": { 377 | "攻击力百分比": 100, 378 | "暴击伤害": 100, 379 | "物理伤害加成": 100 380 | }, 381 | "雷电将军": { 382 | "攻击力百分比": 75, 383 | "暴击率": 100, 384 | "暴击伤害": 100, 385 | "元素伤害加成": 75, 386 | "元素充能效率": 90 387 | }, 388 | "雷电将军-精通": { 389 | "攻击力百分比": 75, 390 | "暴击率": 90, 391 | "暴击伤害": 90, 392 | "元素精通": 100, 393 | "元素伤害加成": 75, 394 | "元素充能效率": 90 395 | }, 396 | "雷电将军-高精": { 397 | "攻击力百分比": 90, 398 | "暴击率": 100, 399 | "暴击伤害": 100, 400 | "元素伤害加成": 90, 401 | "元素充能效率": 90 402 | }, 403 | "早柚": { 404 | "攻击力百分比": 75, 405 | "暴击率": 50, 406 | "暴击伤害": 50, 407 | "元素精通": 100, 408 | "元素伤害加成": 100, 409 | "元素充能效率": 55, 410 | "治疗加成": 100 411 | }, 412 | "珊瑚宫心海": { 413 | "生命值百分比": 100, 414 | "攻击力百分比": 50, 415 | "元素精通": 75, 416 | "元素伤害加成": 100, 417 | "元素充能效率": 55, 418 | "治疗加成": 100 419 | }, 420 | "五郎": { 421 | "攻击力百分比": 75, 422 | "防御力百分比": 100, 423 | "暴击率": 50, 424 | "暴击伤害": 50, 425 | "元素伤害加成": 100, 426 | "元素充能效率": 75 427 | }, 428 | "九条裟罗": { 429 | "攻击力百分比": 75, 430 | "暴击率": 50, 431 | "暴击伤害": 50, 432 | "元素伤害加成": 100, 433 | "元素充能效率": 100 434 | }, 435 | "荒泷一斗": { 436 | "攻击力百分比": 50, 437 | "防御力百分比": 100, 438 | "暴击率": 100, 439 | "暴击伤害": 100, 440 | "元素伤害加成": 100, 441 | "元素充能效率": 30 442 | }, 443 | "八重神子": { 444 | "攻击力百分比": 75, 445 | "暴击率": 100, 446 | "暴击伤害": 100, 447 | "元素精通": 75, 448 | "元素伤害加成": 75, 449 | "元素充能效率": 55 450 | }, 451 | "鹿野院平藏": { 452 | "攻击力百分比": 75, 453 | "暴击率": 100, 454 | "暴击伤害": 100, 455 | "元素精通": 75, 456 | "元素伤害加成": 100, 457 | "元素充能效率": 30 458 | }, 459 | "夜兰": { 460 | "生命值百分比": 80, 461 | "暴击率": 100, 462 | "暴击伤害": 100, 463 | "元素伤害加成": 100, 464 | "元素充能效率": 55 465 | }, 466 | "绮良良": { 467 | "生命值百分比": 100, 468 | "攻击力百分比": 50, 469 | "暴击率": 50, 470 | "暴击伤害": 50, 471 | "元素伤害加成": 80 472 | }, 473 | "埃洛伊": { 474 | "攻击力百分比": 75, 475 | "暴击率": 100, 476 | "暴击伤害": 100, 477 | "元素伤害加成": 100 478 | }, 479 | "申鹤": { 480 | "攻击力百分比": 100, 481 | "暴击率": 50, 482 | "暴击伤害": 50, 483 | "元素伤害加成": 80, 484 | "元素充能效率": 75 485 | }, 486 | "云堇": { 487 | "攻击力百分比": 75, 488 | "防御力百分比": 100, 489 | "暴击率": 80, 490 | "暴击伤害": 80, 491 | "元素伤害加成": 80, 492 | "元素充能效率": 80 493 | }, 494 | "云堇-输出": { 495 | "攻击力百分比": 75, 496 | "防御力百分比": 100, 497 | "暴击率": 100, 498 | "暴击伤害": 100, 499 | "元素伤害加成": 100, 500 | "元素充能效率": 75 501 | }, 502 | "久岐忍": { 503 | "生命值百分比": 100, 504 | "攻击力百分比": 50, 505 | "暴击率": 50, 506 | "暴击伤害": 50, 507 | "元素精通": 100, 508 | "元素伤害加成": 100, 509 | "元素充能效率": 55, 510 | "治疗加成": 100 511 | }, 512 | "神里绫人": { 513 | "生命值百分比": 50, 514 | "攻击力百分比": 75, 515 | "暴击率": 100, 516 | "暴击伤害": 100, 517 | "元素伤害加成": 100, 518 | "元素充能效率": 30 519 | }, 520 | "神里绫人-精通": { 521 | "生命值百分比": 45, 522 | "攻击力百分比": 60, 523 | "暴击率": 100, 524 | "暴击伤害": 100, 525 | "元素精通": 60, 526 | "元素伤害加成": 100, 527 | "元素充能效率": 30 528 | }, 529 | "柯莱": { 530 | "攻击力百分比": 75, 531 | "暴击率": 50, 532 | "暴击伤害": 50, 533 | "元素精通": 75, 534 | "元素伤害加成": 100, 535 | "元素充能效率": 30 536 | }, 537 | "多莉": { 538 | "生命值百分比": 100, 539 | "攻击力百分比": 50, 540 | "暴击率": 50, 541 | "暴击伤害": 50, 542 | "元素伤害加成": 80, 543 | "元素充能效率": 75, 544 | "治疗加成": 100 545 | }, 546 | "提纳里": { 547 | "攻击力百分比": 75, 548 | "暴击率": 100, 549 | "暴击伤害": 100, 550 | "元素精通": 90, 551 | "元素伤害加成": 100, 552 | "元素充能效率": 30 553 | }, 554 | "妮露": { 555 | "生命值百分比": 100, 556 | "暴击率": 30, 557 | "暴击伤害": 30, 558 | "元素精通": 80, 559 | "元素伤害加成": 100, 560 | "元素充能效率": 30 561 | }, 562 | "妮露-满命": { 563 | "生命值百分比": 100, 564 | "暴击率": 100, 565 | "暴击伤害": 100, 566 | "元素精通": 80, 567 | "元素伤害加成": 100, 568 | "元素充能效率": 30 569 | }, 570 | "赛诺": { 571 | "攻击力百分比": 75, 572 | "暴击率": 100, 573 | "暴击伤害": 100, 574 | "元素精通": 75, 575 | "元素伤害加成": 100, 576 | "元素充能效率": 55 577 | }, 578 | "坎蒂丝": { 579 | "生命值百分比": 75, 580 | "攻击力百分比": 75, 581 | "暴击率": 100, 582 | "暴击伤害": 100, 583 | "元素伤害加成": 100, 584 | "元素充能效率": 55 585 | }, 586 | "纳西妲": { 587 | "攻击力百分比": 55, 588 | "暴击率": 100, 589 | "暴击伤害": 100, 590 | "元素精通": 100, 591 | "元素伤害加成": 100, 592 | "元素充能效率": 55 593 | }, 594 | "莱依拉": { 595 | "生命值百分比": 100, 596 | "攻击力百分比": 75, 597 | "暴击率": 50, 598 | "暴击伤害": 50, 599 | "元素伤害加成": 80, 600 | "元素充能效率": 35 601 | }, 602 | "流浪者": { 603 | "攻击力百分比": 80, 604 | "暴击率": 100, 605 | "暴击伤害": 100, 606 | "元素伤害加成": 100, 607 | "元素充能效率": 35 608 | }, 609 | "珐露珊": { 610 | "攻击力百分比": 75, 611 | "暴击率": 100, 612 | "暴击伤害": 100, 613 | "元素伤害加成": 100, 614 | "元素充能效率": 100 615 | }, 616 | "瑶瑶": { 617 | "生命值百分比": 100, 618 | "攻击力百分比": 75, 619 | "暴击率": 30, 620 | "暴击伤害": 30, 621 | "元素精通": 75, 622 | "元素伤害加成": 100, 623 | "元素充能效率": 75, 624 | "治疗加成": 100 625 | }, 626 | "艾尔海森": { 627 | "攻击力百分比": 55, 628 | "暴击率": 100, 629 | "暴击伤害": 100, 630 | "元素精通": 100, 631 | "元素伤害加成": 100, 632 | "元素充能效率": 35 633 | }, 634 | "迪希雅": { 635 | "生命值百分比": 75, 636 | "攻击力百分比": 75, 637 | "暴击率": 100, 638 | "暴击伤害": 100, 639 | "元素精通": 100, 640 | "元素伤害加成": 100, 641 | "元素充能效率": 55 642 | }, 643 | "迪希雅-血牛": { 644 | "生命值百分比": 100, 645 | "攻击力百分比": 30, 646 | "暴击率": 41, 647 | "暴击伤害": 41, 648 | "元素充能效率": 30 649 | }, 650 | "米卡": { 651 | "生命值百分比": 75, 652 | "攻击力百分比": 55, 653 | "暴击率": 50, 654 | "暴击伤害": 50, 655 | "元素伤害加成": 75, 656 | "物理伤害加成": 75, 657 | "元素充能效率": 55, 658 | "治疗加成": 100 659 | }, 660 | "卡维": { 661 | "攻击力百分比": 75, 662 | "暴击率": 100, 663 | "暴击伤害": 100, 664 | "元素精通": 75, 665 | "元素伤害加成": 100, 666 | "元素充能效率": 75 667 | }, 668 | "白术": { 669 | "生命值百分比": 100, 670 | "暴击率": 30, 671 | "暴击伤害": 30, 672 | "元素精通": 50, 673 | "元素伤害加成": 80, 674 | "元素充能效率": 100, 675 | "治疗加成": 100 676 | }, 677 | "琳妮特": { 678 | "攻击力百分比": 75, 679 | "暴击率": 100, 680 | "暴击伤害": 100, 681 | "元素伤害加成": 100, 682 | "元素充能效率": 55 683 | }, 684 | "林尼": { 685 | "攻击力百分比": 75, 686 | "暴击率": 100, 687 | "暴击伤害": 100, 688 | "元素伤害加成": 100, 689 | "元素充能效率": 55 690 | }, 691 | "菲米尼": { 692 | "攻击力百分比": 75, 693 | "暴击率": 100, 694 | "暴击伤害": 100, 695 | "元素伤害加成": 100, 696 | "物理伤害加成": 100, 697 | "元素充能效率": 55 698 | }, 699 | "莱欧斯利": { 700 | "攻击力百分比": 75, 701 | "暴击率": 100, 702 | "暴击伤害": 100, 703 | "元素精通": 75, 704 | "元素伤害加成": 100, 705 | "元素充能效率": 55 706 | }, 707 | "那维莱特": { 708 | "生命值百分比": 100, 709 | "暴击率": 100, 710 | "暴击伤害": 100, 711 | "元素精通": 75, 712 | "元素伤害加成": 100, 713 | "元素充能效率": 55 714 | }, 715 | "夏洛蒂": { 716 | "攻击力百分比": 85, 717 | "暴击率": 50, 718 | "暴击伤害": 50, 719 | "元素精通": 75, 720 | "元素伤害加成": 80, 721 | "元素充能效率": 100, 722 | "治疗加成": 100 723 | }, 724 | "芙宁娜": { 725 | "生命值百分比": 100, 726 | "暴击率": 100, 727 | "暴击伤害": 100, 728 | "元素精通": 45, 729 | "元素伤害加成": 100, 730 | "元素充能效率": 75, 731 | "治疗加成": 95 732 | }, 733 | "夏沃蕾": { 734 | "生命值百分比": 100, 735 | "攻击力百分比": 55, 736 | "暴击率": 50, 737 | "暴击伤害": 50, 738 | "元素伤害加成": 100, 739 | "元素充能效率": 55, 740 | "治疗加成": 100 741 | }, 742 | "娜维娅": { 743 | "攻击力百分比": 75, 744 | "暴击率": 100, 745 | "暴击伤害": 100, 746 | "元素伤害加成": 100, 747 | "元素充能效率": 55 748 | }, 749 | "嘉明": { 750 | "攻击力百分比": 75, 751 | "暴击率": 100, 752 | "暴击伤害": 100, 753 | "元素伤害加成": 100, 754 | "元素充能效率": 55, 755 | "元素精通": 75 756 | }, 757 | "闲云": { 758 | "攻击力百分比": 100, 759 | "暴击率": 50, 760 | "暴击伤害": 50, 761 | "元素伤害加成": 80, 762 | "元素充能效率": 75, 763 | "治疗加成": 95 764 | }, 765 | "闲云-满命": { 766 | "攻击力百分比": 100, 767 | "暴击率": 100, 768 | "暴击伤害": 100, 769 | "元素伤害加成": 100, 770 | "元素充能效率": 35, 771 | "治疗加成": 95 772 | }, 773 | "千织": { 774 | "攻击力百分比": 50, 775 | "防御力百分比": 75, 776 | "暴击率": 100, 777 | "暴击伤害": 100, 778 | "元素伤害加成": 100, 779 | "元素充能效率": 55 780 | }, 781 | "希格雯": { 782 | "生命值百分比": 100, 783 | "暴击率": 100, 784 | "暴击伤害": 100, 785 | "元素伤害加成": 100, 786 | "元素充能效率": 30, 787 | "治疗加成": 100 788 | }, 789 | "阿蕾奇诺": { 790 | "攻击力百分比": 75, 791 | "暴击率": 100, 792 | "暴击伤害": 100, 793 | "元素精通": 75, 794 | "元素伤害加成": 100, 795 | "元素充能效率": 55 796 | }, 797 | "赛索斯": { 798 | "攻击力百分比": 30, 799 | "暴击率": 100, 800 | "暴击伤害": 100, 801 | "元素精通": 100, 802 | "元素伤害加成": 100, 803 | "元素充能效率": 55 804 | }, 805 | "克洛琳德": { 806 | "攻击力百分比": 100, 807 | "暴击率": 100, 808 | "暴击伤害": 100, 809 | "元素伤害加成": 100, 810 | "元素精通": 30, 811 | "元素充能效率": 35 812 | }, 813 | "艾梅莉埃": { 814 | "攻击力百分比": 100, 815 | "暴击率": 100, 816 | "暴击伤害": 100, 817 | "元素精通": 30, 818 | "元素伤害加成": 100, 819 | "元素充能效率": 55 820 | }, 821 | "卡齐娜": { 822 | "防御力百分比": 100, 823 | "暴击率": 100, 824 | "暴击伤害": 100, 825 | "元素伤害加成": 100, 826 | "元素充能效率": 55 827 | }, 828 | "玛拉妮": { 829 | "生命值百分比": 100, 830 | "暴击率": 100, 831 | "暴击伤害": 100, 832 | "元素精通": 75, 833 | "元素伤害加成": 100, 834 | "元素充能效率": 55 835 | } 836 | } -------------------------------------------------------------------------------- /data/gspanel/char-alias.json: -------------------------------------------------------------------------------- 1 | { 2 | "神里绫华": [ 3 | "神里", 4 | "绫华", 5 | "零华", 6 | "零花", 7 | "0华", 8 | "0花", 9 | "神里凌华", 10 | "凌华", 11 | "白鹭公主", 12 | "神里大小姐", 13 | "小乌龟", 14 | "龟龟" 15 | ], 16 | "琴": [ 17 | "团长", 18 | "代理团长", 19 | "琴团长", 20 | "蒲公英骑士" 21 | ], 22 | "丽莎": [ 23 | "图书管理员", 24 | "图书馆管理员", 25 | "蔷薇魔女", 26 | "丽莎阿姨" 27 | ], 28 | "芭芭拉": [ 29 | "巴巴拉", 30 | "拉巴巴", 31 | "内鬼", 32 | "加湿器", 33 | "闪耀偶像", 34 | "蒙德偶像", 35 | "偶像", 36 | "佩奇" 37 | ], 38 | "凯亚": [ 39 | "盖亚", 40 | "凯子哥", 41 | "凯鸭", 42 | "矿工", 43 | "矿工头子", 44 | "骑兵队长", 45 | "凯子", 46 | "凝冰渡海真君", 47 | "亚尔伯里奇" 48 | ], 49 | "迪卢克": [ 50 | "卢姥爷", 51 | "姥爷", 52 | "卢老爷", 53 | "卢锅巴", 54 | "正义人", 55 | "卢本伟", 56 | "暗夜英雄", 57 | "卢卢伯爵", 58 | "落魄了", 59 | "落魄了家人们", 60 | "莱艮芬德" 61 | ], 62 | "雷泽": [ 63 | "狼少年", 64 | "狼崽子", 65 | "狼崽", 66 | "卢皮卡", 67 | "小狼", 68 | "小狼狗", 69 | "狼孩" 70 | ], 71 | "安柏": [ 72 | "安伯", 73 | "兔兔伯爵", 74 | "飞行冠军", 75 | "蒙德飞行冠军", 76 | "侦查骑士", 77 | "点火姬", 78 | "点火机", 79 | "打火机", 80 | "打火姬" 81 | ], 82 | "温迪": [ 83 | "温蒂", 84 | "风神", 85 | "卖唱的", 86 | "巴巴托斯", 87 | "巴巴脱丝", 88 | "芭芭托斯", 89 | "芭芭脱丝", 90 | "干点正事", 91 | "不干正事", 92 | "吟游诗人", 93 | "诶嘿", 94 | "唉嘿", 95 | "摸鱼" 96 | ], 97 | "香菱": [ 98 | "香玲", 99 | "锅巴", 100 | "厨师", 101 | "万民堂厨师", 102 | "香师傅", 103 | "卯香菱" 104 | ], 105 | "北斗": [ 106 | "大姐头", 107 | "大姐", 108 | "无冕的龙王", 109 | "龙王" 110 | ], 111 | "行秋": [ 112 | "秋秋人", 113 | "秋妹妹", 114 | "书呆子", 115 | "飞云商会二少爷" 116 | ], 117 | "魈": [ 118 | "打桩机", 119 | "插秧", 120 | "三眼五显仙人", 121 | "三眼五显真人", 122 | "降魔大圣", 123 | "护法夜叉", 124 | "无聊", 125 | "靖妖傩舞", 126 | "三点五尺仙人" 127 | ], 128 | "凝光": [ 129 | "富婆", 130 | "天权星", 131 | "天权" 132 | ], 133 | "可莉": [ 134 | "嘟嘟可", 135 | "火花骑士", 136 | "蹦蹦炸弹", 137 | "炸鱼", 138 | "放火烧山", 139 | "放火烧山真君", 140 | "逃跑的太阳", 141 | "啦啦啦", 142 | "哒哒哒", 143 | "炸弹人", 144 | "禁闭室", 145 | "太阳", 146 | "小太阳" 147 | ], 148 | "钟离": [ 149 | "摩拉克斯", 150 | "岩王爷", 151 | "岩神", 152 | "钟师傅", 153 | "天动万象", 154 | "岩王帝君", 155 | "未来可期", 156 | "帝君", 157 | "拒收病婿" 158 | ], 159 | "菲谢尔": [ 160 | "皇女", 161 | "小艾米", 162 | "小艾咪", 163 | "奥兹", 164 | "断罪皇女", 165 | "中二病", 166 | "中二少女", 167 | "中二皇女", 168 | "奥兹发射器", 169 | "露弗施洛斯", 170 | "那菲多特" 171 | ], 172 | "班尼特": [ 173 | "点赞哥", 174 | "点赞", 175 | "倒霉少年", 176 | "倒霉蛋", 177 | "班神", 178 | "班爷", 179 | "倒霉", 180 | "六星真神" 181 | ], 182 | "达达利亚": [ 183 | "达达鸭", 184 | "达达利鸭", 185 | "公子", 186 | "玩具销售员", 187 | "玩具推销员", 188 | "钱包", 189 | "鸭鸭", 190 | "愚人众末席", 191 | "阿贾克斯" 192 | ], 193 | "诺艾尔": [ 194 | "女仆", 195 | "高达", 196 | "岩王帝姬" 197 | ], 198 | "七七": [ 199 | "僵尸", 200 | "肚饿真君", 201 | "度厄真君", 202 | "77" 203 | ], 204 | "重云": [ 205 | "纯阳之体", 206 | "冰棍" 207 | ], 208 | "甘雨": [ 209 | "椰羊", 210 | "椰奶", 211 | "王小美" 212 | ], 213 | "阿贝多": [ 214 | "升降机", 215 | "升降台", 216 | "电梯", 217 | "白垩之子", 218 | "白垩", 219 | "阿贝少", 220 | "花呗多", 221 | "阿贝夕", 222 | "abd", 223 | "阿师傅" 224 | ], 225 | "迪奥娜": [ 226 | "迪欧娜", 227 | "dio", 228 | "dio娜", 229 | "冰猫", 230 | "猫猫", 231 | "猫娘", 232 | "喵喵", 233 | "调酒师", 234 | "凯茨莱茵" 235 | ], 236 | "莫娜": [ 237 | "莫纳", 238 | "占星术士", 239 | "占星师", 240 | "讨龙真君", 241 | "半部讨龙真君", 242 | "阿斯托洛吉斯", 243 | "梅姬斯图斯", 244 | "梅姬斯图斯姬" 245 | ], 246 | "刻晴": [ 247 | "刻情", 248 | "氪晴", 249 | "刻师傅", 250 | "刻师父", 251 | "牛杂", 252 | "牛杂师傅", 253 | "斩尽牛杂", 254 | "屁斜剑法", 255 | "玉衡星", 256 | "玉衡", 257 | "阿晴", 258 | "啊晴", 259 | "璃月雷神" 260 | ], 261 | "砂糖": [ 262 | "雷莹术士", 263 | "雷萤术士", 264 | "雷荧术士" 265 | ], 266 | "辛焱": [ 267 | "辛炎", 268 | "薪炎", 269 | "黑妹", 270 | "摇滚" 271 | ], 272 | "罗莎莉亚": [ 273 | "罗莎莉娅", 274 | "白色史莱姆", 275 | "白史莱姆", 276 | "修女", 277 | "罗莎利亚", 278 | "罗莎利娅", 279 | "罗沙莉亚", 280 | "罗沙莉娅", 281 | "罗沙利亚", 282 | "罗沙利娅", 283 | "萝莎莉亚", 284 | "萝莎莉娅", 285 | "萝莎利亚", 286 | "萝莎利娅", 287 | "萝沙莉亚", 288 | "萝沙莉娅", 289 | "萝沙利亚", 290 | "萝沙利娅" 291 | ], 292 | "胡桃": [ 293 | "胡淘", 294 | "往生堂堂主", 295 | "火化", 296 | "抬棺的", 297 | "蝴蝶", 298 | "核桃", 299 | "堂主", 300 | "胡堂主", 301 | "雪霁梅香", 302 | "桃子" 303 | ], 304 | "枫原万叶": [ 305 | "万叶", 306 | "叶天帝", 307 | "天帝", 308 | "叶师傅" 309 | ], 310 | "烟绯": [ 311 | "烟老师", 312 | "律师", 313 | "罗翔" 314 | ], 315 | "宵宫": [ 316 | "霄宫", 317 | "烟花", 318 | "肖宫", 319 | "肖工", 320 | "绷带女孩", 321 | "长野原宵宫" 322 | ], 323 | "托马": [ 324 | "家政官", 325 | "太郎丸", 326 | "地头蛇", 327 | "男仆", 328 | "拖马" 329 | ], 330 | "优菈": [ 331 | "优拉", 332 | "尤拉", 333 | "尤菈", 334 | "浪花骑士", 335 | "记仇", 336 | "优菈劳伦斯", 337 | "劳伦斯" 338 | ], 339 | "雷电将军": [ 340 | "雷神", 341 | "将军", 342 | "雷军", 343 | "巴尔", 344 | "阿影", 345 | "影", 346 | "巴尔泽布", 347 | "煮饭婆", 348 | "奶香一刀", 349 | "无想一刀", 350 | "宅女" 351 | ], 352 | "早柚": [ 353 | "早抽", 354 | "旱抽", 355 | "旱柚", 356 | "小狸猫", 357 | "狸猫", 358 | "忍者", 359 | "貉貉", 360 | "貉" 361 | ], 362 | "珊瑚宫心海": [ 363 | "心海", 364 | "军师", 365 | "珊瑚宫", 366 | "书记", 367 | "观赏鱼", 368 | "水母", 369 | "鱼", 370 | "美人鱼" 371 | ], 372 | "五郎": [ 373 | "柴犬", 374 | "希娜", 375 | "希娜小姐" 376 | ], 377 | "九条裟罗": [ 378 | "九条", 379 | "九条沙罗", 380 | "裟罗", 381 | "沙罗", 382 | "天狗" 383 | ], 384 | "荒泷一斗": [ 385 | "荒龙一斗", 386 | "荒泷天下第一斗", 387 | "一斗", 388 | "一抖", 389 | "荒泷", 390 | "1斗", 391 | "牛牛", 392 | "斗子哥", 393 | "牛子哥", 394 | "牛子", 395 | "孩子王", 396 | "斗虫", 397 | "巧乐兹", 398 | "放牛的" 399 | ], 400 | "八重神子": [ 401 | "八重", 402 | "神子", 403 | "狐狸", 404 | "想得美哦", 405 | "巫女", 406 | "屑狐狸", 407 | "八重宫司", 408 | "婶子", 409 | "小八", 410 | "老八" 411 | ], 412 | "鹿野院平藏": [ 413 | "鹿野苑", 414 | "鹿野院", 415 | "平藏", 416 | "鹿野苑平藏", 417 | "小鹿" 418 | ], 419 | "夜兰": [ 420 | "夜阑", 421 | "叶澜", 422 | "腋兰", 423 | "夜天后" 424 | ], 425 | "绮良良": [ 426 | "稻妻猫猫", 427 | "绮娘娘", 428 | "琦良良", 429 | "良良", 430 | "快递员", 431 | "草猫", 432 | "草猫猫", 433 | "草喵", 434 | "草喵喵", 435 | "猫又" 436 | ], 437 | "埃洛伊": [ 438 | "艾罗伊", 439 | "艾洛伊", 440 | "艾洛一" 441 | ], 442 | "申鹤": [ 443 | "神鹤", 444 | "小姨", 445 | "小姨子", 446 | "审核" 447 | ], 448 | "云堇": [ 449 | "云瑾", 450 | "云先生", 451 | "云锦", 452 | "神女劈观" 453 | ], 454 | "久岐忍": [ 455 | "97忍", 456 | "小忍", 457 | "久歧忍", 458 | "茄忍", 459 | "茄子", 460 | "紫茄子", 461 | "阿忍", 462 | "忍姐" 463 | ], 464 | "神里绫人": [ 465 | "绫人", 466 | "神里凌人", 467 | "凌人", 468 | "0人", 469 | "神人", 470 | "零人", 471 | "大舅哥" 472 | ], 473 | "柯莱": [ 474 | "柯来", 475 | "科莱", 476 | "科来", 477 | "小天使", 478 | "须弥飞行冠军", 479 | "见习巡林员", 480 | "巡林员" 481 | ], 482 | "多莉": [ 483 | "多利", 484 | "多力", 485 | "多丽", 486 | "奸商" 487 | ], 488 | "提纳里": [ 489 | "小提", 490 | "提那里", 491 | "提这里", 492 | "缇娜里", 493 | "提哪里", 494 | "巡林官" 495 | ], 496 | "妮露": [ 497 | "尼露", 498 | "尼禄", 499 | "妮璐", 500 | "舞娘", 501 | "红牛" 502 | ], 503 | "赛诺": [ 504 | "塞诺", 505 | "胡狼", 506 | "大风纪官", 507 | "大风机关" 508 | ], 509 | "坎蒂丝": [ 510 | "坎迪斯" 511 | ], 512 | "纳西妲": [ 513 | "草王", 514 | "草神", 515 | "草萝莉", 516 | "纳西坦", 517 | "小吉祥", 518 | "大吉祥", 519 | "小草神", 520 | "小草王", 521 | "大慈树王", 522 | "小吉祥草王", 523 | "羽毛球", 524 | "摩诃善法大吉祥智慧主", 525 | "智慧之神", 526 | "智慧主", 527 | "布耶尔" 528 | ], 529 | "莱依拉": [ 530 | "来一拉", 531 | "拉一拉", 532 | "莱依菈", 533 | "来依菈", 534 | "来依拉", 535 | "来拉", 536 | "来辣" 537 | ], 538 | "流浪者": [ 539 | "散兵", 540 | "国崩", 541 | "雷电国崩", 542 | "大炮", 543 | "雷电大炮", 544 | "雷大炮", 545 | "伞兵", 546 | "斯卡拉姆齐", 547 | "七叶寂照秘密主", 548 | "正机之神" 549 | ], 550 | "珐露珊": [ 551 | "法露珊", 552 | "法璐珊", 553 | "法露姗", 554 | "法璐姗", 555 | "珐露姗", 556 | "珐璐姗", 557 | "百岁山", 558 | "百岁姗", 559 | "百岁珊", 560 | "前辈", 561 | "仙贝", 562 | "初音", 563 | "初音未来" 564 | ], 565 | "瑶瑶": [ 566 | "遥遥", 567 | "月桂", 568 | "萝卜" 569 | ], 570 | "艾尔海森": [ 571 | "代理贤者", 572 | "埃尔海森", 573 | "书记官", 574 | "海哥", 575 | "海森", 576 | "海参" 577 | ], 578 | "迪希雅": [ 579 | "迪希亚", 580 | "迪希娅", 581 | "迪西雅", 582 | "迪西亚", 583 | "炎拳" 584 | ], 585 | "米卡": [ 586 | "米咖", 587 | "鹦鹉" 588 | ], 589 | "卡维": [ 590 | "室友", 591 | "建筑师", 592 | "设计师", 593 | "建筑设计师" 594 | ], 595 | "白术": [ 596 | "长生", 597 | "白大夫", 598 | "白医生", 599 | "白求恩" 600 | ], 601 | "琳妮特": [ 602 | "林妮特", 603 | "林尼特", 604 | "琳尼特", 605 | "登登", 606 | "噔噔" 607 | ], 608 | "林尼": [ 609 | "大魔术师", 610 | "魔术师", 611 | "琳妮", 612 | "林妮" 613 | ], 614 | "菲米尼": [ 615 | "费米你", 616 | "费米妮", 617 | "菲米妮", 618 | "菲米你", 619 | "费米尼", 620 | "非米尼", 621 | "潜水员" 622 | ], 623 | "莱欧斯利": [ 624 | "莱欧", 625 | "枫丹桑博", 626 | "桑博" 627 | ], 628 | "那维莱特": [ 629 | "那维", 630 | "水龙王", 631 | "水龙", 632 | "审判官", 633 | "海獭", 634 | "龙王" 635 | ], 636 | "夏洛蒂": [ 637 | "夏洛", 638 | "夏洛帝", 639 | "记者", 640 | "小记者" 641 | ], 642 | "芙宁娜": [ 643 | "水神", 644 | "芙芙", 645 | "芙卡洛斯" 646 | ], 647 | "夏沃蕾": [ 648 | "夏沃雷" 649 | ], 650 | "娜维娅": [ 651 | "大小姐", 652 | "刺玫会", 653 | "黄豆", 654 | "流汗黄豆", 655 | "黄豆姐" 656 | ], 657 | "嘉明": [ 658 | "佳明", 659 | "嘉铭", 660 | "家明", 661 | "镖师", 662 | "舞狮少年", 663 | "舞狮" 664 | ], 665 | "闲云": [ 666 | "流云", 667 | "刘云", 668 | "留云", 669 | "野鹤", 670 | "那个女人", 671 | "留云借风", 672 | "留云借风真君", 673 | "很会聊天", 674 | "很会聊天真君" 675 | ], 676 | "千织": [ 677 | "千只", 678 | "设计师", 679 | "裁缝", 680 | "千织屋老板" 681 | ], 682 | "希格雯": [ 683 | "希格文", 684 | "护士", 685 | "护士长" 686 | ], 687 | "阿蕾奇诺": [ 688 | "阿雷奇诺", 689 | "仆人", 690 | "黑优菈", 691 | "黑暗优菈", 692 | "父亲", 693 | "佩露薇利" 694 | ], 695 | "赛索斯": [ 696 | "塞索斯", 697 | "塞索思", 698 | "赛索思" 699 | ], 700 | "克洛琳德": [ 701 | "科洛琳德", 702 | "克洛林德" 703 | ], 704 | "艾梅莉埃": [ 705 | "调香师", 706 | "艾梅" 707 | ], 708 | "卡齐娜": [ 709 | "冲天钻钻" 710 | ], 711 | "基尼奇": [ 712 | "基哥" 713 | ], 714 | "玛拉妮": [ 715 | "鲨鱼妹", 716 | "海豹妹", 717 | "海豹" 718 | ] 719 | } -------------------------------------------------------------------------------- /data/gspanel/font/HYWH-65W.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/font/HYWH-65W.ttf -------------------------------------------------------------------------------- /data/gspanel/font/tttgbnumber.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/font/tttgbnumber.ttf -------------------------------------------------------------------------------- /data/gspanel/imgs/bg-anemo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/bg-anemo.jpg -------------------------------------------------------------------------------- /data/gspanel/imgs/bg-cryo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/bg-cryo.jpg -------------------------------------------------------------------------------- /data/gspanel/imgs/bg-dendro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/bg-dendro.jpg -------------------------------------------------------------------------------- /data/gspanel/imgs/bg-electro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/bg-electro.jpg -------------------------------------------------------------------------------- /data/gspanel/imgs/bg-geo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/bg-geo.jpg -------------------------------------------------------------------------------- /data/gspanel/imgs/bg-hydro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/bg-hydro.jpg -------------------------------------------------------------------------------- /data/gspanel/imgs/bg-pyro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/bg-pyro.jpg -------------------------------------------------------------------------------- /data/gspanel/imgs/talent-anemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/talent-anemo.png -------------------------------------------------------------------------------- /data/gspanel/imgs/talent-cryo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/talent-cryo.png -------------------------------------------------------------------------------- /data/gspanel/imgs/talent-dendro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/talent-dendro.png -------------------------------------------------------------------------------- /data/gspanel/imgs/talent-electro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/talent-electro.png -------------------------------------------------------------------------------- /data/gspanel/imgs/talent-geo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/talent-geo.png -------------------------------------------------------------------------------- /data/gspanel/imgs/talent-hydro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/talent-hydro.png -------------------------------------------------------------------------------- /data/gspanel/imgs/talent-pyro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monsterxcn/nonebot-plugin-gspanel/34b7cc8be03abbc35936da1f2e2d5ef11689834b/data/gspanel/imgs/talent-pyro.png -------------------------------------------------------------------------------- /data/gspanel/list-0.2.7.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --charRarity5: rgba(200, 124, 36, 0.68); 3 | --charRarity4: rgba(148, 112, 187, 0.68); 4 | --charCons6: #ff5722; 5 | --charCons5: #531ba9cf; 6 | --charCons4: #3955b7; 7 | --charCons3: #3e95b9; 8 | --charCons2: #339d61; 9 | --charCons1: #5cbac2; 10 | --artiMark4: #FF5722; 11 | --artiMark3: #FFE699; 12 | --artiMark2: #D699FF; 13 | --artiMark1: #FFFFFF; 14 | --ambrLight: rgb(233, 229, 220); 15 | --ambrDeep: rgb(74, 83, 102); 16 | --listBg: #33333399; 17 | --noteBg: #2e353e; 18 | --timeSuccess: #90e800; 19 | --timeWarning: #ff7c37; 20 | --timeError: #ff5652; 21 | } 22 | 23 | @font-face { 24 | font-family: "PanelFont"; 25 | src: url(./font/HYWH-65W.ttf); 26 | font-weight: 400; 27 | font-style: normal 28 | } 29 | 30 | @font-face { 31 | font-family: "PanelNumFont"; 32 | src: url(./font/tttgbnumber.ttf); 33 | font-weight: 400; 34 | font-style: normal 35 | } 36 | 37 | body { 38 | overflow: overlay; 39 | margin: 0px; 40 | width: 960px; 41 | } 42 | 43 | #lcontainer { 44 | width: 960px; 45 | font-family: "PanelFont"; 46 | background-position: center; 47 | background-size: cover; 48 | } 49 | 50 | #lcontainer.火 { 51 | background-image: url(./imgs/bg-pyro.jpg); 52 | } 53 | 54 | #lcontainer.水 { 55 | background-image: url(./imgs/bg-hydro.jpg); 56 | } 57 | 58 | #lcontainer.风 { 59 | background-image: url(./imgs/bg-anemo.jpg); 60 | } 61 | 62 | #lcontainer.雷 { 63 | background-image: url(./imgs/bg-electro.jpg); 64 | } 65 | 66 | #lcontainer.草 { 67 | background-image: url(./imgs/bg-dendro.jpg); 68 | } 69 | 70 | #lcontainer.冰 { 71 | background-image: url(./imgs/bg-cryo.jpg); 72 | } 73 | 74 | #lcontainer.岩 { 75 | background-image: url(./imgs/bg-geo.jpg); 76 | } 77 | 78 | div.Title { 79 | width: 100%; 80 | display: flex; 81 | flex-direction: row; 82 | justify-content: center; 83 | font-size: 32px; 84 | font-weight: 700; 85 | padding-top: 1em; 86 | text-shadow: 0 0 3px #d3bc8e, 2px 2px 4px rgb(0 0 0 / 70%); 87 | color: #ffffff; 88 | } 89 | 90 | div.Title>div.UID::before { 91 | content: "UID "; 92 | } 93 | 94 | div.Title>div.UID::after { 95 | content: ""; 96 | padding-right: 2em; 97 | } 98 | 99 | div.Title>div.counter::before { 100 | content: "查询可用角色 "; 101 | } 102 | 103 | div.Title>div.counter::after { 104 | content: " 位"; 105 | } 106 | 107 | div.List, 108 | div.Note { 109 | margin: 20px 30px 0 30px; 110 | background-color: #33333399; 111 | border-radius: 15px 15px 0 0; 112 | box-shadow: 0px 15px 15px rgb(0 0 0 / 40%); 113 | } 114 | 115 | div.List { 116 | display: flex; 117 | flex-wrap: wrap; 118 | padding: 10px 5px; 119 | gap: 10px; 120 | } 121 | 122 | div.List>div.avatar { 123 | width: 100px; 124 | text-align: center; 125 | } 126 | 127 | div.List>div.avatar:not(.refreshed) { 128 | opacity: 0.5; 129 | } 130 | 131 | div.List>div.avatar>img { 132 | width: 72px; 133 | border-radius: 50%; 134 | border: 2px solid #fff; 135 | box-shadow: 0px 3px 3px #000; 136 | background-color: var(--charRarity4); 137 | } 138 | 139 | div.List>div.avatar>img.r5 { 140 | background-color: var(--charRarity5); 141 | } 142 | 143 | div.List>div.avatar>div { 144 | color: var(--ambrLight); 145 | background: var(--noteBg); 146 | font-family: "PanelNumFont"; 147 | font-size: 13px; 148 | display: flex; 149 | flex-wrap: wrap; 150 | width: 80px; 151 | margin: -5px 10px 5px 10px; 152 | padding-top: 5px; 153 | border-radius: 5px; 154 | box-shadow: 0px 5px 5px rgb(0 0 0 / 40%); 155 | } 156 | 157 | div.List>div.avatar>div>div { 158 | min-width: 80px; 159 | } 160 | 161 | div.List>div.avatar>div>div.lvl>span { 162 | margin-left: 0.7em; 163 | border-radius: 3px; 164 | color: var(--ambrLight); 165 | text-shadow: 0 0 3px var(--ambrDeep); 166 | } 167 | 168 | div.List>div.avatar>div>div.lvl>span.c1 { 169 | padding: 2px 4px; 170 | background-color: var(--charCons1); 171 | } 172 | 173 | div.List>div.avatar>div>div.lvl>span.c2 { 174 | padding: 2px 4px; 175 | background-color: var(--charCons2); 176 | } 177 | 178 | div.List>div.avatar>div>div.lvl>span.c3 { 179 | padding: 2px 4px; 180 | background-color: var(--charCons3); 181 | } 182 | 183 | div.List>div.avatar>div>div.lvl>span.c4 { 184 | padding: 2px 4px; 185 | background-color: var(--charCons4); 186 | } 187 | 188 | div.List>div.avatar>div>div.lvl>span.c5 { 189 | padding: 2px 4px; 190 | background-color: var(--charCons5); 191 | } 192 | 193 | div.List>div.avatar>div>div.lvl>span.c6 { 194 | padding: 2px 4px; 195 | background-color: var(--charCons6); 196 | } 197 | 198 | div.List>div.avatar>div>div.lvl::before { 199 | content: "Lv"; 200 | } 201 | 202 | div.List>div.avatar>div>div.lvl>span::before { 203 | content: "C"; 204 | } 205 | 206 | div.List>div.avatar>div>div.mark { 207 | padding: 5px 0; 208 | color: var(--artiMark1); 209 | } 210 | 211 | div.List>div.avatar>div>div.mark.NaN { 212 | color: var(--ambrLight); 213 | opacity: .5; 214 | } 215 | 216 | div.List>div.avatar>div>div.mark.A, 217 | div.List>div.avatar>div>div.mark.S { 218 | color: var(--artiMark2); 219 | } 220 | 221 | div.List>div.avatar>div>div.mark.SS, 222 | div.List>div.avatar>div>div.mark.SSS { 223 | color: var(--artiMark3); 224 | } 225 | 226 | div.List>div.avatar>div>div.mark.ACE, 227 | div.List>div.avatar>div>div.mark.ACE² { 228 | color: var(--artiMark4); 229 | } 230 | 231 | div.Note { 232 | height: 50px; 233 | margin: 0 30px 20px 30px; 234 | font-size: 18px; 235 | opacity: .9; 236 | color: var(--ambrLight); 237 | background: var(--noteBg); 238 | border-radius: 0 0 15px 15px; 239 | display: flex; 240 | justify-content: space-between; 241 | align-items: center; 242 | } 243 | 244 | div.Note>div { 245 | padding: 0 20px; 246 | } 247 | 248 | div.Note>div.detail::before { 249 | content: "• 透明显示的角色为本地缓存,数据可能过时"; 250 | } 251 | 252 | div.Note>div.time { 253 | color: var(--timeSuccess); 254 | } 255 | 256 | div.Note>div.time::before { 257 | content: "刷新完成时间 "; 258 | } 259 | 260 | div.Note>div.time.warning { 261 | color: var(--timeWarning); 262 | } 263 | 264 | div.Note>div.time.warning::before { 265 | content: "刷新可用时间 "; 266 | } 267 | 268 | div.Note>div.time.error { 269 | color: var(--timeError); 270 | } 271 | 272 | div.Note>div.time.error::before { 273 | content: "本次刷新失败 "; 274 | } 275 | 276 | div.copyright { 277 | color: #ffffff; 278 | opacity: 0.68; 279 | text-shadow: 3px 5px 4px #333333ad; 280 | font-size: 20px; 281 | width: 960px; 282 | text-align: center; 283 | padding: 0 0 20px 0; 284 | } 285 | 286 | div.copyright::before { 287 | content: "Data from Enka.Network × Powered by NoneBot2 × Inspired by Miao-Plugin"; 288 | } 289 | -------------------------------------------------------------------------------- /data/gspanel/list-0.2.7.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | {% set time = data.get("timetips", []) %} 14 | {% set data = data.get("avatars", []) %} 15 |
16 |
17 |
{{ uid }}
18 |
{{ data | length }}
19 |
20 |
21 | {% for avatar in data %} 22 |
23 | 24 |
25 |
{{ avatar["level"] }}{{ avatar["cons"] 26 | }}
27 |
28 | {{ avatar["relicCalc"]["rank"] }} 29 |   30 | {{ avatar["relicCalc"]["total"] }} 31 |
32 |
33 |
34 | {% endfor %} 35 |
36 |
37 |
38 |
{{ time[1] }}
39 |
40 | 41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /data/gspanel/panel-0.2.7.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --charRarity5: rgba(200, 124, 36, 0.68); 3 | --charRarity4: rgba(148, 112, 187, 0.68); 4 | --itemRarity5: #C87C24; 5 | --itemRarity4: #9470BB; 6 | --itemRarity3: #5987AD; 7 | --itemRarity2: #5A977A; 8 | --itemRarity1: #818486; 9 | --elemPyro: #FF9999; 10 | --elemHydro: #80C0FF; 11 | --elemAnemo: #80FFD7; 12 | --elemElectro: #FFACFF; 13 | --elemDendro: #99FF88; 14 | --elemCryo: #99FFFF; 15 | --elemGeo: #FFE699; 16 | --weaponAffix5: #DEAF39; 17 | --weaponAffix4: #C539DE; 18 | --weaponAffix3: #396CDE; 19 | --weaponAffix2: #51B72F; 20 | --weaponAffix1: #A3A3A3; 21 | --artiMark4: #FF5722; 22 | --artiMark3: #FFE699; 23 | --artiMark2: #D699FF; 24 | --artiMark1: #FFFFFF; 25 | --ambrLight: rgb(233, 229, 220); 26 | --ambrDeep: rgb(74, 83, 102); 27 | --artiBg: rgba(46, 53, 62, .68); 28 | --tableBg: #2e353e; 29 | } 30 | 31 | @font-face { 32 | font-family: "PanelFont"; 33 | src: url(./font/HYWH-65W.ttf); 34 | font-weight: 400; 35 | font-style: normal 36 | } 37 | 38 | @font-face { 39 | font-family: "PanelNumFont"; 40 | src: url(./font/tttgbnumber.ttf); 41 | font-weight: 400; 42 | font-style: normal 43 | } 44 | 45 | body { 46 | overflow: overlay; 47 | margin: 0px; 48 | } 49 | 50 | #container { 51 | width: 960px; 52 | height: auto; 53 | min-height: 720px; 54 | font-family: "PanelFont"; 55 | background-repeat: no-repeat; 56 | background-position: center 0%; 57 | background-size: auto 100%; 58 | position: absolute; 59 | top: 0; 60 | left: 0; 61 | } 62 | 63 | #container.火 { 64 | background-image: url(./imgs/bg-pyro.jpg); 65 | } 66 | 67 | #container.火 div.TopRightBlur { 68 | background-color: rgba(var(--elemPyro), 0.25); 69 | } 70 | 71 | #container.火 div.TopRightBlur>ul.PropPanel { 72 | border-color: var(--elemPyro); 73 | } 74 | 75 | #container.火 div.AvatarCons>div.cons, 76 | #container.火 div.TopRightBlur>div.AvatarSkills>div.skill>div.element { 77 | background-image: url(./imgs/talent-pyro.png); 78 | } 79 | 80 | #container.水 { 81 | background-image: url(./imgs/bg-hydro.jpg); 82 | } 83 | 84 | #container.水 div.TopRightBlur { 85 | background-color: rgba(var(--elemHydro), 0.25); 86 | } 87 | 88 | #container.水 div.TopRightBlur>ul.PropPanel { 89 | border-color: var(--elemHydro); 90 | } 91 | 92 | #container.水 div.AvatarCons>div.cons, 93 | #container.水 div.TopRightBlur>div.AvatarSkills>div.skill>div.element { 94 | background-image: url(./imgs/talent-hydro.png); 95 | } 96 | 97 | #container.风 { 98 | background-image: url(./imgs/bg-anemo.jpg); 99 | } 100 | 101 | #container.风 div.TopRightBlur { 102 | background-color: rgba(var(--elemAnemo), 0.25); 103 | } 104 | 105 | #container.风 div.TopRightBlur>ul.PropPanel { 106 | border-color: var(--elemAnemo); 107 | } 108 | 109 | #container.风 div.AvatarCons>div.cons, 110 | #container.风 div.TopRightBlur>div.AvatarSkills>div.skill>div.element { 111 | background-image: url(./imgs/talent-anemo.png); 112 | } 113 | 114 | #container.雷 { 115 | background-image: url(./imgs/bg-electro.jpg); 116 | } 117 | 118 | #container.雷 div.TopRightBlur { 119 | background-color: rgba(var(--elemElectro), 0.25); 120 | } 121 | 122 | #container.雷 div.TopRightBlur>ul.PropPanel { 123 | border-color: var(--elemElectro); 124 | } 125 | 126 | #container.雷 div.AvatarCons>div.cons, 127 | #container.雷 div.TopRightBlur>div.AvatarSkills>div.skill>div.element { 128 | background-image: url(./imgs/talent-electro.png); 129 | } 130 | 131 | #container.草 { 132 | background-image: url(./imgs/bg-dendro.jpg); 133 | } 134 | 135 | #container.草 div.TopRightBlur { 136 | background-color: rgba(var(--elemDendro), 0.25); 137 | } 138 | 139 | #container.草 div.TopRightBlur>ul.PropPanel { 140 | border-color: var(--elemDendro); 141 | } 142 | 143 | #container.草 div.AvatarCons>div.cons, 144 | #container.草 div.TopRightBlur>div.AvatarSkills>div.skill>div.element { 145 | background-image: url(./imgs/talent-dendro.png); 146 | } 147 | 148 | #container.冰 { 149 | background-image: url(./imgs/bg-cryo.jpg); 150 | } 151 | 152 | #container.冰 div.TopRightBlur { 153 | background-color: rgba(var(--elemCryo), 0.25); 154 | } 155 | 156 | #container.冰 div.TopRightBlur>ul.PropPanel { 157 | border-color: var(--elemCryo); 158 | } 159 | 160 | #container.冰 div.AvatarCons>div.cons, 161 | #container.冰 div.TopRightBlur>div.AvatarSkills>div.skill>div.element { 162 | background-image: url(./imgs/talent-cryo.png); 163 | } 164 | 165 | #container.岩 { 166 | background-image: url(./imgs/bg-geo.jpg); 167 | } 168 | 169 | #container.岩 div.TopRightBlur { 170 | background-color: rgba(var(--elemGeo), 0.25); 171 | } 172 | 173 | #container.岩 div.TopRightBlur>ul.PropPanel { 174 | border-color: var(--elemGeo); 175 | } 176 | 177 | #container.岩 div.AvatarCons>div.cons, 178 | #container.岩 div.TopRightBlur>div.AvatarSkills>div.skill>div.element { 179 | background-image: url(./imgs/talent-geo.png); 180 | } 181 | 182 | img.UIGachaAvatarImg { 183 | position: absolute; 184 | width: 1440px; 185 | height: 720px; 186 | right: 0px; 187 | top: 0px 188 | } 189 | 190 | div.UID { 191 | position: absolute; 192 | top: 20px; 193 | left: 20px; 194 | font-size: 32px; 195 | font-weight: 700; 196 | height: 35px; 197 | line-height: 35px; 198 | text-shadow: 0 0 3px #d3bc8e, 2px 2px 4px rgb(0 0 0 / 70%); 199 | color: #fff; 200 | opacity: .68; 201 | } 202 | 203 | div.UID::before { 204 | content: "UID"; 205 | padding-right: 5px; 206 | } 207 | 208 | div.AvatarLevel { 209 | position: absolute; 210 | width: 120px; 211 | height: 100px; 212 | right: 500px; 213 | top: 530px 214 | } 215 | 216 | div.AvatarLevel>div { 217 | align-items: flex-start; 218 | display: flex; 219 | height: 50px; 220 | justify-content: center; 221 | min-width: 120px; 222 | } 223 | 224 | div.AvatarLevel>div>div { 225 | font-size: 24px; 226 | font-weight: 700; 227 | font-style: normal; 228 | height: 50px; 229 | letter-spacing: 0; 230 | line-height: normal; 231 | text-shadow: 3px 5px 4px #333333ad; 232 | color: #ffffff; 233 | display: flex; 234 | flex-direction: column; 235 | justify-content: center; 236 | } 237 | 238 | div.AvatarLevel>div.exp { 239 | border-radius: 20px 0px 0px 0px; 240 | } 241 | 242 | div.AvatarLevel>div.exp.r5 { 243 | background-color: var(--charRarity5); 244 | } 245 | 246 | div.AvatarLevel>div.exp.r4 { 247 | background-color: var(--charRarity4); 248 | } 249 | 250 | div.AvatarLevel>div.fetter { 251 | background-color: #ff5722ad; 252 | border-radius: 0px 0px 20px 0px; 253 | } 254 | 255 | div.AvatarCons { 256 | position: absolute; 257 | width: 452px; 258 | height: 78px; 259 | right: 494px; 260 | top: 642px; 261 | align-items: center; 262 | display: flex; 263 | gap: 4px; 264 | min-width: 452px; 265 | } 266 | 267 | div.AvatarCons>div.cons { 268 | align-items: center; 269 | background-position: 50% 50%; 270 | background-size: cover; 271 | display: flex; 272 | height: 78px; 273 | width: 72px; 274 | } 275 | 276 | div.AvatarCons>div.cons.off { 277 | filter: blur(2px); 278 | opacity: 0.68; 279 | } 280 | 281 | div.AvatarCons>div.cons>img.UITalentIcon { 282 | height: 40px; 283 | width: 40px; 284 | object-fit: cover; 285 | padding-left: 16px; 286 | } 287 | 288 | div.TopRightBlur { 289 | position: relative; 290 | width: 480px; 291 | height: 720px; 292 | left: 480px; 293 | top: 0px; 294 | display: flex; 295 | flex-direction: column; 296 | align-items: center; 297 | backdrop-filter: blur(10px) brightness(100%); 298 | -webkit-backdrop-filter: blur(5px) brightness(100%); 299 | } 300 | 301 | div.TopRightBlur>h1.AvatarTitle { 302 | top: 30px; 303 | color: #FFFFFF; 304 | font-size: 36px; 305 | font-weight: 700; 306 | height: 60px; 307 | width: 460px; 308 | line-height: 60px; 309 | letter-spacing: 0; 310 | text-align: center; 311 | text-shadow: 312 | 2px 2px 0 rgba(0, 0, 0, 0.68), 313 | -2px -2px 0 rgba(0, 0, 0, 0.68), 314 | 2px -2px 0 rgba(0, 0, 0, 0.68), 315 | -2px 2px 0 rgba(0, 0, 0, 0.68), 316 | 2px 2px 0 rgba(0, 0, 0, 0.68); 317 | margin-block-start: 25px; 318 | margin-block-end: 10px; 319 | margin-inline-start: 0px; 320 | margin-inline-end: 0px; 321 | } 322 | 323 | div.TopRightBlur>div.AvatarSkills { 324 | align-items: center; 325 | align-self: center; 326 | display: flex; 327 | gap: 28px; 328 | width: 362px; 329 | } 330 | 331 | div.TopRightBlur>div.AvatarSkills>div.skill { 332 | height: 130px; 333 | position: relative; 334 | width: 102px; 335 | } 336 | 337 | div.TopRightBlur>div.AvatarSkills>div.skill>div.element { 338 | background-position: 50% 50%; 339 | background-size: cover; 340 | width: 102px; 341 | height: 110px; 342 | left: 0; 343 | top: 0; 344 | position: absolute; 345 | } 346 | 347 | div.TopRightBlur>div.AvatarSkills>div.skill>img.UISkillIcon { 348 | height: 50px; 349 | width: 50px; 350 | position: absolute; 351 | top: 32px; 352 | left: 26px; 353 | object-fit: cover; 354 | } 355 | 356 | div.TopRightBlur>div.AvatarSkills>div.skill>div.level { 357 | align-items: center; 358 | background-color: #ffffff; 359 | border: 1px none; 360 | border-radius: 10px; 361 | box-shadow: 6px 6px 3px #00000040; 362 | display: flex; 363 | width: 50px; 364 | height: 40px; 365 | position: absolute; 366 | left: 26px; 367 | top: 90px; 368 | } 369 | 370 | div.TopRightBlur>div.AvatarSkills>div.skill>div.level.extra { 371 | color: #000000; 372 | background-color: #45deff; 373 | } 374 | 375 | div.TopRightBlur>div.AvatarSkills>div.skill>div.level>div { 376 | color: rgba(0, 0, 0, 0.68); 377 | font-size: 24px; 378 | font-weight: 700; 379 | width: 50px; 380 | height: 40px; 381 | line-height: 40px; 382 | letter-spacing: 0; 383 | display: flex; 384 | flex-direction: column; 385 | align-items: center; 386 | } 387 | 388 | div.TopRightBlur>ul.PropPanel { 389 | background-color: #33333399; 390 | border: 2px dashed; 391 | border-radius: 10px; 392 | height: 440px; 393 | width: 440px; 394 | position: absolute; 395 | bottom: 20px; 396 | display: flex; 397 | flex-direction: column; 398 | justify-content: space-around; 399 | list-style-type: none; 400 | margin-block-start: unset; 401 | margin-block-end: unset; 402 | margin-inline-start: unset; 403 | margin-inline-end: unset; 404 | padding-inline-start: unset; 405 | } 406 | 407 | div.TopRightBlur>ul.PropPanel>li { 408 | color: #ffffff; 409 | height: 50px; 410 | font-size: 24px; 411 | font-weight: 700; 412 | text-shadow: 3px 3px 3px rgba(0, 0, 0, 0.4); 413 | display: flex; 414 | flex-direction: row; 415 | align-items: center; 416 | } 417 | 418 | div.TopRightBlur>ul.PropPanel>li>span.weight { 419 | width: 40px; 420 | height: 24px; 421 | position: absolute; 422 | left: 20px; 423 | color: #000000; 424 | background-color: #ffffff; 425 | font-size: 16px; 426 | font-weight: 700; 427 | border-radius: 5px; 428 | text-shadow: none; 429 | box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.4); 430 | display: flex; 431 | justify-content: center; 432 | align-items: center; 433 | } 434 | 435 | div.TopRightBlur>ul.PropPanel>li>span.weight.error { 436 | color: #ffe699; 437 | background-color: rgba(240, 6, 6, 0.7); 438 | } 439 | 440 | div.TopRightBlur>ul.PropPanel>li>div { 441 | position: absolute; 442 | left: 80px; 443 | } 444 | 445 | div.TopRightBlur>ul.PropPanel>li>span.value { 446 | height: 15px; 447 | line-height: 15px; 448 | text-align: right; 449 | position: absolute; 450 | right: 55px; 451 | } 452 | 453 | div.TopRightBlur>ul.PropPanel>li>span.value>div { 454 | display: inline-flex; 455 | font-size: 12px; 456 | font-weight: 400; 457 | opacity: 0.8; 458 | } 459 | 460 | div.TopRightBlur>ul.PropPanel>li>span.value>div:last-child { 461 | color: #90e800; 462 | } 463 | 464 | div.TopRightBlur>ul.PropPanel>li>span.value>div:last-child::before { 465 | content: "+"; 466 | } 467 | 468 | div.AvatarEquips { 469 | position: relative; 470 | width: 900px; 471 | min-height: 310px; 472 | padding-bottom: 60px; 473 | left: 30px; 474 | top: 30px; 475 | 476 | align-items: center; 477 | display: flex; 478 | flex-wrap: wrap; 479 | gap: 30px; 480 | } 481 | 482 | div.AvatarEquips>div.item { 483 | color: #ffffff; 484 | align-items: center; 485 | display: flex; 486 | flex-direction: column; 487 | width: 280px; 488 | height: 310px; 489 | border-radius: 15px; 490 | border: none; 491 | } 492 | 493 | div.AvatarEquips>div.item:not(.arti)>div.weapon, 494 | div.AvatarEquips>div.item.arti { 495 | background-color: var(--artiBg); 496 | box-shadow: 0px 15px 15px rgb(0 0 0 / 40%); 497 | } 498 | 499 | div.AvatarEquips>div.item:not(.arti) { 500 | gap: 30px; 501 | } 502 | 503 | div.AvatarEquips>div.item:not(.arti)>div { 504 | width: 280px; 505 | height: 135px; 506 | background-color: var(--tableBg); 507 | box-shadow: 0px 15px 15px rgb(0 0 0 / 40%); 508 | border-radius: 15px; 509 | border: none; 510 | } 511 | 512 | div.AvatarEquips>div.item:not(.arti)>div.weapon { 513 | height: 145px; 514 | } 515 | 516 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip { 517 | color: var(--ambrLight); 518 | font-size: 18px; 519 | width: 280px; 520 | position: relative; 521 | top: 15px; 522 | text-align: center; 523 | } 524 | 525 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip::before { 526 | content: "你怎么不强化啊?"; 527 | } 528 | 529 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.ACE², 530 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.ACE, 531 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.SSS { 532 | color: #e85656; 533 | font-weight: 700; 534 | } 535 | 536 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.SS, 537 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.S, 538 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.A { 539 | color: #ffffff; 540 | font-weight: 400; 541 | } 542 | 543 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.ACE²::before { 544 | content: "晒吧,欧不可耻,只是可恨"; 545 | } 546 | 547 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.ACE::before { 548 | content: "达成成就:阳寿圣遗物"; 549 | } 550 | 551 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.SSS::before { 552 | content: "达成成就:高分圣遗物"; 553 | } 554 | 555 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.SS::before { 556 | content: "以普遍理性而论,很好"; 557 | } 558 | 559 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.S::before { 560 | content: "达成成就:合格圣遗物"; 561 | } 562 | 563 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.A::before { 564 | content: "嗯嗯好好好可以(敷衍"; 565 | } 566 | 567 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.tip.B::before { 568 | content: "抽卡有保底,圣遗物无下限"; 569 | } 570 | 571 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail { 572 | font-size: 40px; 573 | font-weight: 700; 574 | display: flex; 575 | flex-direction: row; 576 | align-items: center; 577 | justify-content: space-evenly; 578 | width: 280px; 579 | height: 75px; 580 | position: relative; 581 | top: 20px; 582 | } 583 | 584 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div { 585 | font-family: "PanelNumFont"; 586 | color: var(--artiMark1); 587 | width: 90px; 588 | height: 65px; 589 | display: flex; 590 | align-items: center; 591 | justify-content: center; 592 | flex-direction: column; 593 | } 594 | 595 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.level.ACE², 596 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.level.ACE { 597 | color: var(--artiMark4); 598 | } 599 | 600 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.level.SSS, 601 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.level.SS { 602 | color: var(--artiMark3); 603 | } 604 | 605 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.level.S, 606 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.level.A { 607 | color: var(--artiMark2); 608 | } 609 | 610 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.level::after, 611 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.goal::after { 612 | font-family: "PanelFont"; 613 | content: "圣遗物评级"; 614 | font-size: 18px; 615 | font-weight: 400; 616 | color: var(--ambrLight); 617 | text-shadow: none; 618 | } 619 | 620 | div.AvatarEquips>div.item:not(.arti)>div.mark>div.detail>div.goal::after { 621 | content: "圣遗物评分"; 622 | } 623 | 624 | div.AvatarEquips>div.item:not(.arti)>div.weapon>img.UIWeaponIcon { 625 | width: 145px; 626 | height: 145px; 627 | position: relative; 628 | top: 0px; 629 | left: 0px; 630 | border-radius: 15px 0 0 15px; 631 | } 632 | 633 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.level, 634 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.affix { 635 | width: 55px; 636 | height: 30px; 637 | font-size: 18px; 638 | text-shadow: 3px 3px 3px rgba(0, 0, 0, 0.4); 639 | position: relative; 640 | top: -150px; 641 | left: 0px; 642 | display: flex; 643 | flex-direction: row; 644 | justify-content: center; 645 | align-items: center; 646 | } 647 | 648 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.level { 649 | background-color: var(--itemRarity1); 650 | border-radius: 15px 0 0 0; 651 | } 652 | 653 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.level::before { 654 | content: "Lv."; 655 | } 656 | 657 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.level.r5 { 658 | background-color: var(--itemRarity5); 659 | } 660 | 661 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.level.r4 { 662 | background-color: var(--itemRarity4); 663 | } 664 | 665 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.level.r3 { 666 | background-color: var(--itemRarity3); 667 | } 668 | 669 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.level.r2 { 670 | background-color: var(--itemRarity2); 671 | } 672 | 673 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.affix { 674 | background-color: var(--weaponAffix1); 675 | border-radius: 0 0 15px 0; 676 | } 677 | 678 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.affix::before { 679 | content: "精"; 680 | padding-right: 5px; 681 | } 682 | 683 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.affix.a5 { 684 | background-color: var(--weaponAffix5); 685 | } 686 | 687 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.affix.a4 { 688 | background-color: var(--weaponAffix4); 689 | } 690 | 691 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.affix.a3 { 692 | background-color: var(--weaponAffix3); 693 | } 694 | 695 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.affix.a2 { 696 | background-color: var(--weaponAffix2); 697 | } 698 | 699 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.affix.a1 { 700 | opacity: 0; 701 | } 702 | 703 | div.AvatarEquips>div.item:not(.arti)>div.weapon:has(div.affix.a1)>div.level { 704 | border-bottom-right-radius: 15px; 705 | } 706 | 707 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.name, 708 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.prop { 709 | font-size: 25px; 710 | font-weight: 700; 711 | width: 180px; 712 | height: 32px; 713 | position: relative; 714 | top: -195px; 715 | right: -80px; 716 | text-align: right; 717 | } 718 | 719 | div.AvatarEquips>div.item:not(.arti)>div.weapon>div.prop { 720 | color: var(--ambrLight); 721 | font-size: 18px; 722 | font-weight: normal; 723 | top: -170px; 724 | } 725 | 726 | div.AvatarEquips>div.item.arti>img.UIRelicIcon { 727 | width: 90px; 728 | height: 90px; 729 | position: relative; 730 | left: -95px; 731 | border-radius: 15px 0 15px 0; 732 | background-color: var(--itemRarity1); 733 | background-image: linear-gradient(136deg, rgba(49, 43, 71, .5294117647058824), transparent); 734 | } 735 | 736 | div.AvatarEquips>div.item.arti>img.UIRelicIcon.r5 { 737 | background-color: var(--itemRarity5); 738 | } 739 | 740 | div.AvatarEquips>div.item.arti>img.UIRelicIcon.r4 { 741 | background-color: var(--itemRarity4); 742 | } 743 | 744 | div.AvatarEquips>div.item.arti>img.UIRelicIcon.r3 { 745 | background-color: var(--itemRarity3); 746 | } 747 | 748 | div.AvatarEquips>div.item.arti>img.UIRelicIcon.r2 { 749 | background-color: var(--itemRarity2); 750 | } 751 | 752 | div.AvatarEquips>div.item.arti>div.level { 753 | width: 90px; 754 | min-height: 30px; 755 | position: relative; 756 | top: -15px; 757 | left: -95px; 758 | font-size: 18px; 759 | color: rgb(74, 83, 102); 760 | background-color: rgb(233, 229, 220); 761 | border-bottom-right-radius: 15px; 762 | display: flex; 763 | justify-content: center; 764 | align-items: center; 765 | } 766 | 767 | div.AvatarEquips>div.item.arti>div.level::before { 768 | content: "Lv."; 769 | } 770 | 771 | div.AvatarEquips>div.item.arti>div.title { 772 | min-height: 100px; 773 | width: 175px; 774 | display: flex; 775 | flex-direction: column; 776 | align-content: flex-start; 777 | justify-content: flex-end; 778 | align-items: center; 779 | position: relative; 780 | top: -120px; 781 | left: 45px; 782 | color: #ffffff; 783 | font-size: 25px; 784 | font-weight: 700; 785 | } 786 | 787 | div.AvatarEquips>div.item.arti>div.title>div.mark { 788 | font-family: "PanelNumFont"; 789 | font-size: 32px; 790 | color: var(--artiMark1); 791 | margin-top: 10px; 792 | } 793 | 794 | div.AvatarEquips>div.item.arti>div.title>div.mark.ACE², 795 | div.AvatarEquips>div.item.arti>div.title>div.mark.ACE { 796 | color: var(--artiMark4); 797 | } 798 | 799 | div.AvatarEquips>div.item.arti>div.title>div.mark.SSS, 800 | div.AvatarEquips>div.item.arti>div.title>div.mark.SS { 801 | color: var(--artiMark3); 802 | } 803 | 804 | div.AvatarEquips>div.item.arti>div.title>div.mark.S, 805 | div.AvatarEquips>div.item.arti>div.title>div.mark.A { 806 | color: var(--artiMark2); 807 | } 808 | 809 | div.AvatarEquips>div.item.arti>ul.affix { 810 | min-height: 155px; 811 | width: 250px; 812 | position: relative; 813 | bottom: 105px; 814 | display: flex; 815 | flex-direction: column; 816 | justify-content: space-evenly; 817 | list-style-type: none; 818 | margin-block-start: unset; 819 | margin-block-end: unset; 820 | margin-inline-start: unset; 821 | margin-inline-end: unset; 822 | padding-inline-start: unset; 823 | } 824 | 825 | div.AvatarEquips>div.item.arti>ul.affix>li { 826 | font-size: 18px; 827 | font-weight: 400; 828 | text-shadow: 3px 3px 3px rgb(0 0 0 / 40%); 829 | display: flex; 830 | flex-direction: row; 831 | align-items: center; 832 | } 833 | 834 | div.AvatarEquips>div.item.arti>ul.affix>li.main { 835 | font-size: 18px; 836 | font-weight: 700; 837 | text-shadow: 5px 5px 5px rgb(0 0 0 / 68%); 838 | } 839 | 840 | div.AvatarEquips>div.item.arti>ul.affix>li.sub.unuse { 841 | color: #aaaaaa; 842 | text-shadow: none; 843 | } 844 | 845 | div.AvatarEquips>div.item.arti>ul.affix>li.sub.use { 846 | color: #ffffff; 847 | text-shadow: none; 848 | } 849 | 850 | div.AvatarEquips>div.item.arti>ul.affix>li.sub.great { 851 | color: var(--artiMark3); 852 | text-shadow: none; 853 | } 854 | 855 | div.AvatarEquips>div.item.arti>ul.affix>li>div.key { 856 | width: 110px; 857 | } 858 | 859 | div.AvatarEquips>div.item.arti>ul.affix>li>div.value, 860 | div.AvatarEquips>div.item.arti>ul.affix>li>div.goal { 861 | width: 70px; 862 | text-align: left; 863 | } 864 | 865 | div.AvatarEquips>div.item.arti>ul.affix>li>div.goal { 866 | text-align: right; 867 | font-size: small; 868 | opacity: 0.68; 869 | } 870 | 871 | div.AvatarEquips>div.item.arti>ul.affix>li>div.value::before { 872 | content: "+"; 873 | padding-right: 2px; 874 | } 875 | 876 | div.AvatarEquips>div.item.arti>ul.affix>li>div.value.pct::after { 877 | content: "%"; 878 | } 879 | 880 | div.AvatarEquips>div.item.arti>ul.affix>li>div.goal::after { 881 | content: "分"; 882 | padding-left: 3px; 883 | } 884 | 885 | div.AvatarEquips>div.item.arti>div.note { 886 | width: 280px; 887 | min-height: 35px; 888 | font-size: 16px; 889 | position: relative; 890 | top: -100px; 891 | display: flex; 892 | align-items: center; 893 | align-content: center; 894 | justify-content: space-between; 895 | flex-direction: row; 896 | } 897 | 898 | div.AvatarEquips>div.item.arti>div.note>div { 899 | width: 40px; 900 | height: 35px; 901 | background-color: #ff5652; 902 | border-radius: 0 15px 0 15px; 903 | display: flex; 904 | align-items: center; 905 | justify-content: center; 906 | } 907 | 908 | div.AvatarEquips>div.item.arti>div.note>div.nohit::before { 909 | content: "歪"; 910 | padding-right: 3px; 911 | font-size: 10px; 912 | } 913 | 914 | div.AvatarEquips>div.item.arti>div.note>div.nohit::after { 915 | content: "次"; 916 | padding-left: 3px; 917 | font-size: 10px; 918 | } 919 | 920 | div.AvatarEquips>div.item.arti>div.note>div.calc { 921 | width: auto; 922 | min-width: 130px; 923 | background-color: var(--tableBg); 924 | border-radius: 15px 0 15px 0; 925 | } 926 | 927 | div.AvatarEquips>div.item.arti>div.note>div.calc>div { 928 | display: flex; 929 | align-items: center; 930 | justify-content: center; 931 | } 932 | 933 | div.AvatarEquips>div.item.arti>div.note>div.calc>div.main { 934 | width: 110px; 935 | } 936 | 937 | div.AvatarEquips>div.item.arti>div.note>div.calc>div.total { 938 | width: 130px; 939 | } 940 | 941 | div.AvatarEquips>div.item.arti>div.note:not(:has(div.nohit))>div.calc { 942 | position: relative; 943 | right: -150px; 944 | } 945 | 946 | div.AvatarEquips>div.item.arti>div.note:not(:has(div.nohit)):has(div.main)>div.calc { 947 | position: relative; 948 | right: -40px; 949 | } 950 | 951 | div.AvatarEquips>div.item.arti>div.note>div.calc>div.main::before { 952 | content: "主词条・"; 953 | font-size: 10px; 954 | } 955 | 956 | div.AvatarEquips>div.item.arti>div.note>div.calc>div.total::before { 957 | content: "总分对齐・"; 958 | font-size: 10px; 959 | } 960 | 961 | div.AvatarEquips>div.item.arti>div.note>div.calc>div.main::after, 962 | div.AvatarEquips>div.item.arti>div.note>div.calc>div.total::after { 963 | content: "%"; 964 | } 965 | 966 | ul.AvatarDamage, 967 | ul.AvatarBuffs { 968 | height: auto; 969 | min-height: 90px; 970 | width: 900px; 971 | border-radius: 15px; 972 | background-color: var(--tableBg); 973 | box-shadow: 0px 15px 15px rgb(0 0 0 / 40%); 974 | position: relative; 975 | top: 0px; 976 | left: 30px; 977 | display: flex; 978 | flex-direction: column; 979 | justify-content: space-evenly; 980 | list-style-type: none; 981 | margin-block-start: unset; 982 | margin-block-end: 30px; 983 | margin-inline-start: unset; 984 | margin-inline-end: unset; 985 | padding-inline-start: unset; 986 | } 987 | 988 | ul.AvatarDamage>li, 989 | ul.AvatarBuffs>li { 990 | color: #ffffff; 991 | height: auto; 992 | min-height: 40px; 993 | padding-left: 25px; 994 | font-size: 20px; 995 | font-weight: 400; 996 | display: flex; 997 | flex-direction: row; 998 | align-items: stretch; 999 | border-bottom: 2px solid rgba(255 255 255 / 20%); 1000 | } 1001 | 1002 | ul.AvatarDamage>li:last-child, 1003 | ul.AvatarBuffs>li:last-child { 1004 | border-bottom: none; 1005 | } 1006 | 1007 | ul.AvatarDamage>li.title, 1008 | ul.AvatarBuffs>li.title { 1009 | height: 50px; 1010 | font-size: 25px; 1011 | font-weight: 700; 1012 | padding-left: 15px; 1013 | display: flex; 1014 | align-items: center; 1015 | } 1016 | 1017 | ul.AvatarDamage>li.title::after, 1018 | ul.AvatarBuffs>li.title::after { 1019 | font-size: 18px; 1020 | font-weight: 400; 1021 | opacity: 0.68; 1022 | color: var(--ambrLight); 1023 | padding-left: 20px; 1024 | } 1025 | 1026 | ul.AvatarDamage>li>div, 1027 | ul.AvatarBuffs>li>div { 1028 | color: var(--ambrLight); 1029 | height: auto; 1030 | min-height: 25px; 1031 | padding: 0.4em; 1032 | display: flex; 1033 | flex: 1; 1034 | align-items: center; 1035 | justify-content: center; 1036 | } 1037 | 1038 | ul.AvatarDamage>li>div:first-child, 1039 | ul.AvatarBuffs>li>div:first-child { 1040 | color: #ffffff; 1041 | text-align: right; 1042 | justify-content: flex-end; 1043 | padding-right: 20px; 1044 | } 1045 | 1046 | ul.AvatarDamage>li>div:not(:last-child), 1047 | ul.AvatarBuffs>li>div:not(:last-child) { 1048 | border-right: 2px solid rgba(255 255 255 / 20%); 1049 | } 1050 | 1051 | ul.AvatarDamage>li.title { 1052 | font-family: "PanelNumFont"; 1053 | } 1054 | 1055 | ul.AvatarDamage>li.title::before { 1056 | content: "伤害计算・评级"; 1057 | font-family: "PanelFont"; 1058 | padding-right: 15px; 1059 | } 1060 | 1061 | ul.AvatarDamage>li.title::after { 1062 | content: "伤害以 86 级怪物为基准,等级不同数值有微小偏差"; 1063 | font-family: "PanelFont"; 1064 | } 1065 | 1066 | ul.AvatarDamage>li>div:first-child { 1067 | width: 308px; 1068 | } 1069 | 1070 | ul.AvatarDamage>li>div:not(:first-child) { 1071 | width: 270px; 1072 | } 1073 | 1074 | 1075 | ul.AvatarDamage>li:nth-child(2)>div:first-child::before { 1076 | content: "伤害类型"; 1077 | color: #ffffff; 1078 | } 1079 | 1080 | ul.AvatarDamage>li:nth-child(2)>div:nth-child(2)::before { 1081 | content: "暴击伤害"; 1082 | color: #ffffff; 1083 | } 1084 | 1085 | ul.AvatarDamage>li:nth-child(2)>div:last-child::before { 1086 | content: "期望伤害"; 1087 | color: #ffffff; 1088 | } 1089 | 1090 | ul.AvatarBuffs>li>div:first-child { 1091 | max-width: 270px; 1092 | } 1093 | 1094 | ul.AvatarBuffs>li.title::before { 1095 | content: "Buff 列表"; 1096 | } 1097 | 1098 | ul.AvatarBuffs>li.title::after { 1099 | content: "部分 Buff 的触发条件以及层数可能影响实际伤害结果"; 1100 | } 1101 | 1102 | div.copyright { 1103 | color: #ffffff; 1104 | opacity: 0.68; 1105 | text-shadow: 3px 5px 4px #333333ad; 1106 | font-size: 20px; 1107 | width: 960px; 1108 | text-align: center; 1109 | padding-bottom: 20px; 1110 | } 1111 | 1112 | div.copyright::before { 1113 | content: "Data from Enka.Network × Powered by NoneBot2 × Inspired by Miao-Plugin"; 1114 | } -------------------------------------------------------------------------------- /data/gspanel/panel-0.2.7.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 |
12 | 13 |
{{ uid }}
14 |
15 |
等级 {{ data["level"] }}
16 |
好感 {{ data["fetter"] }}
17 |
18 |
19 | {% for con in data["consts"] %} 20 |
21 | {% endfor %} 22 |
23 |
24 |

{{ data["slogan"] }}·{{ data["name"] }}

25 |
26 | {% for sKey, sValue in data["skills"].items() %} 27 |
28 |
29 | 30 |
{{ sValue['level'] }}
31 |
32 | {% endfor %} 33 |
34 | 49 |
50 |
51 |
52 |
53 |
54 |
55 |
{{ data["relicCalc"]["rank"] }}
56 |
{{ data["relicCalc"]["total"] }}
57 |
58 |
59 |
60 | 61 |
{{ data["weapon"]["level"] }}
62 |
{{ data["weapon"]["affix"] }}
63 |
{{ data["weapon"]["name"] }}
64 |
基础攻击力 {{ data["weapon"]["main"] }}
65 |
{{ data["weapon"]["sub"]["prop"] }} {{ data["weapon"]["sub"]["value"] }}
66 |
67 |
68 | {% for arti in data["relics"] %} 69 |
70 | 71 |
{{ arti["level"] }}
72 |
73 |
{{ arti["name"] }}
74 |
{{ arti["calc"]["total"] }} - {{ arti["calc"]['rank'] }}
75 |
76 |
    77 |
  • 78 |
    {{ arti["main"]["prop"] }}
    79 |
    {{ arti["main"]["value"] }}
    80 |
    {{ arti["calc"]["main"] }}
    81 |
  • 82 | 83 | {% for sub in arti["sub"] %} 84 |
  • 85 |
    {{ sub["prop"] }}
    86 |
    {{ sub["value"] }}
    87 |
    {{ arti["calc"]["sub"][loop.index0]["goal"] }}
    88 |
  • 89 | {% endfor %} 90 |
91 |
92 | {% if arti["calc"]["nohit"] %} 93 |
{{ arti["calc"]["nohit"] }}
94 | {% endif %} 95 |
96 | {% if arti["pos"] >= 3 %} 97 |
{{ arti["calc"]["main_pct"] }}
98 | {% endif %} 99 |
{{ arti["calc"]["total_pct"] }}
100 |
101 |
102 |
103 | {% endfor %} 104 |
105 | {% if data["damage"] %} 106 | 122 | {% if data["damage"]["buff"] %} 123 | 132 | {% endif %} 133 | {% endif %} 134 | 135 |
136 | 137 | -------------------------------------------------------------------------------- /data/gspanel/relic-append.json: -------------------------------------------------------------------------------- 1 | { 2 | "101021": "FIGHT_PROP_HP", 3 | "101022": "FIGHT_PROP_HP", 4 | "201021": "FIGHT_PROP_HP", 5 | "201022": "FIGHT_PROP_HP", 6 | "201023": "FIGHT_PROP_HP", 7 | "301021": "FIGHT_PROP_HP", 8 | "301022": "FIGHT_PROP_HP", 9 | "301023": "FIGHT_PROP_HP", 10 | "301024": "FIGHT_PROP_HP", 11 | "401021": "FIGHT_PROP_HP", 12 | "401022": "FIGHT_PROP_HP", 13 | "401023": "FIGHT_PROP_HP", 14 | "401024": "FIGHT_PROP_HP", 15 | "501021": "FIGHT_PROP_HP", 16 | "501022": "FIGHT_PROP_HP", 17 | "501023": "FIGHT_PROP_HP", 18 | "501024": "FIGHT_PROP_HP", 19 | "101031": "FIGHT_PROP_HP_PERCENT", 20 | "101032": "FIGHT_PROP_HP_PERCENT", 21 | "201031": "FIGHT_PROP_HP_PERCENT", 22 | "201032": "FIGHT_PROP_HP_PERCENT", 23 | "201033": "FIGHT_PROP_HP_PERCENT", 24 | "301031": "FIGHT_PROP_HP_PERCENT", 25 | "301032": "FIGHT_PROP_HP_PERCENT", 26 | "301033": "FIGHT_PROP_HP_PERCENT", 27 | "301034": "FIGHT_PROP_HP_PERCENT", 28 | "401031": "FIGHT_PROP_HP_PERCENT", 29 | "401032": "FIGHT_PROP_HP_PERCENT", 30 | "401033": "FIGHT_PROP_HP_PERCENT", 31 | "401034": "FIGHT_PROP_HP_PERCENT", 32 | "501031": "FIGHT_PROP_HP_PERCENT", 33 | "501032": "FIGHT_PROP_HP_PERCENT", 34 | "501033": "FIGHT_PROP_HP_PERCENT", 35 | "501034": "FIGHT_PROP_HP_PERCENT", 36 | "101051": "FIGHT_PROP_ATTACK", 37 | "101052": "FIGHT_PROP_ATTACK", 38 | "201051": "FIGHT_PROP_ATTACK", 39 | "201052": "FIGHT_PROP_ATTACK", 40 | "201053": "FIGHT_PROP_ATTACK", 41 | "301051": "FIGHT_PROP_ATTACK", 42 | "301052": "FIGHT_PROP_ATTACK", 43 | "301053": "FIGHT_PROP_ATTACK", 44 | "301054": "FIGHT_PROP_ATTACK", 45 | "401051": "FIGHT_PROP_ATTACK", 46 | "401052": "FIGHT_PROP_ATTACK", 47 | "401053": "FIGHT_PROP_ATTACK", 48 | "401054": "FIGHT_PROP_ATTACK", 49 | "501051": "FIGHT_PROP_ATTACK", 50 | "501052": "FIGHT_PROP_ATTACK", 51 | "501053": "FIGHT_PROP_ATTACK", 52 | "501054": "FIGHT_PROP_ATTACK", 53 | "101061": "FIGHT_PROP_ATTACK_PERCENT", 54 | "101062": "FIGHT_PROP_ATTACK_PERCENT", 55 | "201061": "FIGHT_PROP_ATTACK_PERCENT", 56 | "201062": "FIGHT_PROP_ATTACK_PERCENT", 57 | "201063": "FIGHT_PROP_ATTACK_PERCENT", 58 | "301061": "FIGHT_PROP_ATTACK_PERCENT", 59 | "301062": "FIGHT_PROP_ATTACK_PERCENT", 60 | "301063": "FIGHT_PROP_ATTACK_PERCENT", 61 | "301064": "FIGHT_PROP_ATTACK_PERCENT", 62 | "401061": "FIGHT_PROP_ATTACK_PERCENT", 63 | "401062": "FIGHT_PROP_ATTACK_PERCENT", 64 | "401063": "FIGHT_PROP_ATTACK_PERCENT", 65 | "401064": "FIGHT_PROP_ATTACK_PERCENT", 66 | "501061": "FIGHT_PROP_ATTACK_PERCENT", 67 | "501062": "FIGHT_PROP_ATTACK_PERCENT", 68 | "501063": "FIGHT_PROP_ATTACK_PERCENT", 69 | "501064": "FIGHT_PROP_ATTACK_PERCENT", 70 | "101081": "FIGHT_PROP_DEFENSE", 71 | "101082": "FIGHT_PROP_DEFENSE", 72 | "201081": "FIGHT_PROP_DEFENSE", 73 | "201082": "FIGHT_PROP_DEFENSE", 74 | "201083": "FIGHT_PROP_DEFENSE", 75 | "301081": "FIGHT_PROP_DEFENSE", 76 | "301082": "FIGHT_PROP_DEFENSE", 77 | "301083": "FIGHT_PROP_DEFENSE", 78 | "301084": "FIGHT_PROP_DEFENSE", 79 | "401081": "FIGHT_PROP_DEFENSE", 80 | "401082": "FIGHT_PROP_DEFENSE", 81 | "401083": "FIGHT_PROP_DEFENSE", 82 | "401084": "FIGHT_PROP_DEFENSE", 83 | "501081": "FIGHT_PROP_DEFENSE", 84 | "501082": "FIGHT_PROP_DEFENSE", 85 | "501083": "FIGHT_PROP_DEFENSE", 86 | "501084": "FIGHT_PROP_DEFENSE", 87 | "101091": "FIGHT_PROP_DEFENSE_PERCENT", 88 | "101092": "FIGHT_PROP_DEFENSE_PERCENT", 89 | "201091": "FIGHT_PROP_DEFENSE_PERCENT", 90 | "201092": "FIGHT_PROP_DEFENSE_PERCENT", 91 | "201093": "FIGHT_PROP_DEFENSE_PERCENT", 92 | "301091": "FIGHT_PROP_DEFENSE_PERCENT", 93 | "301092": "FIGHT_PROP_DEFENSE_PERCENT", 94 | "301093": "FIGHT_PROP_DEFENSE_PERCENT", 95 | "301094": "FIGHT_PROP_DEFENSE_PERCENT", 96 | "401091": "FIGHT_PROP_DEFENSE_PERCENT", 97 | "401092": "FIGHT_PROP_DEFENSE_PERCENT", 98 | "401093": "FIGHT_PROP_DEFENSE_PERCENT", 99 | "401094": "FIGHT_PROP_DEFENSE_PERCENT", 100 | "501091": "FIGHT_PROP_DEFENSE_PERCENT", 101 | "501092": "FIGHT_PROP_DEFENSE_PERCENT", 102 | "501093": "FIGHT_PROP_DEFENSE_PERCENT", 103 | "501094": "FIGHT_PROP_DEFENSE_PERCENT", 104 | "101231": "FIGHT_PROP_CHARGE_EFFICIENCY", 105 | "101232": "FIGHT_PROP_CHARGE_EFFICIENCY", 106 | "201231": "FIGHT_PROP_CHARGE_EFFICIENCY", 107 | "201232": "FIGHT_PROP_CHARGE_EFFICIENCY", 108 | "201233": "FIGHT_PROP_CHARGE_EFFICIENCY", 109 | "301231": "FIGHT_PROP_CHARGE_EFFICIENCY", 110 | "301232": "FIGHT_PROP_CHARGE_EFFICIENCY", 111 | "301233": "FIGHT_PROP_CHARGE_EFFICIENCY", 112 | "301234": "FIGHT_PROP_CHARGE_EFFICIENCY", 113 | "401231": "FIGHT_PROP_CHARGE_EFFICIENCY", 114 | "401232": "FIGHT_PROP_CHARGE_EFFICIENCY", 115 | "401233": "FIGHT_PROP_CHARGE_EFFICIENCY", 116 | "401234": "FIGHT_PROP_CHARGE_EFFICIENCY", 117 | "501231": "FIGHT_PROP_CHARGE_EFFICIENCY", 118 | "501232": "FIGHT_PROP_CHARGE_EFFICIENCY", 119 | "501233": "FIGHT_PROP_CHARGE_EFFICIENCY", 120 | "501234": "FIGHT_PROP_CHARGE_EFFICIENCY", 121 | "101241": "FIGHT_PROP_ELEMENT_MASTERY", 122 | "101242": "FIGHT_PROP_ELEMENT_MASTERY", 123 | "201241": "FIGHT_PROP_ELEMENT_MASTERY", 124 | "201242": "FIGHT_PROP_ELEMENT_MASTERY", 125 | "201243": "FIGHT_PROP_ELEMENT_MASTERY", 126 | "301241": "FIGHT_PROP_ELEMENT_MASTERY", 127 | "301242": "FIGHT_PROP_ELEMENT_MASTERY", 128 | "301243": "FIGHT_PROP_ELEMENT_MASTERY", 129 | "301244": "FIGHT_PROP_ELEMENT_MASTERY", 130 | "401241": "FIGHT_PROP_ELEMENT_MASTERY", 131 | "401242": "FIGHT_PROP_ELEMENT_MASTERY", 132 | "401243": "FIGHT_PROP_ELEMENT_MASTERY", 133 | "401244": "FIGHT_PROP_ELEMENT_MASTERY", 134 | "501241": "FIGHT_PROP_ELEMENT_MASTERY", 135 | "501242": "FIGHT_PROP_ELEMENT_MASTERY", 136 | "501243": "FIGHT_PROP_ELEMENT_MASTERY", 137 | "501244": "FIGHT_PROP_ELEMENT_MASTERY", 138 | "101201": "FIGHT_PROP_CRITICAL", 139 | "101202": "FIGHT_PROP_CRITICAL", 140 | "201201": "FIGHT_PROP_CRITICAL", 141 | "201202": "FIGHT_PROP_CRITICAL", 142 | "201203": "FIGHT_PROP_CRITICAL", 143 | "301201": "FIGHT_PROP_CRITICAL", 144 | "301202": "FIGHT_PROP_CRITICAL", 145 | "301203": "FIGHT_PROP_CRITICAL", 146 | "301204": "FIGHT_PROP_CRITICAL", 147 | "401201": "FIGHT_PROP_CRITICAL", 148 | "401202": "FIGHT_PROP_CRITICAL", 149 | "401203": "FIGHT_PROP_CRITICAL", 150 | "401204": "FIGHT_PROP_CRITICAL", 151 | "501201": "FIGHT_PROP_CRITICAL", 152 | "501202": "FIGHT_PROP_CRITICAL", 153 | "501203": "FIGHT_PROP_CRITICAL", 154 | "501204": "FIGHT_PROP_CRITICAL", 155 | "101221": "FIGHT_PROP_CRITICAL_HURT", 156 | "101222": "FIGHT_PROP_CRITICAL_HURT", 157 | "201221": "FIGHT_PROP_CRITICAL_HURT", 158 | "201222": "FIGHT_PROP_CRITICAL_HURT", 159 | "201223": "FIGHT_PROP_CRITICAL_HURT", 160 | "301221": "FIGHT_PROP_CRITICAL_HURT", 161 | "301222": "FIGHT_PROP_CRITICAL_HURT", 162 | "301223": "FIGHT_PROP_CRITICAL_HURT", 163 | "301224": "FIGHT_PROP_CRITICAL_HURT", 164 | "401221": "FIGHT_PROP_CRITICAL_HURT", 165 | "401222": "FIGHT_PROP_CRITICAL_HURT", 166 | "401223": "FIGHT_PROP_CRITICAL_HURT", 167 | "401224": "FIGHT_PROP_CRITICAL_HURT", 168 | "501221": "FIGHT_PROP_CRITICAL_HURT", 169 | "501222": "FIGHT_PROP_CRITICAL_HURT", 170 | "501223": "FIGHT_PROP_CRITICAL_HURT", 171 | "501224": "FIGHT_PROP_CRITICAL_HURT", 172 | "999001": "FIGHT_PROP_CRITICAL_HURT", 173 | "999002": "FIGHT_PROP_CRITICAL", 174 | "999003": "FIGHT_PROP_ATTACK", 175 | "999004": "FIGHT_PROP_DEFENSE", 176 | "998001": "FIGHT_PROP_HP_PERCENT", 177 | "998002": "FIGHT_PROP_ATTACK_PERCENT", 178 | "998003": "FIGHT_PROP_DEFENSE_PERCENT", 179 | "998004": "FIGHT_PROP_CRITICAL", 180 | "998005": "FIGHT_PROP_CHARGE_EFFICIENCY", 181 | "998006": "FIGHT_PROP_ELEMENT_MASTERY", 182 | "998007": "FIGHT_PROP_CRITICAL_HURT", 183 | "995001": "FIGHT_PROP_HP_PERCENT", 184 | "995002": "FIGHT_PROP_ATTACK_PERCENT", 185 | "995003": "FIGHT_PROP_DEFENSE_PERCENT", 186 | "995004": "FIGHT_PROP_CRITICAL", 187 | "995005": "FIGHT_PROP_CHARGE_EFFICIENCY", 188 | "995006": "FIGHT_PROP_ELEMENT_MASTERY", 189 | "995007": "FIGHT_PROP_CRITICAL_HURT", 190 | "997001": "FIGHT_PROP_HP_PERCENT", 191 | "997002": "FIGHT_PROP_ATTACK_PERCENT", 192 | "997003": "FIGHT_PROP_DEFENSE_PERCENT", 193 | "997004": "FIGHT_PROP_CRITICAL", 194 | "997005": "FIGHT_PROP_CHARGE_EFFICIENCY", 195 | "997006": "FIGHT_PROP_ELEMENT_MASTERY", 196 | "997007": "FIGHT_PROP_CRITICAL_HURT", 197 | "996001": "FIGHT_PROP_HP_PERCENT", 198 | "996002": "FIGHT_PROP_ATTACK_PERCENT", 199 | "996003": "FIGHT_PROP_DEFENSE_PERCENT", 200 | "996004": "FIGHT_PROP_CRITICAL", 201 | "996005": "FIGHT_PROP_CHARGE_EFFICIENCY", 202 | "996006": "FIGHT_PROP_ELEMENT_MASTERY", 203 | "996007": "FIGHT_PROP_CRITICAL_HURT", 204 | "996008": "FIGHT_PROP_HP", 205 | "996009": "FIGHT_PROP_ATTACK", 206 | "996010": "FIGHT_PROP_DEFENSE", 207 | "994001": "FIGHT_PROP_CRITICAL", 208 | "993001": "FIGHT_PROP_CRITICAL_HURT", 209 | "992001": "FIGHT_PROP_SHIELD_COST_MINUS_RATIO", 210 | "991001": "FIGHT_PROP_HEAL_ADD", 211 | "990001": "FIGHT_PROP_HEALED_ADD", 212 | "989001": "FIGHT_PROP_SKILL_CD_MINUS_RATIO", 213 | "988001": "FIGHT_PROP_SPEED_PERCENT", 214 | "987001": "FIGHT_PROP_FIRE_ADD_HURT", 215 | "986001": "FIGHT_PROP_ELEC_ADD_HURT", 216 | "985001": "FIGHT_PROP_WATER_ADD_HURT", 217 | "984001": "FIGHT_PROP_GRASS_ADD_HURT", 218 | "983001": "FIGHT_PROP_WIND_ADD_HURT", 219 | "982001": "FIGHT_PROP_ROCK_ADD_HURT", 220 | "981001": "FIGHT_PROP_ICE_ADD_HURT", 221 | "980001": "FIGHT_PROP_PHYSICAL_ADD_HURT", 222 | "979001": "FIGHT_PROP_FIRE_SUB_HURT", 223 | "978001": "FIGHT_PROP_ELEC_SUB_HURT", 224 | "977001": "FIGHT_PROP_WATER_SUB_HURT", 225 | "976001": "FIGHT_PROP_GRASS_SUB_HURT", 226 | "975001": "FIGHT_PROP_WIND_SUB_HURT", 227 | "974001": "FIGHT_PROP_ROCK_SUB_HURT", 228 | "973001": "FIGHT_PROP_ICE_SUB_HURT", 229 | "972001": "FIGHT_PROP_PHYSICAL_SUB_HURT", 230 | "971001": "FIGHT_PROP_ADD_HURT", 231 | "970001": "FIGHT_PROP_SUB_HURT", 232 | "969001": "FIGHT_PROP_DEFENSE", 233 | "968001": "FIGHT_PROP_ELEMENT_MASTERY", 234 | "967001": "FIGHT_PROP_CRITICAL", 235 | "966001": "FIGHT_PROP_ELEMENT_MASTERY", 236 | "961001": "FIGHT_PROP_HP_PERCENT", 237 | "961002": "FIGHT_PROP_ATTACK_PERCENT", 238 | "961003": "FIGHT_PROP_DEFENSE_PERCENT", 239 | "961004": "FIGHT_PROP_CRITICAL", 240 | "961005": "FIGHT_PROP_CHARGE_EFFICIENCY", 241 | "961006": "FIGHT_PROP_ELEMENT_MASTERY", 242 | "961007": "FIGHT_PROP_CRITICAL_HURT", 243 | "961008": "FIGHT_PROP_HP", 244 | "961009": "FIGHT_PROP_ATTACK", 245 | "961010": "FIGHT_PROP_DEFENSE", 246 | "962001": "FIGHT_PROP_HP_PERCENT", 247 | "962002": "FIGHT_PROP_ATTACK_PERCENT", 248 | "962003": "FIGHT_PROP_DEFENSE_PERCENT", 249 | "962004": "FIGHT_PROP_CRITICAL", 250 | "962005": "FIGHT_PROP_CHARGE_EFFICIENCY", 251 | "962006": "FIGHT_PROP_ELEMENT_MASTERY", 252 | "962007": "FIGHT_PROP_CRITICAL_HURT", 253 | "962008": "FIGHT_PROP_HP", 254 | "962009": "FIGHT_PROP_ATTACK", 255 | "962010": "FIGHT_PROP_DEFENSE", 256 | "963001": "FIGHT_PROP_HP_PERCENT", 257 | "963002": "FIGHT_PROP_ATTACK_PERCENT", 258 | "963003": "FIGHT_PROP_DEFENSE_PERCENT", 259 | "963004": "FIGHT_PROP_CRITICAL", 260 | "963005": "FIGHT_PROP_CHARGE_EFFICIENCY", 261 | "963006": "FIGHT_PROP_ELEMENT_MASTERY", 262 | "963007": "FIGHT_PROP_CRITICAL_HURT", 263 | "963008": "FIGHT_PROP_HP", 264 | "963009": "FIGHT_PROP_ATTACK", 265 | "963010": "FIGHT_PROP_DEFENSE", 266 | "964001": "FIGHT_PROP_HP_PERCENT", 267 | "964002": "FIGHT_PROP_ATTACK_PERCENT", 268 | "964003": "FIGHT_PROP_DEFENSE_PERCENT", 269 | "964004": "FIGHT_PROP_CRITICAL", 270 | "964005": "FIGHT_PROP_CHARGE_EFFICIENCY", 271 | "964006": "FIGHT_PROP_ELEMENT_MASTERY", 272 | "964007": "FIGHT_PROP_CRITICAL_HURT", 273 | "964008": "FIGHT_PROP_HP", 274 | "964009": "FIGHT_PROP_ATTACK", 275 | "964010": "FIGHT_PROP_DEFENSE", 276 | "965001": "FIGHT_PROP_HP_PERCENT", 277 | "965002": "FIGHT_PROP_ATTACK_PERCENT", 278 | "965003": "FIGHT_PROP_DEFENSE_PERCENT", 279 | "965004": "FIGHT_PROP_CRITICAL", 280 | "965005": "FIGHT_PROP_CHARGE_EFFICIENCY", 281 | "965006": "FIGHT_PROP_ELEMENT_MASTERY", 282 | "965007": "FIGHT_PROP_CRITICAL_HURT", 283 | "965008": "FIGHT_PROP_HP", 284 | "965009": "FIGHT_PROP_ATTACK", 285 | "965010": "FIGHT_PROP_DEFENSE", 286 | "951001": "FIGHT_PROP_HP_PERCENT", 287 | "951002": "FIGHT_PROP_ATTACK_PERCENT", 288 | "951003": "FIGHT_PROP_DEFENSE_PERCENT", 289 | "951004": "FIGHT_PROP_CRITICAL", 290 | "951005": "FIGHT_PROP_CHARGE_EFFICIENCY", 291 | "951006": "FIGHT_PROP_ELEMENT_MASTERY", 292 | "951007": "FIGHT_PROP_CRITICAL_HURT", 293 | "952001": "FIGHT_PROP_HP_PERCENT", 294 | "952002": "FIGHT_PROP_ATTACK_PERCENT", 295 | "952003": "FIGHT_PROP_DEFENSE_PERCENT", 296 | "952004": "FIGHT_PROP_CRITICAL", 297 | "952005": "FIGHT_PROP_CHARGE_EFFICIENCY", 298 | "952006": "FIGHT_PROP_ELEMENT_MASTERY", 299 | "952007": "FIGHT_PROP_CRITICAL_HURT", 300 | "953001": "FIGHT_PROP_HP_PERCENT", 301 | "953002": "FIGHT_PROP_ATTACK_PERCENT", 302 | "953003": "FIGHT_PROP_DEFENSE_PERCENT", 303 | "953004": "FIGHT_PROP_CRITICAL", 304 | "953005": "FIGHT_PROP_CHARGE_EFFICIENCY", 305 | "953006": "FIGHT_PROP_ELEMENT_MASTERY", 306 | "953007": "FIGHT_PROP_CRITICAL_HURT", 307 | "956001": "FIGHT_PROP_HP_PERCENT", 308 | "956002": "FIGHT_PROP_ATTACK_PERCENT", 309 | "956003": "FIGHT_PROP_DEFENSE_PERCENT", 310 | "956004": "FIGHT_PROP_CRITICAL", 311 | "956005": "FIGHT_PROP_CHARGE_EFFICIENCY", 312 | "956006": "FIGHT_PROP_ELEMENT_MASTERY", 313 | "956007": "FIGHT_PROP_CRITICAL_HURT", 314 | "941001": "FIGHT_PROP_HP_PERCENT", 315 | "941002": "FIGHT_PROP_ATTACK_PERCENT", 316 | "941003": "FIGHT_PROP_DEFENSE_PERCENT", 317 | "941004": "FIGHT_PROP_CRITICAL", 318 | "941005": "FIGHT_PROP_CHARGE_EFFICIENCY", 319 | "941006": "FIGHT_PROP_ELEMENT_MASTERY", 320 | "941007": "FIGHT_PROP_CRITICAL_HURT", 321 | "942001": "FIGHT_PROP_HP_PERCENT", 322 | "942002": "FIGHT_PROP_ATTACK_PERCENT", 323 | "942003": "FIGHT_PROP_DEFENSE_PERCENT", 324 | "942004": "FIGHT_PROP_CRITICAL", 325 | "942005": "FIGHT_PROP_CHARGE_EFFICIENCY", 326 | "942006": "FIGHT_PROP_ELEMENT_MASTERY", 327 | "942007": "FIGHT_PROP_CRITICAL_HURT", 328 | "943001": "FIGHT_PROP_HP_PERCENT", 329 | "943002": "FIGHT_PROP_ATTACK_PERCENT", 330 | "943003": "FIGHT_PROP_DEFENSE_PERCENT", 331 | "943004": "FIGHT_PROP_CRITICAL", 332 | "943005": "FIGHT_PROP_CHARGE_EFFICIENCY", 333 | "943006": "FIGHT_PROP_ELEMENT_MASTERY", 334 | "943007": "FIGHT_PROP_CRITICAL_HURT", 335 | "946001": "FIGHT_PROP_HP_PERCENT", 336 | "946002": "FIGHT_PROP_ATTACK_PERCENT", 337 | "946003": "FIGHT_PROP_DEFENSE_PERCENT", 338 | "946004": "FIGHT_PROP_CRITICAL", 339 | "946005": "FIGHT_PROP_CHARGE_EFFICIENCY", 340 | "946006": "FIGHT_PROP_ELEMENT_MASTERY", 341 | "946007": "FIGHT_PROP_CRITICAL_HURT", 342 | "947001": "FIGHT_PROP_HP_PERCENT", 343 | "947002": "FIGHT_PROP_ATTACK_PERCENT", 344 | "947003": "FIGHT_PROP_DEFENSE_PERCENT", 345 | "947004": "FIGHT_PROP_CRITICAL", 346 | "947005": "FIGHT_PROP_CHARGE_EFFICIENCY", 347 | "947006": "FIGHT_PROP_ELEMENT_MASTERY", 348 | "947007": "FIGHT_PROP_CRITICAL_HURT", 349 | "947008": "FIGHT_PROP_HP", 350 | "947009": "FIGHT_PROP_ATTACK", 351 | "947010": "FIGHT_PROP_DEFENSE" 352 | } -------------------------------------------------------------------------------- /data/gspanel/team-0.2.20.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --charRarity5: rgba(200, 124, 36, 0.68); 3 | --charRarity4: rgba(148, 112, 187, 0.68); 4 | --itemRarity5: #C87C24; 5 | --itemRarity4: #9470BB; 6 | --itemRarity3: #5987AD; 7 | --itemRarity2: #5A977A; 8 | --itemRarity1: #818486; 9 | --elemPyro: #FF9999; 10 | --elemHydro: #80C0FF; 11 | --elemAnemo: #80FFD7; 12 | --elemElectro: #FFACFF; 13 | --elemDendro: #99FF88; 14 | --elemCryo: #99FFFF; 15 | --elemGeo: #FFE699; 16 | --charCons6: #ff5722; 17 | --charCons5: #531ba9cf; 18 | --charCons4: #3955b7; 19 | --charCons3: #3e95b9; 20 | --charCons2: #339d61; 21 | --charCons1: #5cbac2; 22 | --weaponAffix5: #DEAF39; 23 | --weaponAffix4: #C539DE; 24 | --weaponAffix3: #396CDE; 25 | --weaponAffix2: #51B72F; 26 | --weaponAffix1: #A3A3A3; 27 | --artiMark4: #FF5722; 28 | --artiMark3: #FFE699; 29 | --artiMark2: #D699FF; 30 | --artiMark1: #FFFFFF; 31 | --ambrLight: rgb(233, 229, 220); 32 | --ambrDeep: rgb(74, 83, 102); 33 | --artiBg: rgba(46, 53, 62, .68); 34 | --tableBg: #2e353e; 35 | } 36 | 37 | @font-face { 38 | font-family: "PanelFont"; 39 | src: url(./font/HYWH-65W.ttf); 40 | font-weight: 400; 41 | font-style: normal 42 | } 43 | 44 | @font-face { 45 | font-family: "PanelNumFont"; 46 | src: url(./font/tttgbnumber.ttf); 47 | font-weight: 400; 48 | font-style: normal 49 | } 50 | 51 | body { 52 | overflow: overlay; 53 | margin: 0px; 54 | } 55 | 56 | #tcontainer { 57 | width: 960px; 58 | height: auto; 59 | min-height: 720px; 60 | font-family: "PanelFont"; 61 | background-repeat: no-repeat; 62 | background-position: center 0%; 63 | background-size: auto 100%; 64 | position: absolute; 65 | top: 0; 66 | left: 0; 67 | } 68 | 69 | #tcontainer.火 { 70 | background-image: url(./imgs/bg-pyro.jpg); 71 | } 72 | 73 | div.Avatars>div.avatar>div.detail>div.talent.火>div { 74 | background-image: url(./imgs/talent-pyro.png); 75 | } 76 | 77 | #tcontainer.水 { 78 | background-image: url(./imgs/bg-hydro.jpg); 79 | } 80 | 81 | div.Avatars>div.avatar>div.detail>div.talent.水>div { 82 | background-image: url(./imgs/talent-hydro.png); 83 | } 84 | 85 | #tcontainer.风 { 86 | background-image: url(./imgs/bg-anemo.jpg); 87 | } 88 | 89 | div.Avatars>div.avatar>div.detail>div.talent.风>div { 90 | background-image: url(./imgs/talent-anemo.png); 91 | } 92 | 93 | #tcontainer.雷 { 94 | background-image: url(./imgs/bg-electro.jpg); 95 | } 96 | 97 | div.Avatars>div.avatar>div.detail>div.talent.雷>div { 98 | background-image: url(./imgs/talent-electro.png); 99 | } 100 | 101 | #tcontainer.草 { 102 | background-image: url(./imgs/bg-dendro.jpg); 103 | } 104 | 105 | div.Avatars>div.avatar>div.detail>div.talent.草>div { 106 | background-image: url(./imgs/talent-dendro.png); 107 | } 108 | 109 | #tcontainer.冰 { 110 | background-image: url(./imgs/bg-cryo.jpg); 111 | } 112 | 113 | div.Avatars>div.avatar>div.detail>div.talent.冰>div { 114 | background-image: url(./imgs/talent-cryo.png); 115 | } 116 | 117 | #tcontainer.岩 { 118 | background-image: url(./imgs/bg-geo.jpg); 119 | } 120 | 121 | div.Avatars>div.avatar>div.detail>div.talent.岩>div { 122 | background-image: url(./imgs/talent-geo.png); 123 | } 124 | 125 | div.TopLeftPanel { 126 | position: relative; 127 | top: 20px; 128 | left: 20px; 129 | width: 325px; 130 | height: 543px; 131 | } 132 | 133 | div.TopLeftPanel>div.UID { 134 | position: relative; 135 | top: 10px; 136 | left: 10px; 137 | font-size: 24px; 138 | font-weight: 700; 139 | width: 300px; 140 | height: 30px; 141 | line-height: 30px; 142 | text-shadow: 0 0 3px #d3bc8e, 2px 2px 4px rgb(0 0 0 / 70%); 143 | color: rgba(255 255 255 / .68); 144 | } 145 | 146 | div.TopLeftPanel>div.UID::before { 147 | content: "#队伍伤害 UID"; 148 | } 149 | 150 | div.TopLeftPanel>div.rank { 151 | color: #ffffff; 152 | text-shadow: 0px 15px 7px rgb(0 0 0 / 68%); 153 | border-radius: 15px; 154 | position: relative; 155 | top: 37px; 156 | display: flex; 157 | flex-wrap: wrap; 158 | align-items: center; 159 | justify-content: space-between; 160 | } 161 | 162 | div.TopLeftPanel>div.rank>div { 163 | width: 60%; 164 | height: 91px; 165 | font-family: "PanelNumFont"; 166 | font-size: 42px; 167 | font-weight: 700; 168 | color: var(--artiMark1); 169 | display: flex; 170 | flex-direction: column; 171 | align-items: center; 172 | justify-content: center; 173 | } 174 | 175 | div.TopLeftPanel>div.rank>div:nth-child(odd) { 176 | width: 40%; 177 | } 178 | 179 | div.TopLeftPanel>div.rank>div::after { 180 | content: "伤害评级"; 181 | color: var(--ambrLight); 182 | font-family: "PanelFont"; 183 | font-size: 18px; 184 | font-weight: 400; 185 | } 186 | 187 | div.TopLeftPanel>div.rank>div.dps::after { 188 | content: "DPS"; 189 | font-style: italic; 190 | } 191 | 192 | div.TopLeftPanel>div.rank>div.time::after { 193 | content: "输出轴长"; 194 | } 195 | 196 | div.TopLeftPanel>div.rank>div.total::after { 197 | content: "输出总伤"; 198 | } 199 | 200 | div.TopLeftPanel>div#pie { 201 | /* background-color: #2e353e; */ 202 | border-radius: 15px; 203 | position: relative; 204 | top: 51px; 205 | left: 22px; 206 | width: 280px; 207 | height: 280px; 208 | display: flex; 209 | justify-content: center; 210 | align-items: center; 211 | } 212 | 213 | div.TopLeftPanel>div#pie::after { 214 | position: absolute; 215 | bottom: 10px; 216 | content: "输出占比"; 217 | font-size: 20px; 218 | color: var(--ambrLight); 219 | } 220 | 221 | div.Avatars { 222 | position: absolute; 223 | top: 20px; 224 | left: 365px; 225 | /* position: relative; 226 | top: -523px; 227 | left: 365px; */ 228 | width: 575px; 229 | height: auto; 230 | min-height: 264px; 231 | display: flex; 232 | flex-direction: row; 233 | flex-wrap: wrap; 234 | align-content: center; 235 | justify-content: flex-start; 236 | align-items: center; 237 | gap: 15px; 238 | } 239 | 240 | div.Avatars>div.avatar { 241 | color: #ffffff; 242 | background-color: var(--artiBg); 243 | align-items: center; 244 | display: flex; 245 | flex-direction: column; 246 | width: 280px; 247 | height: 264px; 248 | border-radius: 15px; 249 | border: none; 250 | } 251 | 252 | div.Avatars>div.avatar>img.char { 253 | width: 64px; 254 | height: 64px; 255 | border-top-left-radius: 15px; 256 | border-bottom: 4px; 257 | position: relative; 258 | left: -108px; 259 | top: 0px; 260 | } 261 | 262 | div.Avatars>div.avatar>div.char-con { 263 | height: 32px; 264 | min-height: 32px; 265 | width: 44px; 266 | background: var(--ambrDeep); 267 | color: var(--ambrLight); 268 | text-shadow: 0 0 3px var(--ambrDeep); 269 | font-size: 22px; 270 | position: relative; 271 | top: -64px; 272 | left: -54px; 273 | } 274 | 275 | div.Avatars>div.avatar>div.char-con::after { 276 | content: "0"; 277 | } 278 | 279 | div.Avatars>div.avatar>div.char-con.c1 { 280 | background: var(--charCons1); 281 | } 282 | 283 | div.Avatars>div.avatar>div.char-con.c1::after { 284 | content: "1"; 285 | } 286 | 287 | div.Avatars>div.avatar>div.char-con.c2 { 288 | background: var(--charCons2); 289 | } 290 | 291 | div.Avatars>div.avatar>div.char-con.c2::after { 292 | content: "2"; 293 | } 294 | 295 | div.Avatars>div.avatar>div.char-con.c3 { 296 | background: var(--charCons3); 297 | } 298 | 299 | div.Avatars>div.avatar>div.char-con.c3::after { 300 | content: "3"; 301 | } 302 | 303 | div.Avatars>div.avatar>div.char-con.c4 { 304 | background: var(--charCons4); 305 | } 306 | 307 | div.Avatars>div.avatar>div.char-con.c4::after { 308 | content: "4"; 309 | } 310 | 311 | div.Avatars>div.avatar>div.char-con.c5 { 312 | background: var(--charCons5); 313 | } 314 | 315 | div.Avatars>div.avatar>div.char-con.c5::after { 316 | content: "5"; 317 | } 318 | 319 | div.Avatars>div.avatar>div.char-con.c6 { 320 | background: var(--charCons6); 321 | } 322 | 323 | div.Avatars>div.avatar>div.char-con.c6::after { 324 | content: "6"; 325 | } 326 | 327 | div.Avatars>div.avatar>div.char-lvl { 328 | height: 32px; 329 | min-height: 32px; 330 | width: 44px; 331 | background: var(--ambrDeep); 332 | color: var(--ambrLight); 333 | font-size: 22px; 334 | position: relative; 335 | top: -64px; 336 | left: -54px; 337 | } 338 | 339 | div.Avatars>div.avatar>img.weapon { 340 | width: 64px; 341 | height: 64px; 342 | position: relative; 343 | top: -128px; 344 | left: 0; 345 | } 346 | 347 | div.Avatars>div.avatar>div.weapon-aff { 348 | height: 32px; 349 | min-height: 32px; 350 | width: 44px; 351 | background: var(--ambrDeep); 352 | color: var(--ambrLight); 353 | text-shadow: 0 0 3px var(--ambrDeep); 354 | font-size: 22px; 355 | position: relative; 356 | top: -192px; 357 | left: 54px; 358 | } 359 | 360 | div.Avatars>div.avatar>div.weapon-aff::after { 361 | content: "1"; 362 | } 363 | 364 | div.Avatars>div.avatar>div.weapon-aff.a2 { 365 | background: var(--weaponAffix2); 366 | } 367 | 368 | div.Avatars>div.avatar>div.weapon-aff.a2::after { 369 | content: "2"; 370 | } 371 | 372 | div.Avatars>div.avatar>div.weapon-aff.a3 { 373 | background: var(--weaponAffix3); 374 | } 375 | 376 | div.Avatars>div.avatar>div.weapon-aff.a3::after { 377 | content: "3"; 378 | } 379 | 380 | div.Avatars>div.avatar>div.weapon-aff.a4 { 381 | background: var(--weaponAffix4); 382 | } 383 | 384 | div.Avatars>div.avatar>div.weapon-aff.a4::after { 385 | content: "4"; 386 | } 387 | 388 | div.Avatars>div.avatar>div.weapon-aff.a5 { 389 | background: var(--weaponAffix5); 390 | } 391 | 392 | div.Avatars>div.avatar>div.weapon-aff.a5::after { 393 | content: "5"; 394 | } 395 | 396 | div.Avatars>div.avatar>div.weapon-lvl { 397 | height: 32px; 398 | min-height: 32px; 399 | width: 44px; 400 | background: var(--ambrDeep); 401 | color: var(--ambrLight); 402 | font-size: 22px; 403 | position: relative; 404 | top: -192px; 405 | left: 54px; 406 | } 407 | 408 | div.Avatars>div.avatar>div.weapon-aff, 409 | div.Avatars>div.avatar>div.weapon-lvl, 410 | div.Avatars>div.avatar>div.char-con, 411 | div.Avatars>div.avatar>div.char-lvl { 412 | font-family: "PanelNumFont"; 413 | font-size: 16px; 414 | display: flex; 415 | align-items: center; 416 | justify-content: center; 417 | } 418 | 419 | div.Avatars>div.avatar>div.char-lvl::before, 420 | div.Avatars>div.avatar>div.weapon-lvl::before { 421 | content: "Lv"; 422 | } 423 | 424 | div.Avatars>div.avatar>div.char-con::before { 425 | content: "C"; 426 | } 427 | 428 | div.Avatars>div.avatar>div.weapon-aff::before { 429 | content: "R"; 430 | } 431 | 432 | div.Avatars>div.avatar>div.arti { 433 | width: 64px; 434 | height: 64px; 435 | min-height: 64px; 436 | background: var(--itemRarity5); 437 | /* rgb(189, 165, 117) */ 438 | border-top-right-radius: 15px; 439 | position: relative; 440 | top: -256px; 441 | left: 108px; 442 | } 443 | 444 | div.Avatars>div.avatar>div.arti:not(:has(img)) { 445 | background: url('data:image/webp;base64,UklGRpoFAABXRUJQVlA4TI0FAAAvHcAJEA0ojCQZqhk/K/+ACSKi/xMAq0x5TQEKUCXFpOo6xoaccQcshJUCYP9Ur3wLMFDQto3k+48/5a0clLaNJAcNFy68/yb6PwHwER3ViXQYRQNoRIpQtxAlsAGgbWt7Iil7Y2XtzkyPD+7u7nDMMdynu/4uuEPTSOtYeRK8a+4AYttGghxqLv03HPxaYttIkiQxMnv9N/feypAr27ZpW23vfXBt27Zt3xve8PEnbdsKbdvvnQkAAfDLle+rPbIu0LjOscbvPQISd9evPX5PTu1Z9qapxLexxAyXrRPqIl0conmSwnpp0WQnlP0UrParLa3YiAUfqTE1BWte26prQTLT3zd1RhdKUEqDRDCqokJ1a8Jk+LfJDUw9Wf7GgoDDFa/O38ZcFCQS2fQJ/7FBeWl7PR8DZazoi/mL7nw09Se/jbYQJnD8Ev94avj0bnxap1+L8A/9Ea/SqD9FU0y0CTbSeJr4QH+QvT7zU9TXSLeeSA4WfEgohJCI+LeZhip9d8keIOa/8RpbOy05ojg8yIUFG4APCQaBHFGOZJ5EnSaD9F9BAkeizAQkoQshTPhYUAqGMmFDRjACpVAmXqJ2kimHzJ9JTfpfIvaX0FQ9AZEzLCEfLc87AUygVIVHBGLEBmswHGa0QtVPkET/vY8Q+oJ5KDIN2vCFDb7VavlRfuE8MPQFe2ChDL5nucY2MZai5EcjtDbdTyFES3lsh0EJNqIXc9/SDt9DDrthqHjIEUcYKUeKzdSvZuRHE1RpsMD38QshADR4ozP6VXnB0Aq2KCIT/JaOGFz4VlaMU1Gm6jvxySou8YKwhy5coQ3tQxtzrc8Qqxp043MEqK3y90Cp9x8HKQgohsgtUgQAgJrf3sLT9BjUIC0GJYDYaBgRhsOBEnM+zY2WcJWCBim0EnCcj/rzohwHAKB/84pJLEU8CnqYEAZrLYhHeDBGkBfEICCiK/7H4wHHhgUNCLuSO+fnHogAKglzKbQ32cYGBf/RWLSMO+OO+Hc0j0qIC+Vg8Cvryo9n+K/4D28wUxSRVdZ4xe817Yxj0rbPysRSbS8x+Sou2mLBmG18bjRiHobQR/ROXHAHER5zqikaRb/SF7LIDGfkIcRuRifLnD6pCmUlcuqTRSUKi6EiNoqjVNgICz8giKIKKtjRqopRjOxkCydmHz4yMctfsqZKfPydg86kjTO2i5FxSRSFCSEqUYtMJVcdBI698oV2cpherEc9LLjD8vRCPeZx59x/yJkhnsjdX2DYNu3JmlOECCCGKMIiu6fqCVZdJVU4Ulgeq1UZFAiJSMlRwBM+X83B/93PtwoAgB19WwEcT7hQWChk2MrcySvE+5WhptWqyAeUBgAIqADu8uJ39gHfj/bT0dSvxqCQIPALQ9h7AvUO/8AdGtgjQQAAACuMsXbKS3QBPzT6Mv9zDzoFCuJweIIoC1x1dv5uzYNCQIM4AAD4IiVWTUPJ1P8IeMWFBzy0VxZCRQUZ0jHUrPF/CAAmKAACoiDEqKW4Qjnw0ze7Z/e40QDFAhxoRApoG59B4Bu8wRoQALFM8TapVLL4Jv1nAJfMZTK42XUvp6igQSSAcLiwbhmNLLINMvRKCPekHsnb1793RxMAv8qJTVI/Tcc4dSXJ5YSfWoFRWdXRBwRFoHr8+qwPWXlkdBEw6LjUbzPrJS1pD42akTYeGK5w1Ef99ECvOfzYTTCLgYC9dnuNduWqjFrAHN6Co6j+yB259wx4sAIGH2+nV6VztMjBg0IVHI9KqqUPgEQhQbrE3sOEA44UXoq/9RSVx8kAaJK350xX/lHYeMe/Pzgv954BST+Yc/e4QgK4Llf6uwdA8vW26ZzskKh3FcAiDpuqXayyWJKqxRg1+RPMAiPJX4xNJo8wpbHR/7wBAAA=') center center no-repeat; 446 | background-size: 45% 60%; 447 | background-color: #818486; 448 | } 449 | 450 | div.Avatars>div.avatar>div.arti:has(img.s4)::after { 451 | content: "4"; 452 | font-family: "PanelNumFont"; 453 | position: relative; 454 | top: -28px; 455 | left: 47px; 456 | text-shadow: 0 0 3px var(--ambrLight); 457 | } 458 | 459 | div.Avatars>div.avatar>div.arti>img.s2 { 460 | width: 64px; 461 | height: 64px; 462 | position: absolute; 463 | transform: scale(.7); 464 | transform-origin: left top; 465 | } 466 | 467 | div.Avatars>div.avatar>div.arti>img.s2:first-child:last-child { 468 | transform: unset; 469 | } 470 | 471 | div.Avatars>div.avatar>div.arti>img.s2:last-child { 472 | transform-origin: right bottom; 473 | } 474 | 475 | div.Avatars>div.avatar>img.r5 { 476 | background-color: var(--itemRarity5); 477 | } 478 | 479 | /* div.Avatars>div.avatar>img.icon.r5 { 480 | background-color: var(--charRarity5); 481 | } */ 482 | div.Avatars>div.avatar>img.r4 { 483 | background-color: var(--itemRarity4); 484 | } 485 | 486 | /* div.Avatars>div.avatar>img.icon.r4 { 487 | background-color: var(--charRarity4); 488 | } */ 489 | div.Avatars>div.avatar>img.r3 { 490 | background-color: var(--itemRarity3); 491 | } 492 | 493 | div.Avatars>div.avatar>img.r2 { 494 | background-color: var(--itemRarity2); 495 | } 496 | 497 | div.Avatars>div.avatar>img.r1 { 498 | background-color: var(--itemRarity1); 499 | } 500 | 501 | div.Avatars>div.avatar>div.detail { 502 | width: 250px; 503 | min-height: 200px; 504 | font-size: 20px; 505 | /* background: aliceblue; */ 506 | position: relative; 507 | top: -256px; 508 | display: flex; 509 | flex-direction: row; 510 | flex-wrap: wrap; 511 | align-content: flex-end; 512 | justify-content: space-around; 513 | align-items: center; 514 | } 515 | 516 | div.Avatars>div.avatar>div.detail>div.cp, 517 | div.Avatars>div.avatar>div.detail>div.cd, 518 | div.Avatars>div.avatar>div.detail>div.prop, 519 | div.Avatars>div.avatar>div.detail>div.recharge, 520 | div.Avatars>div.avatar>div.detail>div.same, 521 | div.Avatars>div.avatar>div.detail>div.diff { 522 | /* background-color: aliceblue; */ 523 | width: 33.33%; 524 | height: 50px; 525 | font-family: "PanelNumFont"; 526 | position: relative; 527 | display: flex; 528 | flex-direction: column; 529 | justify-content: center; 530 | align-items: center; 531 | } 532 | 533 | div.Avatars>div.avatar>div.detail>div.cp::after, 534 | div.Avatars>div.avatar>div.detail>div.cd::after, 535 | div.Avatars>div.avatar>div.detail>div.prop>div.name, 536 | div.Avatars>div.avatar>div.detail>div.recharge::after, 537 | div.Avatars>div.avatar>div.detail>div.same::after, 538 | div.Avatars>div.avatar>div.detail>div.diff::after { 539 | font-family: "PanelFont"; 540 | font-size: 15px; 541 | color: var(--ambrLight); 542 | } 543 | 544 | div.Avatars>div.avatar>div.detail>div.cp::after { 545 | content: "暴击"; 546 | } 547 | 548 | div.Avatars>div.avatar>div.detail>div.cd::after { 549 | content: "暴伤"; 550 | } 551 | 552 | div.Avatars>div.avatar>div.detail>div.recharge::after { 553 | content: "循环"; 554 | } 555 | 556 | div.Avatars>div.avatar>div.detail>div.same::after { 557 | content: "同色球"; 558 | } 559 | 560 | div.Avatars>div.avatar>div.detail>div.diff::after { 561 | content: "异色球"; 562 | } 563 | 564 | div.Avatars>div.avatar>div.detail>div.talent { 565 | width: 100%; 566 | min-height: 80px; 567 | display: flex; 568 | justify-content: center; 569 | align-items: center; 570 | } 571 | 572 | div.Avatars>div.avatar>div.detail>div.talent>div { 573 | background-position: 50% 50%; 574 | background-size: cover; 575 | width: 61px; 576 | height: 66px; 577 | margin-right: 32px; 578 | display: flex; 579 | align-items: center; 580 | justify-content: flex-start; 581 | flex-direction: row; 582 | } 583 | 584 | div.Avatars>div.avatar>div.detail>div.talent>div>img { 585 | width: 32px; 586 | height: 32px; 587 | position: relative; 588 | top: 1px; 589 | left: 15px; 590 | } 591 | 592 | div.Avatars>div.avatar>div.detail>div.talent>div>span { 593 | margin-left: 25px; 594 | width: 30px; 595 | min-width: 30px; 596 | height: 30px; 597 | color: #333333; 598 | background: #ffffff; 599 | border-radius: 5px; 600 | font-family: "PanelNumFont"; 601 | display: flex; 602 | align-items: center; 603 | justify-content: center; 604 | } 605 | 606 | div.Avatars>div.avatar>div.detail>div.talent>div>span.extra { 607 | color: #000000; 608 | background-color: #45deff; 609 | } 610 | 611 | div.Actions, 612 | ul.Damages, 613 | ul.Buffs { 614 | height: auto; 615 | min-height: 90px; 616 | width: 920px; 617 | border-radius: 15px; 618 | color: #ffffff; 619 | background-color: var(--tableBg); 620 | box-shadow: 0px 15px 15px rgb(0 0 0 / 40%); 621 | position: relative; 622 | top: 50px; 623 | left: 20px; 624 | display: flex; 625 | flex-direction: column; 626 | justify-content: space-evenly; 627 | list-style-type: none; 628 | margin-block-start: unset; 629 | margin-block-end: 20px; 630 | margin-inline-start: unset; 631 | margin-inline-end: unset; 632 | padding-inline-start: unset; 633 | } 634 | 635 | div.Actions>div.title, 636 | ul.Damages>li.title, 637 | ul.Buffs>li.title { 638 | height: 50px; 639 | color: #ffffff; 640 | font-size: 25px; 641 | font-weight: 700; 642 | padding-left: 15px; 643 | display: flex; 644 | align-items: center; 645 | } 646 | 647 | div.Actions>div.title::after, 648 | ul.Damages>li.title::after, 649 | ul.Buffs>li.title::after { 650 | font-size: 18px; 651 | font-weight: 400; 652 | opacity: 0.68; 653 | color: var(--ambrLight); 654 | padding-left: 20px; 655 | } 656 | 657 | div.Actions>div.title::before { 658 | content: "操作手法"; 659 | } 660 | 661 | div.Actions>div.title::after { 662 | content: "默认手法可能并非最优,如需自定义操作手法请使用微信小程序"; 663 | } 664 | 665 | div.Actions>div.act { 666 | max-width: 98%; 667 | padding: 0 1% 1%; 668 | display: flex; 669 | flex-direction: row; 670 | flex-wrap: wrap; 671 | justify-content: flex-start; 672 | align-items: center; 673 | } 674 | 675 | div.Actions>div.act>div { 676 | min-width: 30px; 677 | height: 30px; 678 | margin: 0.4em; 679 | padding: 0 0.3em; 680 | color: var(--ambrDeep); 681 | background-color: var(--ambrLight); 682 | font-size: 20px; 683 | border-radius: 5px; 684 | display: flex; 685 | align-items: center; 686 | justify-content: center; 687 | position: relative; 688 | } 689 | 690 | ul.Damages>li, 691 | ul.Buffs>li { 692 | color: var(--ambrLight); 693 | height: auto; 694 | min-height: 40px; 695 | padding-left: 25px; 696 | font-size: 20px; 697 | font-weight: 400; 698 | display: flex; 699 | flex-direction: row; 700 | align-items: stretch; 701 | border-bottom: 2px solid rgba(255 255 255 / 20%); 702 | } 703 | 704 | ul.Damages>li:last-child, 705 | ul.Buffs>li:last-child { 706 | border-bottom: none; 707 | } 708 | 709 | ul.Damages>li>div, 710 | ul.Buffs>li>div { 711 | height: auto; 712 | min-height: 25px; 713 | padding: 0.4em; 714 | display: flex; 715 | flex: 1; 716 | align-items: center; 717 | justify-content: center; 718 | } 719 | 720 | ul.Damages>li:nth-child(2), 721 | ul.Damages>li>div:nth-child(1), 722 | ul.Damages>li>div:nth-child(2), 723 | ul.Buffs>li>div:nth-child(1), 724 | ul.Buffs>li>div:nth-child(2) { 725 | color: #ffffff; 726 | } 727 | 728 | ul.Damages>li.title::before { 729 | content: "伤害过程"; 730 | } 731 | 732 | ul.Damages>li.title::after { 733 | content: "伤害以 90 级怪物为基准,最终输出总伤和 DPS 为 1.25 倍期望伤害(实战对群)"; 734 | } 735 | 736 | ul.Damages>li>div:not(:last-child), 737 | ul.Buffs>li>div:not(:last-child) { 738 | border-right: 2px solid rgba(255 255 255 / 20%); 739 | } 740 | 741 | ul.Damages>li:not(.title)>div:first-child, 742 | ul.Buffs>li:not(.title)>div:first-child { 743 | max-width: 6%; 744 | justify-content: flex-end; 745 | } 746 | 747 | ul.Damages>li.head>div:first-child::after { 748 | content: "时间"; 749 | } 750 | 751 | ul.Damages>li:not(.title):not(.head)>div:first-child::after, 752 | ul.Buffs>li:not(.title)>div:first-child::after { 753 | content: "s"; 754 | } 755 | 756 | ul.Damages>li:not(.title)>div:nth-child(2), 757 | ul.Buffs>li:not(.title)>div:nth-child(2) { 758 | width: auto; 759 | min-width: 35%; 760 | max-width: 40%; 761 | justify-content: flex-end; 762 | text-align: right; 763 | justify-content: flex-end; 764 | padding-right: 20px; 765 | } 766 | 767 | ul.Damages>li.head>div:nth-child(2)::after { 768 | content: "动作"; 769 | } 770 | 771 | ul.Damages>li.head>div:nth-child(3)::after { 772 | content: "暴击伤害"; 773 | } 774 | 775 | ul.Damages>li.head>div:nth-child(4)::after { 776 | content: "未暴击伤害"; 777 | } 778 | 779 | ul.Damages>li.head>div:nth-child(5)::after { 780 | content: "期望伤害"; 781 | } 782 | 783 | ul.Buffs>li.title::before { 784 | content: "Buff 情况"; 785 | } 786 | 787 | ul.Buffs>li.title::after { 788 | content: "部分 Buff 描述可能异常但通常影响较小,若伤害偏差超过 10% 可认为计算错误"; 789 | } 790 | 791 | ul.Buffs>li:not(.title)>div:nth-child(2) { 792 | min-width: 25%; 793 | max-width: 30%; 794 | } 795 | 796 | div.copyright { 797 | color: #ffffff; 798 | opacity: 0.68; 799 | text-shadow: 3px 5px 4px #333333ad; 800 | font-size: 20px; 801 | width: 960px; 802 | text-align: center; 803 | padding-top: 60px; 804 | padding-bottom: 20px; 805 | } 806 | 807 | div.copyright::before { 808 | content: "Data from Teyvat × Powered by NoneBot2 × Inspired by Miao-Plugin"; 809 | } -------------------------------------------------------------------------------- /data/gspanel/team-0.2.20.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 |
14 |
15 |
{{ data["uid"] }}
16 |
17 |
{{ data["rank"] }}
18 |
{{ data["dps"] }}
19 |
{{ data["tm"] }}
20 |
{{ data["total"] }}
21 |
22 |
23 |
24 |
25 | {% for _, a in data["avatars"].items() %} 26 |
27 | 28 |
29 |
{{ a["level"] }}
30 | 31 |
32 |
{{ a['weapon']['level'] }}
33 |
34 | {% for sId, sCnt in a["sets"].items() %} 35 | 36 | {% endfor %} 37 |
38 |
39 |
{{ a["cp"] }}%
40 |
{{ a["cd"] }}%
41 |
42 |
{{ a["key_value"] }}
43 |
{{ a["key_prop"] }}
44 |
45 |
{{ a["recharge"]["pct"] }}
46 |
{{ a["recharge"]["same"] }}
47 |
{{ a["recharge"]["diff"] }}
48 |
49 | {% for skill in a["skills"] %} 50 |
51 | 52 | {{ skill["level"] }} 53 |
54 | {% endfor %} 55 |
56 |
57 |
58 | {% endfor %} 59 |
60 |
61 |
62 |
63 | {% for action in data["actions"] %} 64 |
{{ action }}
65 | {% endfor %} 66 |
67 |
68 | 78 | {% if detail %} 79 | 99 | {% endif %} 100 | 101 |
102 | 103 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /data/gspanel/team-alias.json: -------------------------------------------------------------------------------- 1 | { 2 | "国家队": { 3 | "chars": ["行秋", "香菱", "班尼特", "重云"] 4 | }, 5 | "万达国际": { 6 | "alias": ["万达"], 7 | "chars": ["达达利亚", "枫原万叶", "香菱", "班尼特"] 8 | }, 9 | "雷神国家队": { 10 | "alias": ["雷国"], 11 | "chars": ["雷电将军", "行秋", "香菱", "班尼特"] 12 | }, 13 | "雷夜国家队": { 14 | "alias": ["雷夜", "雷神夜兰国家队"], 15 | "chars": ["雷电将军", "夜兰", "香菱", "班尼特"] 16 | }, 17 | "魔王武装": { 18 | "alias": ["魔王武装队"], 19 | "chars": ["达达利亚", "行秋", "北斗", "菲谢尔"] 20 | }, 21 | "砂糖武装": { 22 | "alias": ["砂糖武装队"], 23 | "chars": ["砂糖", "行秋", "北斗", "菲谢尔"] 24 | }, 25 | "心海武装": { 26 | "alias": ["心海武装队"], 27 | "chars": ["珊瑚宫心海", "行秋", "北斗", "枫原万叶"] 28 | }, 29 | "宵宫武装": { 30 | "alias": ["烟花武装", "宵宫武装队", "烟花武装队"], 31 | "chars": ["宵宫", "行秋", "北斗", "班尼特"] 32 | }, 33 | "莫甘娜": { 34 | "chars": ["甘雨", "莫娜", "迪奥娜"] 35 | }, 36 | "莫甘娜温": { 37 | "alias": ["甘雨永冻队", "甘雨永冻", "甘雨永动队", "甘雨永动"], 38 | "chars": ["甘雨", "莫娜", "温迪", "迪奥娜"] 39 | }, 40 | "神里永冻": { 41 | "alias": ["神里永动", "神里永冻队", "神里永动队"], 42 | "chars": ["神里绫华", "罗莎莉亚", "枫原万叶", "珊瑚宫心海"] 43 | }, 44 | "神鹤万心": { 45 | "alias": ["申鹤万心", "神里永冻队", "神里永冻", "神里永动队", "神里永动"], 46 | "chars": ["神里绫华", "申鹤", "枫原万叶", "珊瑚宫心海"] 47 | }, 48 | "融化神里": { 49 | "alias": ["绫华增伤", "增伤绫华", "绫华融化队", "融化绫华队", "绫华增伤队", "增伤绫华队"], 50 | "chars": ["神里绫华", "砂糖", "香菱", "班尼特"] 51 | }, 52 | "融甘": { 53 | "alias": ["钟离融甘", "融甘钟", "融甘队"], 54 | "chars": ["甘雨", "香菱", "班尼特", "钟离"] 55 | }, 56 | "融卢": { 57 | "alias": ["熔炉", "熔炉队", "融化迪卢克", "融卢队"], 58 | "chars": ["迪卢克", "凯亚", "枫原万叶", "班尼特"] 59 | }, 60 | "胡行钟": { 61 | "chars": ["胡桃", "行秋", "钟离"] 62 | }, 63 | "胡行钟夜": { 64 | "chars": ["胡桃", "行秋", "钟离", "夜兰"] 65 | }, 66 | "胡行钟阿": { 67 | "alias": ["胡行钟啊"], 68 | "chars": ["胡桃", "行秋", "钟离", "阿贝多"] 69 | }, 70 | "胡行钟莫": { 71 | "alias": ["胡行钟娜"], 72 | "chars": ["胡桃", "行秋", "钟离", "莫娜"] 73 | }, 74 | "宵夜钟云": { 75 | "alias": ["夜宵钟云", "宵夜云钟", "夜宵云钟", "宵夜双岩", "夜宵双岩"], 76 | "chars": ["宵宫", "夜兰", "钟离", "云堇"] 77 | }, 78 | "宵行钟云": { 79 | "alias": ["宵钟行云", "宵钟云行", "宵行云钟"], 80 | "chars": ["宵宫", "行秋", "钟离", "云堇"] 81 | }, 82 | "宵行班云": { 83 | "alias": ["宵班行云", "宵班云行", "宵行云班"], 84 | "chars": ["宵宫", "行秋", "班尼特", "云堇"] 85 | }, 86 | "蒸发可莉": { 87 | "alias": ["可莉蒸发", "莉夜班钟", "钟夜班莉", "夜班钟莉"], 88 | "chars": ["可莉", "夜兰", "钟离", "班尼特"] 89 | }, 90 | "草神三水": { 91 | "alias": ["三水草神"], 92 | "chars": ["纳西妲", "妮露", "珊瑚宫心海", "夜兰"] 93 | }, 94 | "刻晴激化": { 95 | "alias": ["激化刻晴", "刻晴激化队", "激化刻晴队"], 96 | "chars": ["刻晴", "菲谢尔", "柯莱", "钟离"] 97 | }, 98 | "刻菲纳万": { 99 | "alias": ["玉皇大帝", "玉皇妲帝"], 100 | "chars": ["刻晴", "菲谢尔", "纳西妲", "枫原万叶"] 101 | }, 102 | "优雷修": { 103 | "chars": ["优菈", "雷电将军", "罗莎莉亚"] 104 | }, 105 | "优雷修班": { 106 | "alias": ["优菈物理", "优拉物理", "尤拉物理", "尤菈物理", "优菈物理队", "优拉物理队", "尤拉物理队", "尤菈物理队"], 107 | "chars": ["优菈", "罗莎莉亚", "北斗", "班尼特"] 108 | }, 109 | "雷优物理": { 110 | "alias": ["雷神优菈", "优菈雷神", "雷神优菈队", "优菈雷神队"], 111 | "chars": ["优菈", "罗莎莉亚", "雷电将军", "班尼特"] 112 | }, 113 | "优菈双神": { 114 | "alias": ["双神优菈", "优菈雷神", "双神优菈队", "优菈双神队", "双神优拉", "优拉雷神", "双神优拉队", "优拉双神队", "双神尤菈", "尤菈雷神", "双神尤菈队", "尤菈双神队"], 115 | "chars": ["优菈", "钟离", "雷电将军", "班尼特"] 116 | }, 117 | "雷九万班": { 118 | "alias": ["雷九"], 119 | "chars": ["雷电将军", "九条裟罗", "枫原万叶", "班尼特"] 120 | }, 121 | "雷八万班": { 122 | "alias": ["雷八"], 123 | "chars": ["雷电将军", "八重神子", "枫原万叶", "班尼特"] 124 | }, 125 | "刻九万班": { 126 | "alias": ["刻九"], 127 | "chars": ["刻晴", "九条裟罗", "枫原万叶", "班尼特"] 128 | }, 129 | "宵云万班": { 130 | "alias": ["宵宫纯火", "纯火宵宫", "宵宫纯火队"], 131 | "chars": ["宵宫", "云堇", "枫原万叶", "班尼特"] 132 | }, 133 | "宵云钟班": { 134 | "alias": ["宵钟云班", "宵云班钟", "宵班云钟", "宵班钟云", "宵宫双岩", "双岩宵宫", "宵宫双岩队"], 135 | "chars": ["宵宫", "云堇", "钟离", "班尼特"] 136 | }, 137 | "可莉纯火": { 138 | "alias": ["可莉三火", "纯火可莉"], 139 | "chars": ["可莉", "香菱", "班尼特", "枫原万叶"] 140 | }, 141 | "绫云钟班": { 142 | "alias": ["绫云班钟", "绫班云钟", "绫班钟云", "绫钟云班", "绫钟班云", "绫人云堇", "绫人云堇队", "绫人双岩", "绫人双岩队"], 143 | "chars": ["神里绫人", "班尼特", "云堇", "钟离"] 144 | }, 145 | "绫万班云": { 146 | "alias": ["绫云万班", "纯水绫人", "绫人纯水", "绫云万班"], 147 | "chars": ["神里绫人", "班尼特", "枫原万叶", "云堇"] 148 | }, 149 | "绫人双岩": { 150 | "alias": ["双岩绫人"], 151 | "chars": ["神里绫人", "钟离", "班尼特", "云堇"] 152 | }, 153 | "万叶纯水": { 154 | "alias": ["夜兰纯水", "心海纯水", "夜兰万叶纯水", "万叶夜兰纯水", "心海万叶纯水", "万叶心海纯水"], 155 | "chars": ["珊瑚宫心海", "夜兰", "行秋", "枫原万叶"] 156 | }, 157 | "钟离纯水": { 158 | "alias": ["夜兰钟离纯水", "钟离夜兰纯水", "心海钟离纯水", "钟离心海纯水"], 159 | "chars": ["珊瑚宫心海", "夜兰", "行秋", "钟离"] 160 | }, 161 | "一斗纯岩": { 162 | "alias": ["纯岩一斗", "一抖纯岩", "一斗纯岩队", "一抖纯岩队"], 163 | "chars": ["荒泷一斗", "五郎", "云堇", "钟离"] 164 | }, 165 | "女仆纯岩": { 166 | "alias": ["高达", "女仆高达", "纯岩诺艾尔", "诺艾尔纯岩", "女仆纯岩队", "诺艾尔纯岩队"], 167 | "chars": ["诺艾尔", "五郎", "云堇", "钟离"] 168 | }, 169 | "魈三风": { 170 | "alias": ["魈风队", "魈三风队"], 171 | "chars": ["魈", "砂糖", "琴"] 172 | }, 173 | "魈琴钟班": { 174 | "alias": ["琴魈钟班"], 175 | "chars": ["魈", "琴", "钟离", "班尼特"] 176 | }, 177 | "魈琴钟阿": { 178 | "alias": ["琴魈钟啊", "魈琴双岩", "琴魈双岩"], 179 | "chars": ["魈", "琴", "钟离", "阿贝多"] 180 | }, 181 | "四风速切": { 182 | "alias": ["四风守护"], 183 | "chars": ["琴", "温迪", "砂糖", "枫原万叶"] 184 | } 185 | } -------------------------------------------------------------------------------- /nonebot_plugin_gspanel/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot import get_driver 2 | from nonebot.log import logger 3 | from nonebot.adapters import Message 4 | from nonebot.params import CommandArg 5 | from nonebot.plugin import on_command 6 | from nonebot.adapters.onebot.v11 import Bot 7 | from nonebot.adapters.onebot.v11.event import MessageEvent 8 | from nonebot.adapters.onebot.v11.message import MessageSegment 9 | 10 | from .data_updater import updateCache 11 | from .data_source import getTeam, getPanel 12 | from .__utils__ import GSPANEL_ALIAS, uidHelper, formatTeam, formatInput, fetchInitRes 13 | 14 | driver = get_driver() 15 | driver.on_startup(fetchInitRes) 16 | driver.on_bot_connect(updateCache) 17 | 18 | showPanel = on_command("panel", aliases=GSPANEL_ALIAS, priority=13, block=True) 19 | showTeam = on_command("teamdmg", aliases={"队伍伤害"}, priority=13, block=True) 20 | 21 | uidStart = ["1", "2", "5", "6", "7", "8", "9"] 22 | 23 | 24 | @showPanel.handle() 25 | async def panel_handle(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): 26 | qq = str(event.get_user_id()) 27 | argsMsg = " ".join(seg.data["text"] for seg in arg["text"]) 28 | # 提取消息中的 at 作为操作目标 QQ 29 | opqq = event.message["at"][0].data["qq"] if event.message.get("at") else "" 30 | # 输入以「绑定」开头,识别为绑定操作 31 | if argsMsg.startswith("绑定"): 32 | args = [a for a in argsMsg[2:].split() if a.isdigit()] 33 | if len(args) == 1: 34 | uid, opqq = args[0], opqq or qq 35 | elif len(args) == 2: 36 | uid, opqq = args[0], (opqq or args[1]) 37 | else: 38 | await showPanel.finish("绑定参数格式错误!", at_sender=True) 39 | if opqq != qq and qq not in bot.config.superusers: 40 | await showPanel.finish(f"没有权限操作 QQ{qq} 的绑定状态!", at_sender=True) 41 | elif uid[0] not in uidStart or len(uid) != 9: 42 | await showPanel.finish(f"UID 是「{uid}」吗?好像不对劲呢..", at_sender=True) 43 | await showPanel.finish(await uidHelper(opqq, uid)) 44 | # 尝试从输入中理解 UID、角色名 45 | uid, char = await formatInput(argsMsg, qq, opqq) 46 | if not uid: 47 | await showPanel.finish("要查询角色面板的 UID 捏?", at_sender=True) 48 | elif not uid.isdigit() or uid[0] not in uidStart or len(uid) != 9: 49 | await showPanel.finish(f"UID 是「{uid}」吗?好像不对劲呢..", at_sender=True) 50 | logger.info(f"正在查找 UID{uid} 的「{char}」角色面板..") 51 | rt = await getPanel(uid, char) 52 | if isinstance(rt, str): 53 | await showPanel.finish(MessageSegment.text(rt)) 54 | elif isinstance(rt, bytes): 55 | await showPanel.finish(MessageSegment.image(rt)) 56 | 57 | 58 | @showTeam.handle() 59 | async def team_handle(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): 60 | qq = str(event.get_user_id()) 61 | argsMsg = " ".join(seg.data["text"] for seg in arg["text"]) 62 | # 提取消息中的 at 作为操作目标 QQ 63 | opqq = event.message["at"][0].data["qq"] if event.message.get("at") else "" 64 | # 是否展示伤害过程,默认不显示 65 | showDetail, keywords = False, ["详情", "过程", "全部", "全图"] 66 | if any(argsMsg.startswith(word) for word in keywords): 67 | showDetail = True 68 | for word in keywords: 69 | argsMsg = argsMsg.lstrip(word).strip() 70 | # 尝试从输入中理解 UID、角色名 71 | uid, chars = await formatTeam(argsMsg, qq, opqq) 72 | if not uid: 73 | await showTeam.finish("要查询队伍伤害的 UID 捏?", at_sender=True) 74 | elif not uid.isdigit() or uid[0] not in uidStart or len(uid) != 9: 75 | await showTeam.finish(f"UID 是「{uid}」吗?好像不对劲呢..", at_sender=True) 76 | if not chars: 77 | logger.info(f"QQ{qq} 的输入「{argsMsg}」似乎未指定队伍角色!") 78 | logger.info(f"正在查找 UID{uid} 的「{'/'.join(chars) or '展柜前 4 角色'}」队伍伤害面板..") 79 | rt = await getTeam(uid, chars, showDetail) 80 | if isinstance(rt, str): 81 | await showTeam.finish(MessageSegment.text(rt)) 82 | elif isinstance(rt, bytes): 83 | await showTeam.finish(MessageSegment.image(rt)) 84 | -------------------------------------------------------------------------------- /nonebot_plugin_gspanel/__utils__.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | from pathlib import Path 4 | from re import IGNORECASE, sub, findall 5 | from typing import Set, List, Tuple, Union 6 | 7 | from nonebot import get_driver 8 | from nonebot.log import logger 9 | from nonebot.drivers import Driver 10 | from httpx import Client, AsyncClient 11 | 12 | from .__version__ import CHAR_TPL_VER, LIST_TPL_VER, TEAM_TPL_VER 13 | 14 | GROW_VALUE = { # 理论最高档(4档)词条成长值 15 | "暴击率": 3.89, 16 | "暴击伤害": 7.77, 17 | "元素精通": 23.31, 18 | "攻击力百分比": 5.83, 19 | "生命值百分比": 5.83, 20 | "防御力百分比": 7.29, 21 | "元素充能效率": 6.48, 22 | "元素伤害加成": 5.825, 23 | "物理伤害加成": 7.288, 24 | "治疗加成": 4.487, 25 | } 26 | SINGLE_VALUE = { # 用于计算词条数 27 | "暴击率": 3.3, 28 | "暴击伤害": 6.6, 29 | "元素精通": 19.75, 30 | "生命值百分比": 4.975, 31 | "攻击力百分比": 4.975, 32 | "防御力百分比": 6.2, 33 | "元素充能效率": 5.5, 34 | } 35 | MAIN_AFFIXS = { # 可能的主词条 36 | "3": "攻击力百分比,防御力百分比,生命值百分比,元素精通,元素充能效率".split(","), # EQUIP_SHOES 37 | "4": "攻击力百分比,防御力百分比,生命值百分比,元素精通,元素伤害加成,物理伤害加成".split(","), # EQUIP_RING 38 | "5": "攻击力百分比,防御力百分比,生命值百分比,元素精通,治疗加成,暴击率,暴击伤害".split(","), # EQUIP_DRESS 39 | } 40 | SUB_AFFIXS = "攻击力,攻击力百分比,防御力,防御力百分比,生命值,生命值百分比,元素精通,元素充能效率,暴击率,暴击伤害".split(",") 41 | RANK_MAP = [ 42 | ["D", 10], 43 | ["C", 16.5], 44 | ["B", 23.1], 45 | ["A", 29.7], 46 | ["S", 36.3], 47 | ["SS", 42.9], 48 | ["SSS", 49.5], 49 | ["ACE", 56.1], 50 | ["ACE²", 66], 51 | ] 52 | ELEM = { 53 | "Fire": "火", 54 | "Water": "水", 55 | "Wind": "风", 56 | "Electric": "雷", 57 | "Grass": "草", 58 | "Ice": "冰", 59 | "Rock": "岩", 60 | } 61 | POS = { 62 | "EQUIP_BRACER": "生之花", 63 | "EQUIP_NECKLACE": "死之羽", 64 | "EQUIP_SHOES": "时之沙", 65 | "EQUIP_RING": "空之杯", 66 | "EQUIP_DRESS": "理之冠", 67 | } 68 | SKILL = {"1": "a", "2": "e", "9": "q"} 69 | DMG = { 70 | "40": "火", 71 | "41": "雷", 72 | "42": "水", 73 | "43": "草", 74 | "44": "风", 75 | "45": "岩", 76 | "46": "冰", 77 | } 78 | PROP = { 79 | "FIGHT_PROP_BASE_ATTACK": "基础攻击力", 80 | "FIGHT_PROP_HP": "生命值", 81 | "FIGHT_PROP_ATTACK": "攻击力", 82 | "FIGHT_PROP_DEFENSE": "防御力", 83 | "FIGHT_PROP_HP_PERCENT": "生命值百分比", 84 | "FIGHT_PROP_ATTACK_PERCENT": "攻击力百分比", 85 | "FIGHT_PROP_DEFENSE_PERCENT": "防御力百分比", 86 | "FIGHT_PROP_CRITICAL": "暴击率", 87 | "FIGHT_PROP_CRITICAL_HURT": "暴击伤害", 88 | "FIGHT_PROP_CHARGE_EFFICIENCY": "元素充能效率", 89 | "FIGHT_PROP_HEAL_ADD": "治疗加成", 90 | "FIGHT_PROP_ELEMENT_MASTERY": "元素精通", 91 | "FIGHT_PROP_PHYSICAL_ADD_HURT": "物理伤害加成", 92 | "FIGHT_PROP_FIRE_ADD_HURT": "火元素伤害加成", 93 | "FIGHT_PROP_ELEC_ADD_HURT": "雷元素伤害加成", 94 | "FIGHT_PROP_WATER_ADD_HURT": "水元素伤害加成", 95 | "FIGHT_PROP_GRASS_ADD_HURT": "草元素伤害加成", 96 | "FIGHT_PROP_WIND_ADD_HURT": "风元素伤害加成", 97 | "FIGHT_PROP_ICE_ADD_HURT": "冰元素伤害加成", 98 | "FIGHT_PROP_ROCK_ADD_HURT": "岩元素伤害加成", 99 | } 100 | 101 | driver: Driver = get_driver() 102 | 103 | GSPANEL_ALIAS: Set[Union[str, Tuple[str, ...]]] = ( 104 | set(driver.config.gspanel_alias) 105 | if hasattr(driver.config, "gspanel_alias") 106 | else {"面板"} 107 | ) 108 | LOCAL_DIR = ( 109 | (Path(driver.config.resources_dir) / "gspanel") 110 | if hasattr(driver.config, "resources_dir") 111 | else (Path() / "data" / "gspanel") 112 | ) 113 | SCALE_FACTOR = ( 114 | float(driver.config.gspanel_scale) 115 | if hasattr(driver.config, "gspanel_scale") 116 | else 1.5 117 | ) 118 | DOWNLOAD_MIRROR = ( 119 | str(driver.config.resources_mirror) 120 | if hasattr(driver.config, "resources_mirror") 121 | else "https://enka.network/ui/" 122 | ) 123 | if not LOCAL_DIR.exists(): 124 | LOCAL_DIR.mkdir(parents=True, exist_ok=True) 125 | if not (LOCAL_DIR / "cache").exists(): 126 | (LOCAL_DIR / "cache").mkdir(parents=True, exist_ok=True) 127 | if not (LOCAL_DIR / "qq-uid.json").exists(): 128 | (LOCAL_DIR / "qq-uid.json").write_text("{}", encoding="UTF-8") 129 | _client = Client(verify=False) 130 | CALC_RULES = _client.get("https://cdn.monsterx.cn/bot/gspanel/calc-rule.json").json() 131 | (LOCAL_DIR / "calc-rule.json").write_text( 132 | json.dumps(CALC_RULES, ensure_ascii=False, indent=2), encoding="utf-8" 133 | ) 134 | CHAR_DATA = _client.get("https://cdn.monsterx.cn/bot/gspanel/char-data.json").json() 135 | (LOCAL_DIR / "char-data.json").write_text( 136 | json.dumps(CHAR_DATA, ensure_ascii=False, indent=2), encoding="utf-8" 137 | ) 138 | CHAR_ALIAS = _client.get("https://cdn.monsterx.cn/bot/gspanel/char-alias.json").json() 139 | (LOCAL_DIR / "char-alias.json").write_text( 140 | json.dumps(CHAR_ALIAS, ensure_ascii=False, indent=2), encoding="utf-8" 141 | ) 142 | TEAM_ALIAS = _client.get("https://cdn.monsterx.cn/bot/gspanel/team-alias.json").json() 143 | (LOCAL_DIR / "team-alias.json").write_text( 144 | json.dumps(TEAM_ALIAS, ensure_ascii=False, indent=2), encoding="utf-8" 145 | ) 146 | HASH_TRANS = _client.get("https://cdn.monsterx.cn/bot/gspanel/hash-trans.json").json() 147 | (LOCAL_DIR / "hash-trans.json").write_text( 148 | json.dumps(HASH_TRANS, ensure_ascii=False, indent=2), encoding="utf-8" 149 | ) 150 | RELIC_APPEND = _client.get( 151 | "https://cdn.monsterx.cn/bot/gspanel/relic-append.json" 152 | ).json() 153 | (LOCAL_DIR / "relic-append.json").write_text( 154 | json.dumps(RELIC_APPEND, ensure_ascii=False, indent=2), encoding="utf-8" 155 | ) 156 | 157 | 158 | def kStr(prop: str, reverse: bool = False) -> str: 159 | """转换词条名称为简短形式""" 160 | if reverse: 161 | return prop.replace("充能", "元素充能").replace("伤加成", "元素伤害加成").replace("物理元素", "物理") 162 | return ( 163 | prop.replace("百分比", "") 164 | .replace("元素充能", "充能") 165 | .replace("元素伤害", "伤") 166 | .replace("物理伤害", "物伤") 167 | ) 168 | 169 | 170 | def vStr(prop: str, value: Union[int, float]) -> str: 171 | """转换词条数值为字符串形式""" 172 | if prop in ["生命值", "攻击力", "防御力", "元素精通"]: 173 | return str(value) 174 | else: 175 | return str(round(value, 1)) + "%" 176 | 177 | 178 | def getServer(uid: str, teyvat: bool = False) -> str: 179 | """获取指定 UID 所属服务器,返回如 ``cn_gf01``""" 180 | if uid[0] == "5": 181 | return "cn_qd01" 182 | elif uid[0] == "6": 183 | return "us" if teyvat else "os_usa" 184 | elif uid[0] == "7": 185 | return "eur" if teyvat else "os_euro" 186 | elif uid[0] == "8": 187 | return "asia" if teyvat else "os_asia" 188 | elif uid[0] == "9": 189 | return "hk" if teyvat else "os_cht" 190 | return "cn_gf01" 191 | 192 | 193 | async def formatInput(msg: str, qq: str, atqq: str = "") -> Tuple[str, str]: 194 | """ 195 | 输入消息中的 UID 与角色名格式化,应具备处理 ``msg`` 为空、包含中文或数字的能力。 196 | - 首个中文字符串捕获为角色名,若不包含则返回 ``all`` 请求角色面板列表数据 197 | - 首个数字字符串捕获为 UID,若不包含则返回 ``uidHelper()`` 根据绑定配置查找的 UID 198 | 199 | * ``param msg: str`` 输入消息,由 ``state["_prefix"]["command_arg"]`` 或 ``event.get_plaintext()`` 生成,可能包含 CQ 码 200 | * ``param qq: str`` 输入消息触发 QQ 201 | * ``param atqq: str = ""`` 输入消息中首个 at 的 QQ 202 | - ``return: Tuple[str, str]`` UID、角色名 203 | """ # noqa: E501 204 | uid, char, tmp = "", "", "" 205 | group = findall( 206 | r"[0-9]+|[\u4e00-\u9fa5]+|[a-z]+", sub(r"\[CQ:.*\]", "", msg), flags=IGNORECASE 207 | ) 208 | for s in group: 209 | if s.isdigit(): 210 | if len(s) == 9: 211 | if not uid: 212 | uid = s 213 | else: 214 | # 0人,1斗,97忍 215 | tmp = s 216 | elif s.encode().isalpha(): 217 | # dio娜,abd 218 | tmp = s.lower() 219 | elif not s.isdigit() and not char: 220 | char = tmp + s 221 | uid = uid or await uidHelper(atqq or qq) 222 | char = await aliasWho(char or tmp or "全部") 223 | return uid, char 224 | 225 | 226 | async def formatTeam(msg: str, qq: str, atqq: str = "") -> Tuple[str, List]: 227 | """ 228 | 输入消息中的 UID 与队伍角色名格式化 229 | 230 | * ``param msg: str`` 输入消息,由 ``MessageSegment.data["text"]`` 拼接组成 231 | * ``param qq: str`` 输入消息触发 QQ 232 | * ``param atqq: str = ""`` 输入消息中首个 at 的 QQ 233 | - ``return: Tuple[str, List]`` UID、队伍角色名 234 | """ 235 | uid, chars = "", [] 236 | for seg in msg.split(): 237 | _uid, char = await formatInput(seg, qq, atqq) 238 | uid = uid or _uid 239 | if char != "全部" and char not in chars: 240 | logger.info(f"从 QQ{qq} 的输入「{seg}」中识别到 UID[{uid}] CHAR[{char}]") 241 | chars.append(char) 242 | if not msg: 243 | uid, _ = await formatInput("", qq, atqq) 244 | if len(chars) == 1: 245 | searchTeam = await aliasTeam(chars[0]) 246 | chars = searchTeam if isinstance(searchTeam, List) else chars 247 | return uid, chars 248 | 249 | 250 | async def fetchInitRes() -> None: 251 | """ 252 | 插件初始化资源下载,通过阿里云 CDN 获取 HTML 模板资源文件、角色词条权重配置、角色数据、TextMap 中文翻译数据等 253 | """ 254 | logger.info("正在检查面板插件所需资源...") 255 | # 仅首次启用插件下载的文件 256 | initRes = [ 257 | "https://cdn.monsterx.cn/bot/gspanel/font/HYWH-65W.ttf", 258 | "https://cdn.monsterx.cn/bot/gspanel/font/tttgbnumber.ttf", 259 | "https://cdn.monsterx.cn/bot/gspanel/imgs/bg-anemo.jpg", 260 | "https://cdn.monsterx.cn/bot/gspanel/imgs/bg-cryo.jpg", 261 | "https://cdn.monsterx.cn/bot/gspanel/imgs/bg-dendro.jpg", 262 | "https://cdn.monsterx.cn/bot/gspanel/imgs/bg-electro.jpg", 263 | "https://cdn.monsterx.cn/bot/gspanel/imgs/bg-geo.jpg", 264 | "https://cdn.monsterx.cn/bot/gspanel/imgs/bg-hydro.jpg", 265 | "https://cdn.monsterx.cn/bot/gspanel/imgs/bg-pyro.jpg", 266 | "https://cdn.monsterx.cn/bot/gspanel/imgs/talent-anemo.png", 267 | "https://cdn.monsterx.cn/bot/gspanel/imgs/talent-cryo.png", 268 | "https://cdn.monsterx.cn/bot/gspanel/imgs/talent-dendro.png", 269 | "https://cdn.monsterx.cn/bot/gspanel/imgs/talent-electro.png", 270 | "https://cdn.monsterx.cn/bot/gspanel/imgs/talent-geo.png", 271 | "https://cdn.monsterx.cn/bot/gspanel/imgs/talent-hydro.png", 272 | "https://cdn.monsterx.cn/bot/gspanel/imgs/talent-pyro.png", 273 | "https://cdn.monsterx.cn/bot/gspanel/g2plot.min.js", 274 | f"https://cdn.monsterx.cn/bot/gspanel/team-{TEAM_TPL_VER}.css", 275 | f"https://cdn.monsterx.cn/bot/gspanel/team-{TEAM_TPL_VER}.html", 276 | f"https://cdn.monsterx.cn/bot/gspanel/panel-{CHAR_TPL_VER}.css", 277 | f"https://cdn.monsterx.cn/bot/gspanel/panel-{CHAR_TPL_VER}.html", 278 | f"https://cdn.monsterx.cn/bot/gspanel/list-{LIST_TPL_VER}.css", 279 | f"https://cdn.monsterx.cn/bot/gspanel/list-{LIST_TPL_VER}.html", 280 | ] 281 | tasks = [] 282 | for r in initRes: 283 | d = r.replace("https://cdn.monsterx.cn/bot/gspanel/", "").split("/")[0] 284 | tasks.append(download(r, local=("" if "." in d else d))) 285 | await asyncio.gather(*tasks) 286 | tasks.clear() 287 | logger.info("面板插件所需资源检查完毕!") 288 | 289 | 290 | async def download( 291 | url: str, local: Union[Path, str] = "", retry: int = 3 292 | ) -> Union[Path, None]: 293 | """ 294 | 一般文件下载,通常是即用即下的角色命座图片、技能图片、抽卡大图、圣遗物图片等 295 | 296 | * ``param url: str`` 下载链接 297 | * ``param local: Union[Path, str] = ""`` 下载路径,传入类型为 ``Path`` 时视为保存文件完整路径,传入类型为 ``str`` 时视为保存文件子文件夹名(默认下载至插件资源根目录) 298 | * ``param retry: int = 3`` 下载失败重试次数 299 | - ``return: Union[Path, None]`` 本地文件路径,出错时返回空 300 | """ # noqa: E501 301 | if not url.startswith("http"): 302 | url = DOWNLOAD_MIRROR + url + ".png" 303 | if not isinstance(local, Path): 304 | d = (LOCAL_DIR / local) if local else LOCAL_DIR 305 | if not d.exists(): 306 | d.mkdir(parents=True, exist_ok=True) 307 | f = d / url.split("/")[-1] 308 | else: 309 | if not local.parent.exists(): 310 | local.parent.mkdir(parents=True, exist_ok=True) 311 | f = local 312 | # 本地文件存在时便不再下载,JSON 文件除外 313 | if f.exists() and ".json" not in f.name: 314 | return f 315 | client, retry = AsyncClient(), 3 316 | while retry: 317 | try: 318 | async with client.stream( 319 | "GET", url, headers={"user-agent": "NoneBot-GsPanel"} 320 | ) as res: 321 | with open(f, "wb") as fb: 322 | async for chunk in res.aiter_bytes(): 323 | fb.write(chunk) 324 | return f 325 | except Exception as e: 326 | retry -= 1 327 | if retry: 328 | await asyncio.sleep(2) 329 | else: 330 | logger.opt(exception=e).error(f"面板资源 {f.name} 下载出错") 331 | return None 332 | 333 | 334 | async def uidHelper(qq: Union[str, int], uid: str = "") -> str: 335 | """ 336 | UID 助手,根据 QQ 获取对应原神 UID,也可传入 UID 更新指定 QQ 的绑定情况 337 | 338 | * ``param qq: Union[str, int]`` 操作 QQ 339 | * ``param uid: str = ""`` 操作 UID,默认不传入以查找该值,传入则视为绑定/更新 340 | - ``return: str``指定 QQ 绑定的原神 UID,绑定/更新时返回操作结果 341 | """ 342 | qq = str(qq) 343 | cfgFile = LOCAL_DIR / "qq-uid.json" 344 | uidCfg = json.loads(cfgFile.read_text(encoding="utf-8")) 345 | if uid: 346 | uidCfg[qq] = uid 347 | cfgFile.write_text( 348 | json.dumps(uidCfg, ensure_ascii=False, indent=2), encoding="utf-8" 349 | ) 350 | return "已{} QQ{} 的 UID 为 {}".format("更新" if qq in uidCfg else "绑定", qq, uid) 351 | return uidCfg.get(qq, "") 352 | 353 | 354 | async def aliasWho(input: str) -> str: 355 | """角色别名,未找到别名配置的原样返回""" 356 | for char in CHAR_ALIAS: 357 | if (input in char) or (input in CHAR_ALIAS[char]): 358 | return char 359 | return input 360 | 361 | 362 | async def aliasTeam(input: str) -> Union[str, List]: 363 | """队伍别名,未找到别名配置的原样返回""" 364 | for team in TEAM_ALIAS: 365 | if (input == team) or (input in TEAM_ALIAS[team].get("alias", [])): 366 | return TEAM_ALIAS[team]["chars"] 367 | return input 368 | -------------------------------------------------------------------------------- /nonebot_plugin_gspanel/__version__.py: -------------------------------------------------------------------------------- 1 | PLUGIN_VERSION = "0.2.20" 2 | LIST_TPL_VER = "0.2.7" 3 | CHAR_TPL_VER = "0.2.7" 4 | TEAM_TPL_VER = "0.2.20" 5 | -------------------------------------------------------------------------------- /nonebot_plugin_gspanel/data_convert.py: -------------------------------------------------------------------------------- 1 | import json 2 | from time import time 3 | from typing import Dict, List, Tuple 4 | 5 | from nonebot.log import logger 6 | 7 | from .__utils__ import ( 8 | CALC_RULES, 9 | CHAR_DATA, 10 | ELEM, 11 | GROW_VALUE, 12 | HASH_TRANS, 13 | MAIN_AFFIXS, 14 | POS, 15 | PROP, 16 | RANK_MAP, 17 | RELIC_APPEND, 18 | SKILL, 19 | SUB_AFFIXS, 20 | getServer, 21 | kStr, 22 | vStr, 23 | ) 24 | 25 | 26 | async def getRelicConfig(char: str, base: Dict = {}) -> Tuple[Dict, Dict, Dict]: 27 | """ 28 | 指定角色圣遗物计算配置获取,包括词条评分权重、词条数值原始权重、各位置圣遗物总分理论最高分和主词条理论最高得分 29 | 30 | * ``param char: str`` 角色名 31 | * ``param base: Dict = {}`` 角色的基础数值,可由 Enka 返回获得,格式为 ``{"生命值": 1, "攻击力": 1, "防御力": 1}`` 32 | - ``return: Tuple[Dict, Dict, Dict]`` 词条评分权重、词条数值原始权重、各位置圣遗物最高得分 33 | """ # noqa: E501 34 | affixWeight = CALC_RULES.get( 35 | char, {"攻击力百分比": 75, "暴击率": 100, "暴击伤害": 100} 36 | ) 37 | # 词条评分权重的 key 排序影响最优主词条选择 38 | # 通过特定排序使同等权重时生命攻击防御固定值词条优先级最低 39 | # key 的原始排序为 生命值攻击力防御力百分比、暴击率、暴击伤害、元素精通、元素伤害加成、物理伤害加成、元素充能效率 40 | # 注:已经忘了最初为什么这样写了,但总之就是顺序有影响+现在这样写能用 41 | affixWeight = dict( 42 | sorted( 43 | affixWeight.items(), 44 | key=lambda item: ( 45 | item[1], 46 | "暴击" in item[0], 47 | "加成" in item[0], 48 | "元素" in item[0], 49 | ), 50 | reverse=True, 51 | ) 52 | ) 53 | # 计算词条数值原始权重 54 | # 是一种与词条数值的乘积在百位数级别的东西,后续据此计算最终得分 55 | # 非百分比的生命攻击防御词条也按百分比词条的 affixWeight 权重计算 56 | pointMark = {k: v / GROW_VALUE[k] for k, v in affixWeight.items()} 57 | if pointMark.get("攻击力百分比"): 58 | pointMark["攻击力"] = pointMark["攻击力百分比"] / base.get("攻击力", 1020) * 100 59 | if pointMark.get("防御力百分比"): 60 | pointMark["防御力"] = pointMark["防御力百分比"] / base.get("防御力", 300) * 100 61 | if pointMark.get("生命值百分比"): 62 | pointMark["生命值"] = pointMark["生命值百分比"] / base.get("生命值", 400) * 100 63 | # 各位置圣遗物的总分理论最高分、主词条理论最高得分 64 | maxMark = {"1": {}, "2": {}, "3": {}, "4": {}, "5": {}} 65 | for posIdx in range(1, 6): 66 | # 主词条最高得分 67 | if posIdx <= 2: 68 | # 花和羽不计算主词条得分 69 | mainAffix = "生命值" if posIdx == 1 else "攻击力" 70 | maxMark[str(posIdx)]["main"] = 0 71 | maxMark[str(posIdx)]["total"] = 0 72 | else: 73 | # 沙杯头计算该位置评分权重最高的词条得分 74 | avalMainAffix = { 75 | k: v for k, v in affixWeight.items() if k in MAIN_AFFIXS[str(posIdx)] 76 | } 77 | # logger.debug( 78 | # "{} 的主词条推荐顺序为:\n{}".format( 79 | # list(POS.values())[posIdx - 1], 80 | # " / ".join(f"{k}[{v}]" for k, v in avalMainAffix.items()), 81 | # ) 82 | # ) 83 | mainAffix = list(avalMainAffix)[0] 84 | maxMark[str(posIdx)]["main"] = affixWeight[mainAffix] 85 | maxMark[str(posIdx)]["total"] = affixWeight[mainAffix] * 2 86 | # 副词条最高得分 87 | maxSubAffixs = { 88 | k: v 89 | for k, v in affixWeight.items() 90 | if k in SUB_AFFIXS and k != mainAffix and affixWeight.get(k) 91 | } 92 | # logger.debug( 93 | # "{} 的副词条推荐顺序为:\n{}".format( 94 | # list(POS.values())[posIdx - 1], 95 | # " / ".join(f"{k}[{v}]" for k, v in maxSubAffixs.items()), 96 | # ) 97 | # ) 98 | # 副词条中评分权重最高的词条得分大幅提升 99 | maxMark[str(posIdx)]["total"] += sum( 100 | affixWeight[k] * (1 if kIdx else 6) 101 | for kIdx, k in enumerate(list(maxSubAffixs)[0:4]) 102 | ) 103 | logger.debug( 104 | ( 105 | "「{}」圣遗物评分依据:" 106 | "\n\t词条评分权重 affixWeight\n\t{}" 107 | "\n\t词条数值原始权重 pointMark\n\t{}" 108 | "\n\t各位置圣遗物最高得分 maxMark\n\t{}" 109 | ).format( 110 | char, 111 | " / ".join(f"{k}[{v}]" for k, v in affixWeight.items()), 112 | " / ".join(f"{k}[{v}]" for k, v in pointMark.items()), 113 | " / ".join( 114 | f"{list(POS.values())[int(k)-1]}>主词条[{v['main']}]总分[{v['total']}]" 115 | for k, v in maxMark.items() 116 | ), 117 | ) 118 | ) 119 | return affixWeight, pointMark, maxMark 120 | 121 | 122 | def getRelicRank(score: float) -> str: 123 | """圣遗物评级获取""" 124 | # 在角色等级较低(基础数值较低)时评级可能显示为 "ERR" 125 | # 注:角色等级较低时不为 "ERR" 的评分也有可能出错 126 | return [r[0] for r in RANK_MAP if score <= r[1]][0] if score <= 66 else "ERR" 127 | 128 | 129 | async def calcRelicMark( 130 | relicData: Dict, charElement: str, affixWeight: Dict, pointMark: Dict, maxMark: Dict 131 | ) -> Dict: 132 | """ 133 | 指定角色圣遗物评分计算 134 | 135 | * ``param relicData: Dict`` 圣遗物数据 136 | * ``param charElement: str`` 角色的中文元素属性 137 | * ``param affixWeight: Dict`` 角色的词条评分权重,由 ``getRelicConfig()`` 获取 138 | * ``param pointMark: Dict`` 角色的词条数值原始权重,由 ``getRelicConfig()`` 获取 139 | * ``param maxMark: Dict`` 角色的各位置圣遗物最高得分,由 ``getRelicConfig()`` 获取 140 | - ``return: Dict`` 圣遗物评分结果 141 | """ 142 | posIdx, relicLevel = str(relicData["pos"]), relicData["level"] 143 | mainProp, subProps = relicData["main"], relicData["sub"] 144 | # 主词条得分、主词条收益系数(百分数) 145 | if posIdx in ["1", "2"]: 146 | calcMain, calcMainPct = 0.0, 100 147 | else: 148 | # 角色元素属性与伤害属性不同时权重为 0,不影响物理伤害得分 149 | _mainPointMark: float = pointMark.get( 150 | mainProp["prop"].replace(charElement, ""), 0 151 | ) 152 | _point: float = _mainPointMark * mainProp["value"] 153 | # 主词条与副词条的得分计算规则一致,但只取 25% 154 | calcMain = _point * 46.6 / 6 / 100 / 4 155 | # 主词条收益系数用于沙杯头位置主词条不正常时的圣遗物总分惩罚,最多扣除 50% 总分 156 | _punishPct: float = _point / maxMark[posIdx]["main"] / 2 / 4 157 | calcMainPct = 100 - 50 * (1 - _punishPct) 158 | # 副词条得分 159 | calcSubs = [] 160 | for s in subProps: 161 | _subPointMark: float = pointMark.get(s["prop"], 0) 162 | calcSub: float = _subPointMark * s["value"] * 46.6 / 6 / 100 163 | # 副词条 CSS 样式 164 | _awKey = ( 165 | f"{s['prop']}百分比" 166 | if s["prop"] in ["生命值", "攻击力", "防御力"] 167 | else s["prop"] 168 | ) 169 | _subAffixWeight: int = affixWeight.get(_awKey, 0) 170 | subStyleClass = ( 171 | ("great" if _subAffixWeight > 79 else "use") if calcSub else "unuse" 172 | ) 173 | # [词条名, 词条数值, 词条得分] 174 | calcSubs.append([subStyleClass, calcSub]) 175 | # 总分对齐系数(百分数),按满分 66 对齐各位置圣遗物的总分 176 | calcTotalPct: float = 66 / (maxMark[posIdx]["total"] * 46.6 / 6 / 100) * 100 177 | # 最终圣遗物总分 178 | _total = calcMain + sum(s[1] for s in calcSubs) 179 | calcTotal = _total * calcMainPct * calcTotalPct / 10000 180 | # 强化歪次数 181 | realAppendPropIdList: List[int] = ( 182 | relicData["_appendPropIdList"][-(relicLevel // 4) :] 183 | if (relicLevel // 4) 184 | else [] 185 | ) 186 | # logger.debug( 187 | # "{} 强化记录:\n{}".format( 188 | # list(POS.values())[int(posIdx) - 1], 189 | # " / ".join( 190 | # PROP.get(RELIC_APPEND[str(x)], RELIC_APPEND[str(x)]) 191 | # for x in realAppendPropIdList 192 | # ), 193 | # ) 194 | # ) 195 | notHit = len( 196 | [ 197 | x 198 | for x in realAppendPropIdList 199 | if not pointMark.get(PROP.get(RELIC_APPEND[str(x)], RELIC_APPEND[str(x)])) 200 | ] 201 | ) 202 | return { 203 | "rank": getRelicRank(calcTotal), 204 | "total": calcTotal, 205 | "nohit": notHit, 206 | "main": round(calcMain, 1), 207 | "sub": [ 208 | {"style": subRes[0], "goal": round(subRes[1], 1)} for subRes in calcSubs 209 | ], 210 | "main_pct": round(calcMainPct, 1), 211 | "total_pct": round(calcTotalPct, 1), 212 | } 213 | 214 | 215 | async def transFromEnka(avatarInfo: Dict, ts: int = 0) -> Dict: 216 | """ 217 | 转换 Enka.Network 角色查询数据为内部格式 218 | 219 | * ``param avatarInfo: Dict`` Enka.Network 角色查询数据,取自 ``data["avatarInfoList"]`` 列表 220 | * ``param ts: int = 0`` 数据时间戳 221 | - ``return: Dict`` 内部格式角色数据,用于本地缓存等 222 | """ 223 | charData = CHAR_DATA[str(avatarInfo["avatarId"])] 224 | res = { 225 | "id": avatarInfo["avatarId"], 226 | "rarity": 5 if "QUALITY_ORANGE" in charData["QualityType"] else 4, 227 | "name": charData["NameCN"], 228 | "slogan": charData["Slogan"], 229 | "element": ELEM[charData["Element"]], # 中文单字 230 | "cons": len(avatarInfo.get("talentIdList", [])), # int 231 | "fetter": avatarInfo["fetterInfo"]["expLevel"], # int 232 | "level": int(avatarInfo["propMap"]["4001"]["val"]), # int 233 | "icon": charData["Costumes"][str(avatarInfo["costumeId"])]["icon"] 234 | if avatarInfo.get("costumeId") 235 | else charData["iconName"], 236 | "gachaAvatarImg": charData["Costumes"][str(avatarInfo["costumeId"])]["art"] 237 | if avatarInfo.get("costumeId") 238 | else charData["iconName"].replace("UI_AvatarIcon_", "UI_Gacha_AvatarImg_"), 239 | "baseProp": { # float 240 | "生命值": avatarInfo["fightPropMap"]["1"], 241 | "攻击力": avatarInfo["fightPropMap"]["4"], 242 | "防御力": avatarInfo["fightPropMap"]["7"], 243 | }, 244 | "fightProp": { # float 245 | "生命值": avatarInfo["fightPropMap"]["2000"], 246 | # "攻击力": avatarInfo["fightPropMap"]["2001"], 247 | "攻击力": avatarInfo["fightPropMap"]["4"] 248 | * (1 + avatarInfo["fightPropMap"].get("6", 0)) 249 | + avatarInfo["fightPropMap"].get("5", 0), 250 | "防御力": avatarInfo["fightPropMap"]["2002"], 251 | "暴击率": avatarInfo["fightPropMap"]["20"] * 100, 252 | "暴击伤害": avatarInfo["fightPropMap"]["22"] * 100, 253 | "治疗加成": avatarInfo["fightPropMap"]["26"] * 100, 254 | "元素精通": avatarInfo["fightPropMap"]["28"], 255 | "元素充能效率": avatarInfo["fightPropMap"]["23"] * 100, 256 | "物理伤害加成": avatarInfo["fightPropMap"]["30"] * 100, 257 | "火元素伤害加成": avatarInfo["fightPropMap"]["40"] * 100, 258 | "水元素伤害加成": avatarInfo["fightPropMap"]["42"] * 100, 259 | "风元素伤害加成": avatarInfo["fightPropMap"]["44"] * 100, 260 | "雷元素伤害加成": avatarInfo["fightPropMap"]["41"] * 100, 261 | "草元素伤害加成": avatarInfo["fightPropMap"]["43"] * 100, 262 | "冰元素伤害加成": avatarInfo["fightPropMap"]["46"] * 100, 263 | "岩元素伤害加成": avatarInfo["fightPropMap"]["45"] * 100, 264 | }, 265 | "skills": {}, 266 | "consts": [], 267 | "weapon": {}, 268 | "relics": [], 269 | "relicSet": {}, 270 | "relicCalc": {}, 271 | "damage": {}, # 预留 272 | "time": ts or int(time()), 273 | } 274 | # 技能数据 275 | skills = {"a": {}, "e": {}, "q": {}} 276 | extraLevels = { 277 | k[-1]: v for k, v in avatarInfo.get("proudSkillExtraLevelMap", {}).items() 278 | } 279 | for idx, skillId in enumerate(charData["SkillOrder"]): 280 | # 实际技能等级、显示技能等级 281 | level = avatarInfo["skillLevelMap"][str(skillId)] 282 | currentLvl = level + extraLevels.get(list(SKILL)[idx], 0) 283 | skills[list(SKILL.values())[idx]] = { 284 | "style": "extra" if currentLvl > level else "", 285 | "icon": charData["Skills"][str(skillId)], 286 | "level": currentLvl, 287 | "originLvl": level, 288 | } 289 | res["skills"] = skills 290 | # 命座数据 291 | consts = [] 292 | for cIdx, consImgName in enumerate(charData["Consts"]): 293 | consts.append( 294 | { 295 | "style": "off" if cIdx + 1 > res["cons"] else "", 296 | "icon": consImgName, 297 | } 298 | ) 299 | res["consts"] = consts 300 | # 装备数据 301 | affixWeight, pointMark, maxMark = await getRelicConfig( 302 | charData["NameCN"], res["baseProp"] 303 | ) 304 | relicsMark, relicsCnt, relicSet = 0.0, 0, {} 305 | for equip in avatarInfo["equipList"]: 306 | if equip["flat"]["itemType"] == "ITEM_WEAPON": 307 | weaponSub: str = equip["flat"]["weaponStats"][-1]["appendPropId"] 308 | weaponSubValue = equip["flat"]["weaponStats"][-1]["statValue"] 309 | res["weapon"] = { 310 | "id": equip["itemId"], 311 | "rarity": equip["flat"]["rankLevel"], # int 312 | "name": HASH_TRANS.get( 313 | str(equip["flat"]["nameTextMapHash"]), "缺少翻译" 314 | ), 315 | "affix": list(equip["weapon"].get("affixMap", {"_": 0}).values())[0] 316 | + 1, 317 | "level": equip["weapon"]["level"], # int 318 | "icon": equip["flat"]["icon"], 319 | "main": equip["flat"]["weaponStats"][0]["statValue"], # int 320 | "sub": { 321 | "prop": PROP[weaponSub].replace("百分比", ""), 322 | "value": "{}{}".format( 323 | weaponSubValue, 324 | "" if weaponSub.endswith("ELEMENT_MASTERY") else "%", 325 | ), 326 | } 327 | if weaponSub != "FIGHT_PROP_BASE_ATTACK" 328 | else {}, 329 | } 330 | elif equip["flat"]["itemType"] == "ITEM_RELIQUARY": 331 | mainProp: Dict = equip["flat"]["reliquaryMainstat"] 332 | subProps: List = equip["flat"].get("reliquarySubstats", []) 333 | posIdx = list(POS.keys()).index(equip["flat"]["equipType"]) + 1 334 | relicData = { 335 | "pos": posIdx, 336 | "rarity": equip["flat"]["rankLevel"], 337 | "name": HASH_TRANS.get( 338 | str(equip["flat"]["nameTextMapHash"]), "缺少翻译" 339 | ), 340 | "setName": HASH_TRANS.get( 341 | str(equip["flat"]["setNameTextMapHash"]), "缺少翻译" 342 | ), 343 | "level": equip["reliquary"]["level"] - 1, 344 | "main": { 345 | "prop": PROP[mainProp["mainPropId"]], 346 | "value": mainProp["statValue"], 347 | }, 348 | "sub": [ 349 | {"prop": PROP[s["appendPropId"]], "value": s["statValue"]} 350 | for s in subProps 351 | ], 352 | "calc": {}, 353 | "icon": equip["flat"]["icon"], 354 | "_appendPropIdList": equip["reliquary"].get("appendPropIdList", []), 355 | } 356 | relicData["calc"] = await calcRelicMark( 357 | relicData, res["element"], affixWeight, pointMark, maxMark 358 | ) 359 | # 分数计算完毕后再将词条名称、数值转为适合 HTML 渲染的格式 360 | relicData["main"]["value"] = vStr( 361 | relicData["main"]["prop"], relicData["main"]["value"] 362 | ) 363 | relicData["main"]["prop"] = kStr(relicData["main"]["prop"]) 364 | relicData["sub"] = [ 365 | {"prop": kStr(s["prop"]), "value": vStr(s["prop"], s["value"])} 366 | for s in relicData["sub"] 367 | ] 368 | # 额外数据处理 369 | relicData["calc"]["total"] = round(relicData["calc"]["total"], 1) 370 | relicData.pop("_appendPropIdList") 371 | relicSet[relicData["setName"]] = relicSet.get(relicData["setName"], 0) + 1 372 | res["relics"].append(relicData) 373 | # 累积圣遗物套装评分和计数器 374 | relicsMark += relicData["calc"]["total"] 375 | relicsCnt += 1 376 | # 圣遗物套装 377 | res["relicSet"] = relicSet 378 | res["relicCalc"] = { 379 | "rank": getRelicRank(relicsMark / relicsCnt) if relicsCnt else "NaN", 380 | "total": round(relicsMark, 1), 381 | } 382 | return res 383 | 384 | 385 | async def transToTeyvat(avatarsData: List[Dict], uid: str) -> Dict: 386 | """ 387 | 转换内部格式角色数据为 Teyvat Helper 请求格式 388 | 389 | * ``param avatarsData: List[Dict]`` 内部格式角色数据,由 ``transFromEnka()`` 获取 390 | * ``param uid: str`` 角色所属用户 UID 391 | - ``return: Dict`` Teyvat Helper 请求格式角色数据 392 | """ 393 | res = {"uid": uid, "role_data": []} 394 | if uid[0] not in ["1", "2"]: 395 | res["server"] = getServer(uid, teyvat=True) 396 | 397 | for avatarData in avatarsData: 398 | name = avatarData["name"] 399 | cons = avatarData["cons"] 400 | weapon = avatarData["weapon"] 401 | baseProp = avatarData["baseProp"] 402 | fightProp = avatarData["fightProp"] 403 | skills = avatarData["skills"] 404 | relics = avatarData["relics"] 405 | relicSet = avatarData["relicSet"] 406 | 407 | # dataFix from https://github.com/yoimiya-kokomi/miao-plugin/blob/ac27075276154ef5a87a458697f6e5492bd323bd/components/profile-data/enka-data.js#L186 # noqa: E501 408 | if name == "雷电将军": 409 | _thunderDmg = fightProp["雷元素伤害加成"] 410 | _recharge = fightProp["元素充能效率"] 411 | fightProp["雷元素伤害加成"] = max(0, _thunderDmg - (_recharge - 100) * 0.4) 412 | if name == "莫娜": 413 | _waterDmg = fightProp["水元素伤害加成"] 414 | _recharge = fightProp["元素充能效率"] 415 | fightProp["水元素伤害加成"] = max(0, _waterDmg - _recharge * 0.2) 416 | if name == "妮露" and cons == 6: 417 | _count = float(fightProp["生命值"] / 1000) 418 | _crit = fightProp["暴击率"] 419 | _critDmg = fightProp["暴击伤害"] 420 | fightProp["暴击率"] = max(5, _crit - min(30, _count * 0.6)) 421 | fightProp["暴击伤害"] = max(50, _critDmg - min(60, _count * 1.2)) 422 | if weapon["name"] in ["息灾", "波乱月白经津", "雾切之回光", "猎人之径"]: 423 | for elem in ["火", "水", "雷", "风", "冰", "岩", "草"]: 424 | _origin = fightProp[f"{elem}元素伤害加成"] 425 | fightProp[f"{elem}元素伤害加成"] = max( 426 | 0, _origin - 12 - 12 * (weapon["affix"] - 1) / 4 427 | ) 428 | 429 | # 圣遗物数据 430 | artifacts = [] 431 | for a in relics: 432 | tData = { 433 | "artifacts_name": a["name"], 434 | "artifacts_type": list(POS.values())[a["pos"] - 1], 435 | "level": a["level"], 436 | "maintips": kStr(a["main"]["prop"], reverse=True), 437 | "mainvalue": int(a["main"]["value"]) 438 | if str(a["main"]["value"]).isdigit() 439 | else a["main"]["value"], 440 | } 441 | tData.update( 442 | { 443 | f"tips{sIdx + 1}": "{}+{}".format( 444 | kStr(a["sub"][sIdx]["prop"], reverse=True) 445 | if sIdx < len(a["sub"]) 446 | else "", 447 | a["sub"][sIdx]["value"] if sIdx < len(a["sub"]) else 0, 448 | ) 449 | for sIdx in range(4) 450 | } 451 | ) 452 | artifacts.append(tData) 453 | 454 | # 单个角色最终结果 455 | res["role_data"].append( 456 | { 457 | "uid": uid, 458 | "role": name, 459 | "role_class": cons, 460 | "level": int(avatarData["level"]), 461 | "weapon": weapon["name"], 462 | "weapon_level": weapon["level"], 463 | "weapon_class": f"精炼{weapon['affix']}阶", 464 | "hp": int(fightProp["生命值"]), 465 | "base_hp": int(baseProp["生命值"]), 466 | "attack": int(fightProp["攻击力"]), 467 | "base_attack": int(baseProp["攻击力"]), 468 | "defend": int(fightProp["防御力"]), 469 | "base_defend": int(baseProp["防御力"]), 470 | "element": round(fightProp["元素精通"]), 471 | "crit": f"{round(fightProp['暴击率'], 1)}%", 472 | "crit_dmg": f"{round(fightProp['暴击伤害'], 1)}%", 473 | "heal": f"{round(fightProp['治疗加成'], 1)}%", 474 | "recharge": f"{round(fightProp['元素充能效率'], 1)}%", 475 | "fire_dmg": f"{round(fightProp['火元素伤害加成'], 1)}%", 476 | "water_dmg": f"{round(fightProp['水元素伤害加成'], 1)}%", 477 | "thunder_dmg": f"{round(fightProp['雷元素伤害加成'], 1)}%", 478 | "wind_dmg": f"{round(fightProp['风元素伤害加成'], 1)}%", 479 | "ice_dmg": f"{round(fightProp['冰元素伤害加成'], 1)}%", 480 | "rock_dmg": f"{round(fightProp['岩元素伤害加成'], 1)}%", 481 | "grass_dmg": f"{round(fightProp['草元素伤害加成'], 1)}%", 482 | "physical_dmg": f"{round(fightProp['物理伤害加成'], 1)}%", 483 | "artifacts": "+".join( 484 | f"{k}{4 if v >= 4 else (2 if v >= 2 else 1)}" 485 | for k, v in relicSet.items() 486 | if (v >= 2) or ("之人" in k) 487 | ), 488 | "ability1": skills["a"]["level"], 489 | "ability2": skills["e"]["level"], 490 | "ability3": skills["q"]["level"], 491 | "artifacts_detail": artifacts, 492 | } 493 | ) 494 | 495 | return res 496 | 497 | 498 | async def simplDamageRes(damage: Dict) -> Dict: 499 | """ 500 | 转换角色伤害计算请求数据为精简格式 501 | 502 | * ``param damage: Dict`` 角色伤害计算请求数据,由 ``queryDamageApi()["result"][int]`` 获取 503 | - ``return: Dict`` 精简格式伤害数据,出错时返回 ``{}`` 504 | """ 505 | res = {"level": damage["zdl_result"] or "NaN", "data": [], "buff": []} 506 | for key in ["damage_result_arr", "damage_result_arr2"]: 507 | for dmgDetail in damage[key]: 508 | if not isinstance(dmgDetail, dict): 509 | continue 510 | dmgTitle = "{}{}".format( 511 | f"[{damage['zdl_result2']}]
" if key == "damage_result_arr2" else "", 512 | dmgDetail["title"], 513 | ) 514 | if "期望" in str(dmgDetail["value"]) or not dmgDetail.get("expect"): 515 | dmgCrit, dmgExp = "-", str(dmgDetail["value"]).replace("期望", "") 516 | else: 517 | dmgCrit = str(dmgDetail["value"]) 518 | dmgExp = str(dmgDetail["expect"]).replace("期望", "") 519 | res["data"].append([dmgTitle, dmgCrit, dmgExp]) 520 | for buff in damage["bonus"]: 521 | # damage["bonus"]: {"0": {}, "2": {}, ...} 522 | # damage["bonus"]: [{}, {}, ...] 523 | intro = ( 524 | damage["bonus"][buff]["intro"] if isinstance(buff, str) else buff["intro"] 525 | ) 526 | buffTitle, buffDetail = intro.split(":") 527 | if buffTitle not in ["注", "备注"]: 528 | res["buff"].append([buffTitle, buffDetail]) 529 | return res 530 | 531 | 532 | async def simplFightProp( 533 | fightProp: Dict, baseProp: Dict, char: str, element: str 534 | ) -> Dict[str, Dict]: 535 | """ 536 | 转换角色面板数据为 HTML 模板需求格式 537 | 538 | * ``param fightProp: Dict`` 角色面板数据,由 ``transFromEnka()["fightProp"]`` 获取 539 | * ``param baseProp: Dict`` 角色基础数据,由 ``transFromEnka()["baseProp"]`` 获取 540 | * ``param element: str`` 角色元素属性 541 | - ``return: Dict[str, Dict]`` HTML 模板需求格式面板数据 542 | """ 543 | affixWeight = CALC_RULES.get( 544 | char, {"攻击力百分比": 75, "暴击率": 100, "暴击伤害": 100} 545 | ) 546 | 547 | # 排列伤害加成 548 | prefer = ( 549 | element 550 | if affixWeight.get("元素伤害加成", 0) > affixWeight.get("物理伤害加成", 0) 551 | else "物" 552 | ) 553 | damages = sorted( 554 | [{"k": k, "v": v} for k, v in fightProp.items() if str(k).endswith("伤害加成")], 555 | key=lambda x: (x["v"], x["k"][0] == prefer), 556 | reverse=True, 557 | ) 558 | for unuseDmg in damages[1:]: 559 | fightProp.pop(unuseDmg["k"]) 560 | 561 | # 生成模板渲染所需数据 562 | res = {} 563 | for propTitle, propValue in fightProp.items(): 564 | # 跳过无效治疗加成 565 | if propTitle == "治疗加成" and not propValue and not affixWeight.get(propTitle): 566 | continue 567 | # 整理渲染数据 568 | res[propTitle] = { 569 | "value": f"{round(propValue, 1)}%" 570 | if propTitle not in ["生命值", "攻击力", "防御力", "元素精通"] 571 | else round(propValue), 572 | "weight": max( 573 | affixWeight.get("元素伤害加成", 0), affixWeight.get("物理伤害加成", 0) 574 | ) 575 | if propTitle.endswith("伤害加成") 576 | else affixWeight.get(propTitle) or affixWeight.get(f"{propTitle}百分比", 0), 577 | } 578 | # 补充基础数值 579 | if propTitle in ["生命值", "攻击力", "防御力"]: 580 | res[propTitle]["detail"] = [ 581 | round(baseProp[propTitle]), 582 | round(res[propTitle]["value"] - baseProp[propTitle]), 583 | ] 584 | # 标记异常伤害加成 585 | if propTitle.endswith("伤害加成"): 586 | if ( 587 | propTitle[0] not in ["物", element] 588 | or affixWeight.get(propTitle[-6:], 0) != res[propTitle]["weight"] 589 | ): 590 | res[propTitle]["error"] = True 591 | 592 | return res 593 | 594 | 595 | async def simplTeamDamageRes(raw: Dict, rolesData: Dict) -> Dict: 596 | """ 597 | 转换队伍伤害计算请求数据为精简格式 598 | 599 | * ``param raw: Dict`` 队伍伤害计算请求数据,由 ``queryDamageApi(*, "team")["result"]`` 获取 600 | * ``param rolesData: Dict`` 角色数据,键为角色中文名,值为内部格式 601 | - ``return: Dict`` 精简格式伤害数据。出错时返回 ``{"error": "错误信息"}`` 602 | """ 603 | s = ( 604 | str(raw["zdl_tips0"]) 605 | .replace("你的队伍", "") 606 | .replace("秒内造成总伤害", "-") 607 | .replace(",DPS为:", "") 608 | ) 609 | tm, total = s.split("-") 610 | 611 | pieData, pieColor = [], [] 612 | for x in raw["chart_data"]: 613 | char, damage = x["name"].split("\n") 614 | pieData.append({"char": char, "damage": float(damage.replace("W", ""))}) 615 | pieColor.append(x["label"]["color"]) 616 | pieData = sorted(pieData, key=lambda x: x["damage"], reverse=True) 617 | # 寻找伤害最高的角色元素属性,跳过绽放等伤害来源 618 | elem = [ 619 | rolesData[_source["char"]]["element"] 620 | for _source in pieData 621 | if rolesData.get(_source["char"]) 622 | ][0] 623 | 624 | avatars = {} 625 | for role in raw["role_list"]: 626 | panelData = rolesData[role["role"]] 627 | avatars[role["role"]] = { 628 | "rarity": role["role_star"], 629 | "icon": panelData["icon"], 630 | "name": role["role"], 631 | "elem": panelData["element"], 632 | "cons": role["role_class"], 633 | "level": role["role_level"].replace("Lv", ""), 634 | "weapon": { 635 | "icon": panelData["weapon"]["icon"], 636 | "level": panelData["weapon"]["level"], 637 | "rarity": panelData["weapon"]["rarity"], 638 | "affix": panelData["weapon"]["affix"], 639 | }, 640 | "sets": { 641 | [r for r in panelData["relics"] if r["setName"] == k][0]["icon"].split( 642 | "_" 643 | )[-2]: (2 if v < 4 else 4) 644 | for k, v in panelData["relicSet"].items() 645 | if v >= 2 # 暂未排版 祭X之人 单件套装 646 | }, 647 | "cp": round(panelData["fightProp"]["暴击率"], 1), 648 | "cd": round(panelData["fightProp"]["暴击伤害"], 1), 649 | "key_prop": role["key_ability"], 650 | "key_value": role["key_value"], 651 | "skills": [ 652 | { 653 | "icon": skill["icon"], 654 | "style": skill["style"], 655 | "level": skill["level"], 656 | } 657 | for _, skill in panelData["skills"].items() 658 | ], 659 | } 660 | 661 | for rechargeData in raw["recharge_info"]: 662 | name, tmp = rechargeData["recharge"].split("共获取同色球") 663 | same, diff = tmp.split("个,异色球") 664 | if len(diff.split("个,无色球")) == 2: 665 | # 暂未排版无色球 666 | diff = diff.split("个,无色球")[0] 667 | avatars[name]["recharge"] = { 668 | "pct": rechargeData["rate"], 669 | "same": round(float(same), 1), 670 | "diff": round(float(diff.replace("个", "")), 1), 671 | } 672 | 673 | damages = [] 674 | for step in raw["advice"]: 675 | if not step.get("content"): 676 | logger.error(f"奇怪的伤害:{step}") 677 | continue 678 | # content: "4.2s 雷神e协同,暴击:3016,不暴击:1565,期望:2343" 679 | t, s = step["content"].split(" ") 680 | if len(s.split(",")) == 1: 681 | # "3.89s 万叶q染色为:雷" 682 | a = s.split(",")[0] 683 | d = ["-", "-", "-"] 684 | else: 685 | a, dmgs = s.split(",") 686 | if len(dmgs.split(",")) == 1: 687 | d = ["-", "-", dmgs.split(",")[0].split(":")[-1]] 688 | else: 689 | d = [dd.split(":")[-1] for dd in dmgs.split(",")] 690 | damages.append([t.replace("s", ""), a.upper(), *d]) 691 | 692 | buffs = [] 693 | for buff in raw["buff"]: 694 | if not buff.get("content"): 695 | logger.error(f"奇怪的 Buff:{buff}") 696 | continue 697 | # buff: "1.5s 风套-怪物雷抗减少-40%" 698 | t, tmp = buff["content"].split(" ", 1) 699 | b, bd = tmp.split("-", 1) 700 | buffs.append([t.replace("s", ""), b.upper(), bd.upper()]) 701 | 702 | return { 703 | "uid": raw["uid"], 704 | "elem": elem, 705 | "rank": raw["zdl_tips2"], 706 | "dps": raw["zdl_result"], 707 | "tm": tm, 708 | "total": total, 709 | "pie_data": json.dumps(pieData, ensure_ascii=False), 710 | "pie_color": json.dumps(pieColor), 711 | "avatars": avatars, 712 | "actions": raw["combo_intro"].split(","), 713 | "damages": damages, 714 | "buffs": buffs, 715 | } 716 | -------------------------------------------------------------------------------- /nonebot_plugin_gspanel/data_source.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | from time import time 4 | from copy import deepcopy 5 | from typing import Dict, List, Union, Literal 6 | from datetime import datetime, timezone, timedelta 7 | 8 | from nonebot import require 9 | from nonebot.log import logger 10 | from httpx import HTTPError, AsyncClient 11 | 12 | from .__utils__ import LOCAL_DIR, SCALE_FACTOR, download 13 | from .__version__ import CHAR_TPL_VER, LIST_TPL_VER, TEAM_TPL_VER 14 | from .data_convert import ( 15 | transFromEnka, 16 | transToTeyvat, 17 | simplDamageRes, 18 | simplFightProp, 19 | simplTeamDamageRes, 20 | ) 21 | 22 | require("nonebot_plugin_htmlrender") 23 | from nonebot_plugin_htmlrender import template_to_pic # noqa: E402 24 | 25 | 26 | async def queryPanelApi(uid: str) -> Dict: 27 | """ 28 | 原神游戏内角色展柜数据请求 29 | 30 | * ``param uid: str`` 查询用户 UID 31 | - ``return: Dict`` 查询结果,出错时返回 ``{"error": "错误信息"}`` 32 | """ 33 | enkaMirrors = [ 34 | "https://enka.network", 35 | "http://profile.microgg.cn", 36 | ] 37 | # B 服优先从 MicroGG API 尝试 38 | if int(uid[0]) == 5: 39 | enkaMirrors.reverse() 40 | 41 | async with AsyncClient() as client: 42 | resJson = {} 43 | for idx, root in enumerate(enkaMirrors): 44 | apiName = "MicroGG API" if "microgg" in root else "Enka API" 45 | try: 46 | res = await client.get( 47 | url=f"{root}/api/uid/{uid}", 48 | headers={ 49 | "Accept": "application/json", 50 | "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7", 51 | "Cache-Control": "no-cache", 52 | "Cookie": "locale=zh-CN", 53 | "Referer": "https://enka.network/", 54 | "User-Agent": "GsPanel/0.2", 55 | }, 56 | follow_redirects=True, 57 | timeout=20.0, 58 | ) 59 | 60 | # 400 = Wrong UID format 61 | # 404 = Player does not exist (MHY server said that) 62 | # 424 = Game maintenance / everything is broken after the game update 63 | # 429 = Rate-limited (either by my server or by MHY server) 64 | # 500 = General server error 65 | # 503 = I screwed up massively 66 | errorMsg = { 67 | "400": f"玩家 {uid} UID 格式错误!", 68 | "404": f"玩家 {uid} 不存在!", 69 | "424": f"{apiName} 正在维护中!", 70 | "429": f"{apiName} 访问过于频繁!", 71 | "500": f"{apiName} 服务器普通故障!", 72 | "503": f"{apiName} 服务器严重错误!", 73 | } 74 | status = str(res.status_code) 75 | if status in ["400", "404"]: 76 | return {"error": errorMsg[status]} 77 | elif status in errorMsg: 78 | if idx == len(enkaMirrors) - 1: 79 | return {"error": errorMsg[status]} 80 | logger.error(errorMsg[status]) 81 | continue 82 | 83 | resJson = res.json() 84 | break 85 | except (HTTPError, json.decoder.JSONDecodeError) as e: 86 | if idx == len(enkaMirrors) - 1: 87 | logger.opt(exception=e).error("面板数据接口无法访问或返回错误") 88 | return {"error": f"[{e.__class__.__name__}] 暂时无法访问面板数据接口.."} 89 | logger.info(f"从 {apiName} 获取面板失败,正在自动切换镜像重试...") 90 | if not resJson.get("playerInfo"): 91 | return {"error": f"玩家 {uid} 返回信息不全,接口可能正在维护.."} 92 | if not resJson.get("avatarInfoList"): 93 | return {"error": f"玩家 {uid} 的角色展柜详细数据已隐藏!"} 94 | if not resJson["playerInfo"].get("showAvatarInfoList"): 95 | return {"error": f"玩家 {uid} 的角色展柜内还没有角色哦!"} 96 | return resJson 97 | 98 | 99 | async def queryDamageApi( 100 | body: Dict, mode: Literal["single", "team"] = "single" 101 | ) -> Dict: 102 | """ 103 | 角色伤害计算数据请求(提瓦特小助手) 104 | 105 | * ``param body: Dict`` 查询角色数据 106 | * ``param mode: Literal["single", "team"] = "single"`` 查询接口类型,默认请求角色伤害接口,传入 ``"team"`` 请求队伍伤害接口 107 | - ``return: Dict`` 查询结果,出错时返回 ``{}`` 108 | """ # noqa: E501 109 | apiMap = { 110 | "single": "https://api.lelaer.com/ys/getDamageResult.php", 111 | "team": "https://api.lelaer.com/ys/getTeamResult.php", 112 | } 113 | async with AsyncClient() as client: 114 | try: 115 | res = await client.post( 116 | apiMap[mode], 117 | json=body, 118 | headers={ 119 | "referer": "https://servicewechat.com/wx2ac9dce11213c3a8/192/page-frame.html", # noqa: E501 120 | "user-agent": ( 121 | "Mozilla/5.0 (Linux; Android 12; SM-G977N Build/SP1A.210812.016" 122 | "; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 " 123 | "Chrome/86.0.4240.99 XWEB/4375 MMWEBSDK/20221011 Mobile Safari" 124 | "/537.36 MMWEBID/4357 MicroMessenger/8.0.30.2244(0x28001E44) " 125 | "WeChat/arm64 Weixin GPVersion/1 NetType/WIFI Language/zh_CN " 126 | "ABI/arm64 MiniProgramEnv/android" 127 | ), 128 | }, 129 | timeout=20.0, 130 | ) 131 | return res.json() 132 | except (HTTPError, json.decoder.JSONDecodeError) as e: 133 | logger.opt(exception=e).error("提瓦特小助手接口无法访问或返回错误") 134 | return {} 135 | 136 | 137 | async def getAvatarData(uid: str, char: str = "全部") -> Dict: 138 | """ 139 | 角色数据获取(内部格式) 140 | 141 | * ``param uid: str`` 查询用户 UID 142 | * ``param char: str = "全部"`` 查询角色名 143 | - ``return: Dict`` 查询结果。出错时返回 ``{"error": "错误信息"}`` 144 | """ 145 | # 总是先读取一遍缓存 146 | cache = LOCAL_DIR / "cache" / f"{uid}.json" 147 | if cache.exists(): 148 | cacheData = json.loads(cache.read_text(encoding="utf-8")) 149 | nextQueryTime: int = cacheData["next"] 150 | else: 151 | cacheData, nextQueryTime = {}, 0 152 | 153 | refreshed, _tip, _time = [], "", 0 154 | 155 | if int(time()) <= nextQueryTime: 156 | _tip, _time = "warning", nextQueryTime 157 | logger.info(f"UID{uid} 的角色展柜数据刷新冷却还有 {int(nextQueryTime - time())} 秒!") 158 | else: 159 | logger.info(f"UID{uid} 的角色展柜数据正在刷新!") 160 | newData = await queryPanelApi(uid) 161 | _time = time() 162 | # 没有缓存 & 本次刷新失败,返回错误信息 163 | if not cacheData and newData.get("error"): 164 | return newData 165 | # 本次刷新成功,处理全部角色 166 | elif not newData.get("error"): 167 | _tip = "success" 168 | avatarsCache = {str(x["id"]): x for x in cacheData.get("avatars", [])} 169 | now, wait4Dmg, avatars = int(time()), {}, [] 170 | for newAvatar in newData["avatarInfoList"]: 171 | if newAvatar["avatarId"] in [10000005, 10000007]: 172 | logger.info("旅行者面板查询暂未支持!") 173 | continue 174 | tmp, gotDmg = await transFromEnka(newAvatar, now), False 175 | 176 | if str(tmp["id"]) in avatarsCache: 177 | # 保留旧的伤害计算数据 178 | avatarsCache[str(tmp["id"])].pop("time") 179 | cacheDmg = avatarsCache[str(tmp["id"])].pop("damage") 180 | nowStat = { 181 | k: v for k, v in tmp.items() if k not in ["damage", "time"] 182 | } 183 | if cacheDmg and avatarsCache[str(tmp["id"])] == nowStat: 184 | logger.info(f"UID{uid} 的 {tmp['name']} 伤害计算结果无需刷新!") 185 | tmp["damage"], gotDmg = cacheDmg, True 186 | else: 187 | logger.debug( 188 | "UID{} 的 {} 数据变化细则:\n{}\n{}".format( 189 | uid, tmp["name"], avatarsCache[str(tmp["id"])], nowStat 190 | ) 191 | ) 192 | refreshed.append(tmp["id"]) 193 | avatars.append(tmp) 194 | if not gotDmg: 195 | wait4Dmg[str(len(avatars) - 1)] = tmp 196 | 197 | if wait4Dmg: 198 | _names = "/".join(f"[{aI}]{a['name']}" for aI, a in wait4Dmg.items()) 199 | logger.info(f"正在为 UID{uid} 的 {_names} 重新请求伤害计算接口") 200 | # 深拷贝避免转换对上下文中的 avatars 产生影响 201 | wtf = deepcopy([a for _, a in wait4Dmg.items()]) 202 | teyvatBody = await transToTeyvat(wtf, uid) 203 | teyvatRaw = await queryDamageApi(teyvatBody) 204 | if teyvatRaw.get("code", "x") != 200 or len(wait4Dmg) != len( 205 | teyvatRaw.get("result", []) 206 | ): 207 | logger.error( 208 | f"UID{uid} 的 {len(wait4Dmg)} 位角色伤害计算请求失败!" 209 | f"\n>>>> [提瓦特返回] {teyvatRaw}" 210 | ) 211 | else: 212 | for dmgIdx, dmgData in enumerate(teyvatRaw.get("result", [])): 213 | aIdx = int(list(wait4Dmg.keys())[dmgIdx]) 214 | avatars[aIdx]["damage"] = await simplDamageRes(dmgData) 215 | 216 | cacheData["avatars"] = [ 217 | *avatars, 218 | *[ 219 | aData 220 | for _, aData in avatarsCache.items() 221 | if aData["id"] not in refreshed 222 | ], 223 | ] 224 | cacheData["next"] = now + newData["ttl"] 225 | cache.write_text( 226 | json.dumps(cacheData, ensure_ascii=False, indent=2), encoding="utf-8" 227 | ) 228 | # 有缓存 & 本次刷新失败,打印错误信息 229 | else: 230 | _tip = "error" 231 | logger.error(newData["error"]) 232 | 233 | # 获取所需角色数据 234 | if char == "全部": 235 | # 为本次更新的角色添加刷新标记 236 | for aIdx, aData in enumerate(cacheData["avatars"]): 237 | cacheData["avatars"][aIdx]["refreshed"] = aData["id"] in refreshed 238 | # 格式化刷新时间 239 | _datetime = datetime.fromtimestamp(_time, timezone(timedelta(hours=8))) 240 | cacheData["timetips"] = [_tip, _datetime.strftime("%Y-%m-%d %H:%M:%S")] 241 | return cacheData 242 | searchRes = [x for x in cacheData["avatars"] if x["name"] == char] 243 | return ( 244 | { 245 | "error": "玩家 {} 游戏内展柜中的 {} 位角色中没有 {}!".format( 246 | uid, len(cacheData["avatars"]), char 247 | ) 248 | } 249 | if not searchRes 250 | else searchRes[0] 251 | ) 252 | 253 | 254 | async def getPanel(uid: str, char: str = "全部") -> Union[bytes, str]: 255 | """ 256 | 原神游戏内角色展柜消息生成入口 257 | 258 | * ``param uid: str`` 查询用户 UID 259 | * ``param char: str = "全部"`` 查询角色 260 | - ``return: Union[bytes, str]`` 查询结果。一般返回图片字节,出错时返回错误信息字符串 261 | """ 262 | htmlBase = str(LOCAL_DIR.resolve()) 263 | 264 | # 获取面板数据 265 | data = await getAvatarData(uid, char) 266 | if data.get("error"): 267 | return data["error"] 268 | 269 | mode, tplVer = ("list", LIST_TPL_VER) if char == "全部" else ("panel", CHAR_TPL_VER) 270 | 271 | # 图标下载任务 272 | dlTasks = ( 273 | [ 274 | download(data["icon"], local=char), 275 | download(data["gachaAvatarImg"], local=char), 276 | *[ 277 | download(sData["icon"], local=char) 278 | for _, sData in data["skills"].items() 279 | ], 280 | *[download(conData["icon"], local=char) for conData in data["consts"]], 281 | download(data["weapon"]["icon"], local="weapon"), 282 | *[ 283 | download(relicData["icon"], local="artifacts") 284 | for relicData in data["relics"] 285 | ], 286 | ] 287 | if mode == "panel" 288 | else [download(role["icon"], local=role["name"]) for role in data["avatars"]] 289 | ) 290 | await asyncio.gather(*dlTasks) 291 | dlTasks.clear() 292 | 293 | # 如果渲染角色面板,额外根据需要精简面板数据(缓存中仍保留全部数据) 294 | if mode == "panel": 295 | data["fightProp"] = await simplFightProp( 296 | data["fightProp"], data["baseProp"], char, data["element"] 297 | ) 298 | 299 | # 渲染截图 300 | return await template_to_pic( 301 | template_path=htmlBase, 302 | template_name=f"{mode}-{tplVer}.html", 303 | templates={"css": tplVer, "uid": uid, "data": data}, 304 | pages={ 305 | "viewport": {"width": 600, "height": 300}, 306 | "base_url": f"file://{htmlBase}", 307 | }, 308 | wait=2, 309 | type="jpeg", 310 | quality=100, 311 | device_scale_factor=SCALE_FACTOR, 312 | ) 313 | 314 | 315 | async def getTeam( 316 | uid: str, chars: List[str] = [], showDetail: bool = False 317 | ) -> Union[bytes, str]: 318 | """ 319 | 队伍伤害消息生成入口 320 | 321 | * ``param uid: str`` 查询用户 UID 322 | * ``param chars: List[str] = []`` 查询角色,为空默认数据中前四个 323 | * ``param showDetail: bool = False`` 查询结果是否展示伤害过程。默认不展示 324 | - ``return: Union[bytes, str]`` 查询结果。一般返回图片字节,出错时返回错误信息字符串 325 | """ 326 | # 获取面板数据 327 | data = await getAvatarData(uid, "全部") 328 | if data.get("error"): 329 | return data["error"] 330 | 331 | if chars: 332 | extract = [a for a in data["avatars"] if a["name"] in chars] 333 | if len(extract) != len(chars): 334 | gotThis = [a["name"] for a in extract] 335 | return "玩家 {} 的最新数据中未发现{}!".format( 336 | uid, "、".join(c for c in chars if c not in gotThis) 337 | ) 338 | elif len(data["avatars"]) >= 4: 339 | extract = data["avatars"][:4] 340 | logger.info( 341 | "UID{} 未指定队伍,自动选择面板中前 4 位进行计算:{} ...".format( 342 | uid, "、".join(a["name"] for a in extract) 343 | ) 344 | ) 345 | else: 346 | return f"玩家 {uid} 的面板数据甚至不足以组成一支队伍呢!" 347 | 348 | # 图片下载任务 349 | for tmp in extract: 350 | dlTasks = [ 351 | download(tmp["icon"], local=tmp["name"]), 352 | *[ 353 | download(sData["icon"], local=tmp["name"]) 354 | for _, sData in tmp["skills"].items() 355 | ], 356 | download(tmp["weapon"]["icon"], local="weapon"), 357 | *[ 358 | download( 359 | f"UI_RelicIcon_{relicData['icon'].split('_')[-2]}_4", 360 | local="artifacts", 361 | ) 362 | for relicData in tmp["relics"] 363 | ], 364 | ] 365 | await asyncio.gather(*dlTasks) 366 | dlTasks.clear() 367 | 368 | teyvatBody = await transToTeyvat(deepcopy(extract), uid) 369 | teyvatRaw = await queryDamageApi(teyvatBody, "team") 370 | if teyvatRaw.get("code", "x") != 200 or not teyvatRaw.get("result"): 371 | logger.error( 372 | f"UID{uid} 的 {len(extract)} 位角色队伍伤害计算请求失败!" f"\n>>>> [提瓦特返回] {teyvatRaw}" 373 | ) 374 | return f"玩家 {uid} 队伍伤害计算失败,接口可能发生变动!" if teyvatRaw else "啊哦,队伍伤害计算小程序状态异常!" 375 | try: 376 | data = await simplTeamDamageRes( 377 | teyvatRaw["result"], {a["name"]: a for a in extract} 378 | ) 379 | except Exception as e: 380 | logger.opt(exception=e).error("队伍伤害数据解析出错") 381 | return f"[{e.__class__.__name__}] 队伍伤害数据解析出错咯" 382 | 383 | htmlBase = str(LOCAL_DIR.resolve()) 384 | return await template_to_pic( 385 | template_path=htmlBase, 386 | template_name=f"team-{TEAM_TPL_VER}.html", 387 | templates={"css": TEAM_TPL_VER, "data": data, "detail": showDetail}, 388 | pages={ 389 | "viewport": {"width": 600, "height": 300}, 390 | "base_url": f"file://{htmlBase}", 391 | }, 392 | wait=2, 393 | type="jpeg", 394 | quality=100, 395 | device_scale_factor=SCALE_FACTOR, 396 | ) 397 | -------------------------------------------------------------------------------- /nonebot_plugin_gspanel/data_updater.py: -------------------------------------------------------------------------------- 1 | """ 2 | 迁移缓存的面板数据,将在未来某个版本删除 3 | """ 4 | 5 | import json 6 | import asyncio 7 | from copy import deepcopy 8 | 9 | from nonebot.log import logger 10 | 11 | from .__utils__ import LOCAL_DIR 12 | from .data_source import queryDamageApi 13 | from .data_convert import transFromEnka, transToTeyvat, simplDamageRes 14 | 15 | 16 | async def updateCache() -> None: 17 | for f in (LOCAL_DIR / "cache").iterdir(): 18 | cache = json.loads(f.read_text(encoding="UTF-8")) 19 | if f.name.replace(".json", "").isdigit(): 20 | # 已经迁移的文件中部分数据格式升级 21 | uid, wait4Dmg = f.name.replace(".json", ""), {} 22 | for aIdx, a in enumerate(cache["avatars"]): 23 | cache["avatars"][aIdx]["level"] = int(a["level"]) 24 | if not a["damage"]: 25 | wait4Dmg[str(aIdx)] = a 26 | else: 27 | # 暴击伤害移动至期望伤害 28 | for dIdx, d in enumerate(a["damage"].get("data", [])): 29 | if str(d[1]).isdigit() and d[2] == "-": 30 | cache["avatars"][aIdx]["damage"]["data"][dIdx] = [ 31 | d[0], 32 | d[2], 33 | d[1], 34 | ] 35 | if wait4Dmg: 36 | # 补充角色伤害数据 37 | logger.info( 38 | "正在为 UID{} 的 {} 重新请求伤害计算接口".format( 39 | uid, "/".join(a["name"] for _, a in wait4Dmg.items()) 40 | ) 41 | ) 42 | teyvatBody = await transToTeyvat( 43 | deepcopy([a for _, a in wait4Dmg.items()]), uid 44 | ) 45 | teyvatRaw = await queryDamageApi(teyvatBody) 46 | if teyvatRaw.get("code", "x") != 200 or len(wait4Dmg) != len( 47 | teyvatRaw.get("result", []) 48 | ): 49 | logger.error( 50 | f"UID{uid} 的 {len(wait4Dmg)} 位角色伤害计算请求失败!" 51 | f"\n>>>> [提瓦特返回] {teyvatRaw}" 52 | ) 53 | for dmgIdx, dmgData in enumerate(teyvatRaw.get("result", [])): 54 | aRealIdx = int(list(wait4Dmg.keys())[dmgIdx]) 55 | cache["avatars"][aRealIdx]["damage"] = await simplDamageRes(dmgData) 56 | f.write_text( 57 | json.dumps(cache, ensure_ascii=False, indent=2), encoding="utf-8" 58 | ) 59 | continue 60 | # 旧版缓存文件,内容为 Enka.Network 返回 61 | uid = f.name.replace("__data.json", "") 62 | now, newData = cache["time"], [] 63 | avatarInfoList = cache.get("avatarInfoList", []) 64 | if not avatarInfoList: 65 | logger.error(f"UID{uid} 没有角色数据,清除旧版缓存") 66 | f.unlink(missing_ok=True) 67 | continue 68 | for avatarData in avatarInfoList: 69 | if avatarData["avatarId"] in [10000005, 10000007]: 70 | logger.info(f"UID{uid} 面板中含有旅行者,跳过暂未支持的角色!") 71 | continue 72 | newData.append(await transFromEnka(avatarData, now)) 73 | # 补充角色伤害数据 74 | teyvatBody = await transToTeyvat(deepcopy(newData), uid) 75 | teyvatRaw = await queryDamageApi(teyvatBody) 76 | if teyvatRaw.get("code", "x") != 200 or len(newData) != len( 77 | teyvatRaw.get("result", []) 78 | ): 79 | logger.error( 80 | f"UID{uid} 的 {len(newData)} 位角色伤害计算请求失败!\n>>>> [提瓦特返回] {teyvatRaw}" 81 | ) 82 | else: 83 | for tvtIdx, tvtDmg in enumerate(teyvatRaw["result"]): 84 | newData[tvtIdx]["damage"] = await simplDamageRes(tvtDmg) 85 | newCache = {"avatars": newData, "next": now + 120} 86 | (LOCAL_DIR / "cache" / f"{uid}.json").write_text( 87 | json.dumps(newCache, ensure_ascii=False, indent=2), encoding="utf-8" 88 | ) 89 | f.unlink(missing_ok=True) 90 | logger.info(f"UID{uid} 的角色面板缓存已迁移完毕!") 91 | await asyncio.sleep(2) 92 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "nonebot-plugin-gspanel" 3 | version = "0.2.25" 4 | description = "Genshin player cards plugin for NoneBot2" 5 | authors = ["monsterxcn "] 6 | documentation = "https://github.com/monsterxcn/nonebot-plugin-gspanel#readme" 7 | license = "MIT" 8 | homepage = "https://github.com/monsterxcn/nonebot-plugin-gspanel" 9 | readme = "README.md" 10 | keywords = ["nonebot", "nonebot2", "genshin", "panel", "card"] 11 | 12 | [tool.poetry.dependencies] 13 | python = ">=3.8.1,<4.0" 14 | nonebot2 = ">=2.0.0b3" 15 | nonebot-adapter-onebot = ">=2.0.0b1" 16 | httpx = ">=0.20.0, <1.0.0" 17 | nonebot-plugin-htmlrender = ">=0.2.1" 18 | playwright = ">=1.25.0" # chrome compatibility of `:has()` 19 | attrs = ">=20.1.0" # attrs 20.1.0 adds `@frozen` 20 | 21 | [tool.poetry.group.dev.dependencies] 22 | black = "^23.1.0" 23 | flake8 = "^6.0.0" 24 | flake8-pyproject = "^1.2.2" 25 | isort = "^5.12.0" 26 | pre-commit = "^3.0.4" 27 | pycln = "^2.1.3" 28 | pyupgrade = "^3.3.1" 29 | 30 | [tool.black] 31 | line-length = 88 32 | target-version = ["py38", "py39", "py310", "py311"] 33 | include = '\.pyi?$' 34 | extend-exclude = ''' 35 | ''' 36 | 37 | [tool.flake8] 38 | exclude = [ 39 | ".git", 40 | "__pycache__", 41 | "build", 42 | "dist", 43 | ".venv", 44 | ] 45 | max-line-length = 88 46 | # W292: No newline at end of file 47 | # W391: Blank line at end of file 48 | # W503: Line break occurred before a binary operator 49 | # E203: Whitespace before ':' 50 | ignore = ["W292", "W391", "W503", "E203"] 51 | # F401: Module imported but unused 52 | per-file-ignores = [ 53 | "__init__.py:F401", 54 | ] 55 | 56 | [tool.isort] 57 | profile = "black" 58 | line_length = 88 59 | length_sort = true 60 | skip_gitignore = true 61 | force_sort_within_sections = true 62 | extra_standard_library = ["typing_extensions"] 63 | 64 | [tool.pycln] 65 | path = "." 66 | all = false 67 | 68 | [tool.pyright] 69 | reportShadowedImports = false 70 | pythonVersion = "3.8" 71 | pythonPlatform = "All" 72 | executionEnvironments = [ 73 | { root = "./tests", extraPaths = ["./"] }, 74 | { root = "./" }, 75 | ] 76 | 77 | [build-system] 78 | requires = ["poetry-core>=1.0.0"] 79 | build-backend = "poetry.core.masonry.api" 80 | --------------------------------------------------------------------------------