├── .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 |
9 |
10 |
11 | 
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | |  |  |  |
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 |
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 |
31 |
32 | {% endfor %}
33 |
34 |
35 | {% for pKey, prop in data["fightProp"].items() %}
36 | -
37 | {% if prop["weight"] %}
38 | {{ prop["weight"] }}
39 | {% endif %}
40 |
{{ pKey }}
41 | {{ prop["value"] }}
42 | {% if prop.get('detail', '') %}
43 |
{{ prop["detail"][0] }}
{{ prop["detail"][1] }}
44 | {% endif %}
45 |
46 |
47 | {% endfor %}
48 |
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 |
107 | - {{ data["damage"]["level"] }}
108 | -
109 |
110 |
111 |
112 |
113 |
114 | {% for dmg in data["damage"]["data"] %}
115 | -
116 |
{{ dmg[0] }}
117 | {{ dmg[1] }}
118 | {{ dmg[2] }}
119 |
120 | {% endfor %}
121 |
122 | {% if data["damage"]["buff"] %}
123 |
124 |
125 | {% for buff in data["damage"]["buff"] %}
126 | -
127 |
{{ buff[0] }}
128 | {{ buff[1] }}
129 |
130 | {% endfor %}
131 |
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 |
69 |
70 | {% for buff in data["buffs"] %}
71 | -
72 |
{{ buff[0] }}
73 | {{ buff[1] }}
74 | {{ buff[2] }}
75 |
76 | {% endfor %}
77 |
78 | {% if detail %}
79 |
80 |
81 | -
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | {% for damage in data["damages"] %}
90 | -
91 |
{{ damage[0] }}
92 | {{ damage[1] }}
93 | {{ damage[2] }}
94 | {{ damage[3] }}
95 | {{ damage[4] }}
96 |
97 | {% endfor %}
98 |
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 |
--------------------------------------------------------------------------------