diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 4417c40..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 -updates: - - package-ecosystem: pip - directory: "/" - schedule: - interval: daily - time: "18:00" - timezone: Asia/Shanghai - open-pull-requests-limit: 10 - allow: - - dependency-type: "direct" diff --git a/core/dependence/assets.py b/core/dependence/assets.py index ab8fcfe..90bc299 100644 --- a/core/dependence/assets.py +++ b/core/dependence/assets.py @@ -19,6 +19,8 @@ ASSETS_PATH.mkdir(exist_ok=True, parents=True) DATA_MAP = { "avatar": WikiModel.BASE_URL + "avatar_icons.json", "light_cone": WikiModel.BASE_URL + "light_cone_icons.json", + "avatar_eidolon": WikiModel.BASE_URL + "avatar_eidolon_icons.json", + "avatar_skill": WikiModel.BASE_URL + "skill/info.json", } @@ -75,17 +77,28 @@ class _AvatarAssets(_AssetsService): async def initialize(self): logger.info("正在初始化角色素材图标") html = await self.client.get(DATA_MAP["avatar"]) + eidolons = await self.client.get(DATA_MAP["avatar_eidolon"]) + eidolons_data = eidolons.json() + skills = await self.client.get(DATA_MAP["avatar_skill"]) + skills_data = skills.json() self.data = [AvatarIcon(**data) for data in html.json()] self.name_map = {icon.name: icon for icon in self.data} self.id_map = {icon.id: icon for icon in self.data} tasks = [] for icon in self.data: + eidolons_s_data = eidolons_data.get(str(icon.id), []) + skills_s_data = [f"{i}.png" for i in skills_data if i.startswith(str(icon.id) + "_")] base_path = self.path / f"{icon.id}" base_path.mkdir(exist_ok=True, parents=True) gacha_path = base_path / "gacha.webp" icon_path = base_path / "icon.webp" normal_path = base_path / "normal.webp" square_path = base_path / "square.png" + eidolons_paths = [(base_path / f"eidolon_{eidolon_id}.webp") for eidolon_id in range(1, 7)] + skills_paths = [] + for i in skills_s_data: + temp_end = "_".join(i.split("_")[1:]) + skills_paths.append(base_path / f"skill_{temp_end}") if not gacha_path.exists(): tasks.append(self._download(icon.gacha, gacha_path)) if not icon_path.exists(): @@ -94,6 +107,12 @@ class _AvatarAssets(_AssetsService): tasks.append(self._download(icon.normal, normal_path)) if not square_path.exists() and icon.square: tasks.append(self._download(icon.square, square_path)) + for index, eidolon in enumerate(eidolons_paths): + if not eidolon.exists(): + tasks.append(self._download(eidolons_s_data[index], eidolon)) + for index, skill in enumerate(skills_paths): + if not skill.exists(): + tasks.append(self._download(WikiModel.BASE_URL + "skill/" + skills_s_data[index], skill)) if len(tasks) >= 100: await asyncio.gather(*tasks) tasks = [] @@ -145,6 +164,46 @@ class _AvatarAssets(_AssetsService): raise AssetsCouldNotFound("角色素材图标不存在", target) return path + def eidolons(self, target: StrOrInt, second_target: StrOrInt = None) -> List[Path]: + """星魂""" + icon = self.get_target(target, second_target) + return [self.get_path(icon, f"eidolon_{i}") for i in range(1, 7)] + + def skill_basic_atk(self, target: StrOrInt, second_target: StrOrInt = None) -> Path: + """普攻 001""" + icon = self.get_target(target, second_target) + return self.get_path(icon, "skill_basic_atk", "png") + + def skill_skill(self, target: StrOrInt, second_target: StrOrInt = None) -> Path: + """战技 002""" + icon = self.get_target(target, second_target) + return self.get_path(icon, "skill_skill", "png") + + def skill_ultimate(self, target: StrOrInt, second_target: StrOrInt = None) -> Path: + """终结技 003""" + icon = self.get_target(target, second_target) + return self.get_path(icon, "skill_ultimate", "png") + + def skill_talent(self, target: StrOrInt, second_target: StrOrInt = None) -> Path: + """天赋 004""" + icon = self.get_target(target, second_target) + return self.get_path(icon, "skill_talent", "png") + + def skill_technique(self, target: StrOrInt, second_target: StrOrInt = None) -> Path: + """秘技 007""" + icon = self.get_target(target, second_target) + return self.get_path(icon, "skill_technique", "png") + + def skills(self, target: StrOrInt, second_target: StrOrInt = None) -> List[Path]: + icon = self.get_target(target, second_target) + return [ + self.get_path(icon, "skill_basic_atk", "png"), + self.get_path(icon, "skill_skill", "png"), + self.get_path(icon, "skill_ultimate", "png"), + self.get_path(icon, "skill_talent", "png"), + self.get_path(icon, "skill_technique", "png"), + ] + class _LightConeAssets(_AssetsService): path: Path diff --git a/metadata/shortname.py b/metadata/shortname.py index 26dc61f..7cdedfc 100644 --- a/metadata/shortname.py +++ b/metadata/shortname.py @@ -3,109 +3,120 @@ from __future__ import annotations import functools from typing import List -from metadata.genshin import WEAPON_DATA - -__all__ = ["roles", "light_cones", "roleToId", "roleToName", "lightConeToName", "lightConeToId", "not_real_roles", "roleToTag"] +__all__ = [ + "roles", + "light_cones", + "roleToId", + "roleToName", + "idToRole", + "lightConeToName", + "lightConeToId", + "not_real_roles", + "roleToTag", +] # noinspection SpellCheckingInspection roles = { - 1001: ['三月七'], - 1002: ['丹恒'], - 1003: ['姬子'], - 1004: ['瓦尔特'], - 1005: ['卡芙卡'], - 1006: ['银狼'], - 1008: ['阿兰'], - 1009: ['艾丝妲'], - 1013: ['黑塔'], - 1101: ['布洛妮娅'], - 1102: ['希儿'], - 1103: ['希露瓦'], - 1104: ['杰帕德'], - 1105: ['娜塔莎'], - 1106: ['佩拉'], - 1107: ['克拉拉'], - 1108: ['桑博'], - 1109: ['虎克'], - 1201: ['青雀'], - 1202: ['停云'], - 1203: ['罗刹'], - 1204: ['景元'], - 1206: ['素裳'], - 1209: ['彦卿'], - 1211: ['白露'], - 8004: ['开拓者'], + 8001: ["开拓者"], + 8002: ["开拓者"], + 8003: ["开拓者"], + 8004: ["开拓者"], + 1001: ["三月七"], + 1002: ["丹恒"], + 1003: ["姬子"], + 1004: ["瓦尔特"], + 1005: ["卡芙卡"], + 1006: ["银狼"], + 1008: ["阿兰"], + 1009: ["艾丝妲"], + 1013: ["黑塔"], + 1101: ["布洛妮娅"], + 1102: ["希儿"], + 1103: ["希露瓦"], + 1104: ["杰帕德"], + 1105: ["娜塔莎"], + 1106: ["佩拉"], + 1107: ["克拉拉"], + 1108: ["桑博"], + 1109: ["虎克"], + 1201: ["青雀"], + 1202: ["停云"], + 1203: ["罗刹"], + 1204: ["景元"], + 1206: ["素裳"], + 1209: ["彦卿"], + 1211: ["白露"], } not_real_roles = [] light_cones = { - 20000: ['锋镝'], - 20001: ['物穰'], - 20002: ['天倾'], - 20003: ['琥珀'], - 20004: ['幽邃'], - 20005: ['齐颂'], - 20006: ['智库'], - 20007: ['离弦'], - 20008: ['嘉果'], - 20009: ['乐圮'], - 20010: ['戍御'], - 20011: ['渊环'], - 20012: ['轮契'], - 20013: ['灵钥'], - 20014: ['相抗'], - 20015: ['蕃息'], - 20016: ['俱殁'], - 20017: ['开疆'], - 20018: ['匿影'], - 20019: ['调和'], - 20020: ['睿见'], - 21000: ['一场术后对话'], - 21001: ['晚安与睡颜'], - 21002: ['余生的第一天'], - 21003: ['唯有沉默'], - 21004: ['记忆中的模样'], - 21005: ['鼹鼠党欢迎你'], - 21006: ['「我」的诞生'], - 21007: ['同一种心情'], - 21008: ['猎物的视线'], - 21009: ['朗道的选择'], - 21010: ['论剑'], - 21011: ['与行星相会'], - 21012: ['秘密誓心'], - 21013: ['别让世界静下来'], - 21014: ['此时恰好'], - 21015: ['决心如汗珠般闪耀'], - 21016: ['宇宙市场趋势'], - 21017: ['点个关注吧!'], - 21018: ['舞!舞!舞!'], - 21019: ['在蓝天下'], - 21020: ['天才们的休憩'], - 21021: ['等价交换'], - 21022: ['延长记号'], - 21023: ['我们是地火'], - 21024: ['春水初生'], - 21025: ['过往未来'], - 21026: ['汪!散步时间!'], - 21027: ['早餐的仪式感'], - 21028: ['暖夜不会漫长'], - 21029: ['后会有期'], - 21030: ['这就是我啦!'], - 21031: ['重返幽冥'], - 21032: ['镂月裁云之意'], - 21033: ['无处可逃'], - 21034: ['今日亦是和平的一日'], - 23000: ['银河铁道之夜'], - 23001: ['于夜色中'], - 23002: ['无可取代的东西'], - 23003: ['但战斗还未结束'], - 23004: ['以世界之名'], - 23005: ['制胜的瞬间'], - 23010: ['拂晓之前'], - 23012: ['如泥酣眠'], - 23013: ['时节不居'], - 24000: ['记一位星神的陨落'], - 24001: ['星海巡航'], - 24002: ['记忆的质料'] + 20000: ["锋镝"], + 20001: ["物穰"], + 20002: ["天倾"], + 20003: ["琥珀"], + 20004: ["幽邃"], + 20005: ["齐颂"], + 20006: ["智库"], + 20007: ["离弦"], + 20008: ["嘉果"], + 20009: ["乐圮"], + 20010: ["戍御"], + 20011: ["渊环"], + 20012: ["轮契"], + 20013: ["灵钥"], + 20014: ["相抗"], + 20015: ["蕃息"], + 20016: ["俱殁"], + 20017: ["开疆"], + 20018: ["匿影"], + 20019: ["调和"], + 20020: ["睿见"], + 21000: ["一场术后对话"], + 21001: ["晚安与睡颜"], + 21002: ["余生的第一天"], + 21003: ["唯有沉默"], + 21004: ["记忆中的模样"], + 21005: ["鼹鼠党欢迎你"], + 21006: ["「我」的诞生"], + 21007: ["同一种心情"], + 21008: ["猎物的视线"], + 21009: ["朗道的选择"], + 21010: ["论剑"], + 21011: ["与行星相会"], + 21012: ["秘密誓心"], + 21013: ["别让世界静下来"], + 21014: ["此时恰好"], + 21015: ["决心如汗珠般闪耀"], + 21016: ["宇宙市场趋势"], + 21017: ["点个关注吧!"], + 21018: ["舞!舞!舞!"], + 21019: ["在蓝天下"], + 21020: ["天才们的休憩"], + 21021: ["等价交换"], + 21022: ["延长记号"], + 21023: ["我们是地火"], + 21024: ["春水初生"], + 21025: ["过往未来"], + 21026: ["汪!散步时间!"], + 21027: ["早餐的仪式感"], + 21028: ["暖夜不会漫长"], + 21029: ["后会有期"], + 21030: ["这就是我啦!"], + 21031: ["重返幽冥"], + 21032: ["镂月裁云之意"], + 21033: ["无处可逃"], + 21034: ["今日亦是和平的一日"], + 23000: ["银河铁道之夜"], + 23001: ["于夜色中"], + 23002: ["无可取代的东西"], + 23003: ["但战斗还未结束"], + 23004: ["以世界之名"], + 23005: ["制胜的瞬间"], + 23010: ["拂晓之前"], + 23012: ["如泥酣眠"], + 23013: ["时节不居"], + 24000: ["记一位星神的陨落"], + 24001: ["星海巡航"], + 24002: ["记忆的质料"], } @@ -125,6 +136,13 @@ def roleToId(name: str) -> int | None: return next((key for key, value in roles.items() for n in value if n == name), None) +# noinspection PyPep8Naming +@functools.lru_cache() +def idToRole(aid: int) -> str | None: + """获取角色名""" + return roles.get(aid, [None])[0] + + # noinspection PyPep8Naming @functools.lru_cache() def lightConeToName(shortname: str) -> str: diff --git a/modules/apihelper/client/components/calendar.py b/modules/apihelper/client/components/calendar.py index 00d527a..7f0521f 100644 --- a/modules/apihelper/client/components/calendar.py +++ b/modules/apihelper/client/components/calendar.py @@ -3,8 +3,6 @@ from datetime import datetime, timedelta from typing import List, Tuple, Optional, Dict, Union, TYPE_CHECKING from httpx import AsyncClient - -from metadata.genshin import AVATAR_DATA from metadata.shortname import roleToId from modules.apihelper.client.components.remote import Remote from modules.apihelper.models.genshin.calendar import Date, FinalAct, ActEnum, ActDetail, ActTime, BirthChar diff --git a/modules/apihelper/client/components/player_cards.py b/modules/apihelper/client/components/player_cards.py new file mode 100644 index 0000000..5933bca --- /dev/null +++ b/modules/apihelper/client/components/player_cards.py @@ -0,0 +1,162 @@ +from typing import List, Optional, Union, Dict + +import ujson +from httpx import AsyncClient, TimeoutException +from pydantic import BaseModel + +from modules.playercards.fight_prop import EquipmentsStats +from modules.wiki.base import WikiModel +from modules.wiki.models.relic_affix import RelicAffixAll +from utils.enkanetwork import RedisCache +from modules.playercards.file import PlayerCardsFile + + +class Behavior(BaseModel): + BehaviorID: int + Level: int + + +class Equipment(BaseModel): + ID: Optional[int] = 0 + Level: Optional[int] = 0 + Promotion: Optional[int] = 3 + """星级""" + Rank: Optional[int] = 0 + """叠影""" + + +class SubAffix(BaseModel): + Cnt: Optional[int] = 1 + Step: Optional[int] = 0 + SubAffixID: int + + +class Relic(BaseModel): + ID: int + Level: Optional[int] = 0 + MainAffixID: int + RelicSubAffix: Optional[List[SubAffix]] + Type: int + + +class Avatar(BaseModel): + AvatarID: int + BehaviorList: List[Behavior] + EquipmentID: Optional[Equipment] + Level: int + Promotion: Optional[int] = 4 + Rank: Optional[int] = 0 + RelicList: Optional[List[Relic]] + + +class ChallengeData(BaseModel): + MazeGroupID: Optional[int] + MazeGroupIndex: Optional[int] + PreMazeGroupIndex: Optional[int] + + +class PlayerSpaceInfo(BaseModel): + AchievementCount: Optional[int] = 0 + AvatarCount: Optional[int] = 0 + ChallengeData: ChallengeData + LightConeCount: Optional[int] = 0 + PassAreaProgress: Optional[int] = 0 + + +class PlayerInfo(BaseModel): + Birthday: Optional[int] + CurFriendCount: Optional[int] + AvatarList: List[Avatar] + HeadIconID: Optional[int] + IsDisplayAvatarList: bool + Level: int + NickName: str + PlayerSpaceInfo: PlayerSpaceInfo + Signature: Optional[str] + UID: int + WorldLevel: Optional[int] + + +class PlayerCardsError(Exception): + def __init__(self, msg): + self.msg = msg + + +class PlayerCards: + url = "https://mhy.fuckmys.tk/sr_info/" + prop_url = f"{WikiModel.BASE_URL}relic_config.json" + + def __init__(self, redis): + self.cache = RedisCache(redis.client, key="plugin:player_cards:fake_enka_network", ex=60) + self.client = AsyncClient() + self.player_cards_file = PlayerCardsFile() + self.init = False + self.relic_datas_map: Dict[int, RelicAffixAll] = {} + + async def async_init(self): + if self.init: + return + self.relic_datas_map.clear() + req = await self.client.get(self.prop_url) + data = req.json() + for i in data: + self.relic_datas_map[i["id"]] = RelicAffixAll(**i) + self.init = True + + async def update_data(self, uid: str) -> Union[PlayerInfo, str]: + try: + data = await self.cache.get(uid) + if data is not None: + return PlayerInfo.parse_obj(data) + user = await self.client.get(self.url + uid, timeout=15) + if user.status_code != 200: + raise PlayerCardsError(f"请求异常,错误代码 {user.status_code}") + data = ujson.loads(user.text) + error_code = data.get("ErrCode", 0) + if error_code: + raise PlayerCardsError(f"请求异常,错误代码 {error_code}") + data = data.get("PlayerDetailInfo", {}) + data = await self.player_cards_file.merge_info(uid, data) + await self.cache.set(uid, data) + return PlayerInfo.parse_obj(data) + except TimeoutException: + error = "服务请求超时,请稍后重试" + except PlayerCardsError as e: + error = e.msg + return error + + def get_affix_by_id(self, cid: int) -> RelicAffixAll: + return self.relic_datas_map.get(cid) + + def get_set_by_id(self, cid: int) -> int: + if affix := self.get_affix_by_id(cid): + return affix.set_id + return 101 + + def get_affix(self, relic: Relic, main: bool = True, sub: bool = True) -> List[EquipmentsStats]: + affix = self.get_affix_by_id(relic.ID) + if not affix: + return [] + main_affix = affix.main_affix[str(relic.MainAffixID)] + datas = ( + [ + EquipmentsStats( + prop_id=main_affix.property, + prop_value=main_affix.get_value(relic.Level), + ) + ] + if main + else [] + ) + if not sub: + return datas + if relic.RelicSubAffix: + for sub_a in relic.RelicSubAffix: + sub_affix = affix.sub_affix[str(sub_a.SubAffixID)] + datas.append( + EquipmentsStats( + prop_id=sub_affix.property, + prop_value=sub_affix.get_value(sub_a.Step, sub_a.Cnt), + ) + ) + return datas diff --git a/modules/gacha_log/log.py b/modules/gacha_log/log.py index 409757c..9185ed2 100644 --- a/modules/gacha_log/log.py +++ b/modules/gacha_log/log.py @@ -205,7 +205,6 @@ class GachaLog: except GachaLogMixedProvider as e: raise GachaLogMixedProvider from e except Exception as exc: - breakpoint() raise GachaLogException from exc async def get_gacha_log_data(self, user_id: int, client: Client, authkey: str) -> int: diff --git a/modules/playercards/fight_prop.py b/modules/playercards/fight_prop.py index 7e971fa..dc6967d 100644 --- a/modules/playercards/fight_prop.py +++ b/modules/playercards/fight_prop.py @@ -1,68 +1,87 @@ -import enum +import functools + +from pydantic import BaseModel + +from modules.wiki.models.enums import RelicAffix + +relic_affix_map = { + RelicAffix.AttackAddedRatio: "攻击力百分比", + RelicAffix.AttackDelta: "攻击力", + RelicAffix.BreakDamageAddedRatioBase: "击破特攻", + RelicAffix.CriticalChanceBase: "暴击率百分比", + RelicAffix.CriticalDamageBase: "暴击伤害百分比", + RelicAffix.DefenceAddedRatio: "防御力百分比", + RelicAffix.DefenceDelta: "防御力", + RelicAffix.FireAddedRatio: "火属性伤害提高百分比", + RelicAffix.HPAddedRatio: "生命值百分比", + RelicAffix.HPDelta: "生命值", + RelicAffix.HealRatioBase: "治疗量加成百分比", + RelicAffix.IceAddedRatio: "冰属性伤害提高百分比", + RelicAffix.ImaginaryAddedRatio: "虚数属性伤害提高百分比", + RelicAffix.PhysicalAddedRatio: "物理属性伤害提高百分比", + RelicAffix.QuantumAddedRatio: "量子属性伤害提高百分比", + RelicAffix.SpeedDelta: "速度", + RelicAffix.SPRatioBase: "能量恢复效率百分比", + RelicAffix.StatusProbabilityBase: "效果命中百分比", + RelicAffix.StatusResistanceBase: "效果抵抗百分比", + RelicAffix.ThunderAddedRatio: "雷属性伤害提高百分比", + RelicAffix.WindAddedRatio: "风属性伤害提高百分比", +} +relic_affix_name_map = {v: k for k, v in relic_affix_map.items()} +relic_affix_score_map = { + RelicAffix.AttackAddedRatio: 1.0, + RelicAffix.AttackDelta: 0.5, + RelicAffix.BreakDamageAddedRatioBase: 1.0, + RelicAffix.CriticalChanceBase: 2.0, + RelicAffix.CriticalDamageBase: 2.0, + RelicAffix.DefenceAddedRatio: 1.0, + RelicAffix.DefenceDelta: 0.5, + RelicAffix.FireAddedRatio: 1.0, + RelicAffix.HPAddedRatio: 1.0, + RelicAffix.HPDelta: 0.5, + RelicAffix.HealRatioBase: 1.0, + RelicAffix.IceAddedRatio: 1.0, + RelicAffix.ImaginaryAddedRatio: 1.0, + RelicAffix.PhysicalAddedRatio: 1.0, + RelicAffix.QuantumAddedRatio: 1.0, + RelicAffix.SpeedDelta: 1.0, + RelicAffix.SPRatioBase: 1.0, + RelicAffix.StatusProbabilityBase: 1.0, + RelicAffix.StatusResistanceBase: 1.0, + RelicAffix.ThunderAddedRatio: 1.0, + RelicAffix.WindAddedRatio: 1.0, +} -class FightProp(enum.Enum): - BASE_HP = "基础血量" - FIGHT_PROP_BASE_ATTACK = "基础攻击力" - FIGHT_PROP_BASE_DEFENSE = "基础防御力" - FIGHT_PROP_BASE_HP = "基础血量" - FIGHT_PROP_ATTACK = "攻击力" - FIGHT_PROP_ATTACK_PERCENT = "攻击力百分比" - FIGHT_PROP_HP = "生命值" - FIGHT_PROP_HP_PERCENT = "生命值百分比" - FIGHT_PROP_DEFENSE = "防御力" - FIGHT_PROP_DEFENSE_PERCENT = "防御力百分比" - FIGHT_PROP_ELEMENT_MASTERY = "元素精通" - FIGHT_PROP_CRITICAL = "暴击率" - FIGHT_PROP_CRITICAL_HURT = "暴击伤害" - FIGHT_PROP_CHARGE_EFFICIENCY = "元素充能效率" - FIGHT_PROP_FIRE_SUB_HURT = "火元素抗性" - FIGHT_PROP_ELEC_SUB_HURT = "雷元素抗性" - FIGHT_PROP_ICE_SUB_HURT = "冰元素抗性" - FIGHT_PROP_WATER_SUB_HURT = "水元素抗性" - FIGHT_PROP_WIND_SUB_HURT = "风元素抗性" - FIGHT_PROP_ROCK_SUB_HURT = "岩元素抗性" - FIGHT_PROP_GRASS_SUB_HURT = "草元素抗性" - FIGHT_PROP_FIRE_ADD_HURT = "火元素伤害加成" - FIGHT_PROP_ELEC_ADD_HURT = "雷元素伤害加成" - FIGHT_PROP_ICE_ADD_HURT = "冰元素伤害加成" - FIGHT_PROP_WATER_ADD_HURT = "水元素伤害加成" - FIGHT_PROP_WIND_ADD_HURT = "风元素伤害加成" - FIGHT_PROP_ROCK_ADD_HURT = "岩元素伤害加成" - FIGHT_PROP_GRASS_ADD_HURT = "草元素伤害加成" - FIGHT_PROP_PHYSICAL_ADD_HURT = "物理伤害加成" - FIGHT_PROP_HEAL_ADD = "治疗加成" +# noinspection PyPep8Naming +@functools.lru_cache() +def FightProp(prop: RelicAffix, percent: bool = True) -> str: + name = relic_affix_map.get(prop) + return name if percent else name.replace("百分比", "") -class FightPropScore(enum.Enum): - _value_: float - value: float - FIGHT_PROP_BASE_ATTACK = 1 - FIGHT_PROP_BASE_DEFENSE = 1 - FIGHT_PROP_BASE_HP = 1 - FIGHT_PROP_ATTACK = 662 / 3110 # 攻击力 - FIGHT_PROP_ATTACK_PERCENT = 4 / 3 # 攻击力百分比 - FIGHT_PROP_HP = 662 / 47800 # 生命 - FIGHT_PROP_HP_PERCENT = 4 / 3 # 生命百分比 - FIGHT_PROP_DEFENSE = 662 / 3890 # 防御力 - FIGHT_PROP_DEFENSE_PERCENT = 662 / 583 # 防御力百分比 - FIGHT_PROP_ELEMENT_MASTERY = 1 / 3 # 元素精通 - FIGHT_PROP_CRITICAL = 2 # 暴击率 - FIGHT_PROP_CRITICAL_HURT = 1 # 暴击伤害 - FIGHT_PROP_CHARGE_EFFICIENCY = 662 / 518 # 元素充能效率 - FIGHT_PROP_FIRE_SUB_HURT = 1 - FIGHT_PROP_ELEC_SUB_HURT = 1 - FIGHT_PROP_ICE_SUB_HURT = 1 - FIGHT_PROP_WATER_SUB_HURT = 1 - FIGHT_PROP_WIND_SUB_HURT = 1 - FIGHT_PROP_ROCK_SUB_HURT = 1 - FIGHT_PROP_GRASS_SUB_HURT = 1 - FIGHT_PROP_FIRE_ADD_HURT = 1 - FIGHT_PROP_ELEC_ADD_HURT = 1 - FIGHT_PROP_ICE_ADD_HURT = 1 - FIGHT_PROP_WATER_ADD_HURT = 1 - FIGHT_PROP_WIND_ADD_HURT = 1 - FIGHT_PROP_ROCK_ADD_HURT = 1 - FIGHT_PROP_GRASS_ADD_HURT = 1 - FIGHT_PROP_PHYSICAL_ADD_HURT = 1 - FIGHT_PROP_HEAL_ADD = 1 +# noinspection PyPep8Naming +@functools.lru_cache() +def nameToFightProp(name: str) -> RelicAffix: + return relic_affix_name_map.get(name) + + +# noinspection PyPep8Naming +@functools.lru_cache() +def FightPropScore(prop) -> float: + return relic_affix_score_map.get(prop) + + +class EquipmentsStats(BaseModel): + prop_id: RelicAffix + prop_value: float + + @property + def name(self) -> str: + return FightProp(self.prop_id, False) + + @property + def value(self) -> str: + return ( + str(round(self.prop_value, 1)) if self.prop_value > 1 else str(str(round(self.prop_value * 100.0, 1)) + "%") + ) diff --git a/modules/playercards/file.py b/modules/playercards/file.py index 51d8705..09c83c7 100644 --- a/modules/playercards/file.py +++ b/modules/playercards/file.py @@ -63,12 +63,25 @@ class PlayerCardsFile: async with self._lock: old_data = await self.load_history_info(uid) if old_data is None: - await self.save_json(self.get_file_path(uid), data) - return data - data["avatarInfoList"] = data.get("avatarInfoList", []) - characters = [i.get("avatarId", 0) for i in data["avatarInfoList"]] - for i in old_data.get("avatarInfoList", []): - if i.get("avatarId", 0) not in characters: - data["avatarInfoList"].append(i) + old_data = {} + avatars = [] + avatar_ids = [] + assist_avatar = data.get("AssistAvatar", None) + if assist_avatar: + avatars.append(assist_avatar) + avatar_ids.append(assist_avatar.get("AvatarID", 0)) + for avatar in data.get("DisplayAvatarList", []): + if avatar.get("AvatarID", 0) in avatar_ids: + continue + avatars.append(avatar) + avatar_ids.append(avatar.get("AvatarID", 0)) + data["AvatarList"] = avatars + if "AssistAvatar" in data: + del data["AssistAvatar"] + if "DisplayAvatarList" in data: + del data["DisplayAvatarList"] + for i in old_data.get("AvatarList", []): + if i.get("AvatarID", 0) not in avatar_ids: + data["AvatarList"].append(i) await self.save_json(self.get_file_path(uid), data) return data diff --git a/modules/playercards/helpers.py b/modules/playercards/helpers.py index c4c18c0..2324ba7 100644 --- a/modules/playercards/helpers.py +++ b/modules/playercards/helpers.py @@ -1,9 +1,9 @@ import os import ujson as json -from enkanetwork import EquipmentsStats -from modules.playercards.fight_prop import FightProp, FightPropScore +from modules.playercards.fight_prop import FightPropScore, EquipmentsStats, nameToFightProp +from modules.wiki.models.enums import RelicAffix _project_path = os.path.dirname(__file__) _fight_prop_rule_file = os.path.join(_project_path, "metadata", "FightPropRule.json") @@ -15,32 +15,23 @@ class ArtifactStatsTheory: def __init__(self, character_name: str): self.character_name = character_name fight_prop_rule_list = fight_prop_rule_data.get(self.character_name, []) - self.main_prop = [FightProp(fight_prop_rule) for fight_prop_rule in fight_prop_rule_list] + self.main_prop = [nameToFightProp(fight_prop_rule) for fight_prop_rule in fight_prop_rule_list] if not self.main_prop: self.main_prop = [ - FightProp.FIGHT_PROP_CRITICAL, - FightProp.FIGHT_PROP_CRITICAL_HURT, - FightProp.FIGHT_PROP_ATTACK_PERCENT, + RelicAffix.CriticalChanceBase, + RelicAffix.CriticalDamageBase, + RelicAffix.AttackAddedRatio, ] - # 修正要评分的数值词条 - if FightProp.FIGHT_PROP_ATTACK_PERCENT in self.main_prop and FightProp.FIGHT_PROP_ATTACK not in self.main_prop: - self.main_prop.append(FightProp.FIGHT_PROP_ATTACK) - if FightProp.FIGHT_PROP_HP_PERCENT in self.main_prop and FightProp.FIGHT_PROP_HP not in self.main_prop: - self.main_prop.append(FightProp.FIGHT_PROP_HP) - if ( - FightProp.FIGHT_PROP_DEFENSE_PERCENT in self.main_prop - and FightProp.FIGHT_PROP_DEFENSE not in self.main_prop - ): - self.main_prop.append(FightProp.FIGHT_PROP_DEFENSE) def theory(self, sub_stats: EquipmentsStats) -> float: - """圣遗物副词条评分 + """圣遗物词条评分 Args: sub_stats: 圣遗物对象 Returns: 返回得分 """ score: float = 0 - if sub_stats.prop_id in map(lambda x: x.name, self.main_prop): - score = float(FightPropScore[sub_stats.prop_id].value) * sub_stats.value + if sub_stats.prop_id in self.main_prop: + base_value = 100.0 if sub_stats.prop_value < 1 else 1.0 + score = float(FightPropScore(sub_stats.prop_id) * sub_stats.prop_value * base_value) return round(score, 1) diff --git a/modules/playercards/metadata/FightPropRule.json b/modules/playercards/metadata/FightPropRule.json index 6c56255..844f9f6 100644 --- a/modules/playercards/metadata/FightPropRule.json +++ b/modules/playercards/metadata/FightPropRule.json @@ -1,411 +1,125 @@ { - "旅行者": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率", - "元素精通" - ], - "安柏": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通" - ], - "凯亚": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "丽莎": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通" - ], - "芭芭拉": [ - "生命值百分比", - "元素充能效率", - "元素精通" - ], - "芭芭拉-核爆": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通" - ], - "雷泽": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "物理伤害加成" - ], - "香菱": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "北斗": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "行秋": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "凝光": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "菲谢尔": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通" - ], - "班尼特": [ - "生命值百分比", - "元素充能效率", - "治疗加成" - ], - "诺艾尔": [ - "攻击力百分比", - "防御力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "重云": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "砂糖": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "琴": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "物理伤害加成", - "元素充能效率", - "治疗加成" - ], - "迪卢克": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通" - ], - "七七": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "物理伤害加成", - "元素充能效率", - "治疗加成" - ], - "莫娜": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "刻晴": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "物理伤害加成" - ], - "温迪": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "可莉": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "迪奥娜": [ - "生命值百分比", - "元素充能效率", - "治疗加成" - ], - "达达利亚": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "辛焱": [ - "防御力百分比", - "攻击力百分比", - "暴击率", - "暴击伤害", - "物理伤害加成" - ], - "钟离": [ - "生命值百分比", - "攻击力百分比", - "暴击率", - "暴击伤害", - "物理伤害加成", - "元素充能效率" - ], - "钟离-安如磐石": [ - "生命值百分比", - "暴击率" - ], - "阿贝多": [ - "防御力百分比", - "暴击率", - "暴击伤害" - ], - "甘雨": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通" - ], - "甘雨-永冻": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "魈": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "胡桃": [ - "生命值百分比", - "暴击率", - "暴击伤害", - "元素精通" - ], - "罗莎莉亚": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "物理伤害加成", - "元素充能效率" - ], - "烟绯": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "优菈": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "物理伤害加成", - "元素充能效率" - ], - "枫原万叶": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "神里绫华": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "早柚": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率", - "治疗加成" - ], - "宵宫": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通" - ], - "埃洛伊": [ - "攻击力百分比", - "暴击率", - "暴击伤害" - ], - "九条裟罗": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "雷电将军": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率", - "元素精通" - ], - "珊瑚宫心海": [ - "生命值百分比", - "攻击力百分比", - "元素充能效率", - "治疗加成", - "元素精通" - ], - "托马": [ - "生命值百分比", - "暴击率", - "元素充能效率", - "元素精通" - ], - "五郎": [ - "防御力百分比", - "暴击率", - "元素充能效率" - ], - "荒泷一斗": [ - "防御力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "云堇": [ - "防御力百分比", - "暴击率", - "元素充能效率" - ], - "申鹤": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "八重神子": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "神里绫人": [ - "生命值百分比", - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "夜兰": [ - "生命值百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "久岐忍": [ - "生命值百分比", - "元素精通", - "治疗加成" - ], - "鹿野院平藏": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "提纳里": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "柯莱": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "赛诺": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "妮露": [ - "生命值百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "纳西妲": [ - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率", - "攻击力百分比" - ], - "流浪者": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "珐露珊": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素充能效率" - ], - "艾尔海森": [ - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ], - "瑶瑶": [ - "生命值百分比", - "元素充能效率" - ], - "迪希雅": [ - "生命值百分比", - "攻击力百分比", - "暴击率", - "暴击伤害", - "元素精通", - "元素充能效率" - ] + "丹恒": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "风属性伤害提高百分比" + ], + "彦卿": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "冰属性伤害提高百分比" + ], + "希儿": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "量子属性伤害提高百分比", + "速度" + ], + "姬子": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "火属性伤害提高百分比" + ], + "阿兰": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "雷属性伤害提高百分比" + ], + "黑塔": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "冰属性伤害提高百分比" + ], + "希露瓦": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "雷属性伤害提高百分比" + ], + "克拉拉": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "物理属性伤害提高百分比" + ], + "虎克": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "火属性伤害提高百分比" + ], + "青雀": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "量子属性伤害提高百分比" + ], + "素裳": [ + "攻击力百分比", + "暴击率百分比", + "暴击伤害百分比", + "物理属性伤害提高百分比" + ], + "杰帕德": [ + "防御力百分比", + "防御力", + "能量恢复效率百分比" + ], + "三月七": [ + "防御力百分比", + "防御力", + "能量恢复效率百分比" + ], + "瓦尔特": [ + "虚数属性伤害提高百分比", + "速度", + "能量恢复效率百分比", + "效果命中百分比" + ], + "桑博": [ + "速度", + "能量恢复效率百分比", + "效果命中百分比", + "风属性伤害提高百分比" + ], + "佩拉": [ + "冰属性伤害提高百分比", + "速度", + "能量恢复效率百分比", + "效果命中百分比" + ], + "停云": [ + "攻击力百分比", + "速度", + "能量恢复效率百分比", + "雷属性伤害提高百分比" + ], + "艾丝妲": [ + "攻击力百分比", + "火属性伤害提高百分比", + "速度", + "能量恢复效率百分比" + ], + "白露": [ + "生命值百分比", + "生命值", + "治疗量加成百分比" + ], + "娜塔莎": [ + "生命值百分比", + "生命值", + "治疗量加成百分比" + ], + "布洛妮娅": [ + "暴击伤害百分比", + "速度", + "能量恢复效率百分比", + "风属性伤害提高百分比" + ] } \ No newline at end of file diff --git a/modules/wiki/character.py b/modules/wiki/character.py index a5b2baf..53ccbc8 100644 --- a/modules/wiki/character.py +++ b/modules/wiki/character.py @@ -37,10 +37,10 @@ class Character(WikiModel): self.all_avatars_name[m.name] = m def get_by_id(self, cid: int) -> Optional[Avatar]: - return self.all_avatars_map.get(cid, None) + return self.all_avatars_map.get(cid) def get_by_name(self, name: str) -> Optional[Avatar]: - return self.all_avatars_name.get(name, None) + return self.all_avatars_name.get(name) def get_name_list(self) -> List[str]: return list(self.all_avatars_name.keys()) diff --git a/modules/wiki/light_cone.py b/modules/wiki/light_cone.py index fbf436f..09aac5f 100644 --- a/modules/wiki/light_cone.py +++ b/modules/wiki/light_cone.py @@ -37,10 +37,10 @@ class LightCone(WikiModel): self.all_light_cones_name[m.name] = m def get_by_id(self, cid: int) -> Optional[LightConeModel]: - return self.all_light_cones_map.get(cid, None) + return self.all_light_cones_map.get(cid) def get_by_name(self, name: str) -> Optional[LightConeModel]: - return self.all_light_cones_name.get(name, None) + return self.all_light_cones_name.get(name) def get_name_list(self) -> List[str]: return list(self.all_light_cones_name.keys()) diff --git a/modules/wiki/material.py b/modules/wiki/material.py index 35f4618..c137388 100644 --- a/modules/wiki/material.py +++ b/modules/wiki/material.py @@ -37,10 +37,10 @@ class Material(WikiModel): self.all_materials_name[m.name] = m def get_by_id(self, cid: int) -> Optional[MaterialModel]: - return self.all_materials_map.get(cid, None) + return self.all_materials_map.get(cid) def get_by_name(self, name: str) -> Optional[MaterialModel]: - return self.all_materials_name.get(name, None) + return self.all_materials_name.get(name) def get_name_list(self) -> List[str]: return list(self.all_materials_name.keys()) diff --git a/modules/wiki/models/enums.py b/modules/wiki/models/enums.py index 28ce262..46ba953 100644 --- a/modules/wiki/models/enums.py +++ b/modules/wiki/models/enums.py @@ -2,7 +2,8 @@ from enum import Enum class Quality(str, Enum): - """ 星级 """ + """星级""" + Five = "五星" Four = "四星" Three = "三星" @@ -11,7 +12,8 @@ class Quality(str, Enum): class Destiny(str, Enum): - """ 命途 """ + """命途""" + HuiMie = "毁灭" ZhiShi = "智识" XunLie = "巡猎" @@ -22,7 +24,8 @@ class Destiny(str, Enum): class Element(str, Enum): - """ 属性 """ + """属性""" + Physical = "物理" Pyro = "火" Anemo = "风" @@ -35,7 +38,8 @@ class Element(str, Enum): class MonsterType(str, Enum): - """ 怪物种类 """ + """怪物种类""" + Normal = "普通" Elite = "精英" Leader = "首领" @@ -43,7 +47,8 @@ class MonsterType(str, Enum): class Area(str, Enum): - """ 地区 """ + """地区""" + Herta = "空间站「黑塔」" YaLiLuo = "雅利洛-VI" LuoFu = "仙舟「罗浮」" @@ -51,7 +56,8 @@ class Area(str, Enum): class MaterialType(str, Enum): - """ 材料类型 """ + """材料类型""" + AvatarUpdate = "角色晋阶材料" XingJi = "行迹材料" LightConeUpdate = "光锥晋阶材料" @@ -66,7 +72,8 @@ class MaterialType(str, Enum): class PropType(str, Enum): - """ 遗器套装效果 """ + """遗器套装效果""" + HP = "基础-生命值" Defense = "基础-防御力" Attack = "基础-攻击力" @@ -82,3 +89,75 @@ class PropType(str, Enum): Heal = "其他-治疗加成" OtherCritical = "其他-效果命中" Charge = "其他-能量充能效率" + + +class RelicAffix(str, Enum): + AttackAddedRatio: str = "AttackAddedRatio" + """ 攻击力 百分比 """ + AttackDelta: str = "AttackDelta" + """ 攻击力 """ + BreakDamageAddedRatioBase: str = "BreakDamageAddedRatioBase" + """ 击破特攻 """ + CriticalChanceBase: str = "CriticalChanceBase" + """ 暴击率 百分比 """ + CriticalDamageBase: str = "CriticalDamageBase" + """ 暴击伤害 百分比 """ + DefenceAddedRatio: str = "DefenceAddedRatio" + """ 防御力 百分比 """ + DefenceDelta: str = "DefenceDelta" + """ 防御力 """ + FireAddedRatio: str = "FireAddedRatio" + """ 火属性伤害提高 百分比 """ + HPAddedRatio: str = "HPAddedRatio" + """ 生命值 百分比 """ + HPDelta: str = "HPDelta" + """ 生命值 """ + HealRatioBase: str = "HealRatioBase" + """ 治疗量加成 百分比""" + IceAddedRatio: str = "IceAddedRatio" + """ 冰属性伤害提高 百分比 """ + ImaginaryAddedRatio: str = "ImaginaryAddedRatio" + """ 虚数属性伤害提高 百分比 """ + PhysicalAddedRatio: str = "PhysicalAddedRatio" + """ 物理属性伤害提高 百分比 """ + QuantumAddedRatio: str = "QuantumAddedRatio" + """ 量子属性伤害提高 百分比 """ + SpeedDelta: str = "SpeedDelta" + """ 速度 """ + SPRatioBase: str = "SPRatioBase" + """ 能量恢复效率 百分比 """ + StatusProbabilityBase: str = "StatusProbabilityBase" + """ 效果命中 百分比 """ + StatusResistanceBase: str = "StatusResistanceBase" + """ 效果抵抗 百分比 """ + ThunderAddedRatio: str = "ThunderAddedRatio" + """ 雷属性伤害提高 百分比 """ + WindAddedRatio: str = "WindAddedRatio" + """ 风属性伤害提高 百分比 """ + + +class RelicPosition(str, Enum): + HEAD: str = "HEAD" + """ 头 """ + HAND: str = "HAND" + """ 手 """ + BODY: str = "BODY" + """ 躯干 """ + FOOT: str = "FOOT" + """ 脚 """ + NECK: str = "NECK" + """ 位面球 """ + OBJECT: str = "OBJECT" + """ 连结绳 """ + + @property + def num(self): + index_map = { + RelicPosition.HEAD: 0, + RelicPosition.HAND: 1, + RelicPosition.BODY: 2, + RelicPosition.FOOT: 3, + RelicPosition.NECK: 0, + RelicPosition.OBJECT: 1, + } + return index_map.get(self) diff --git a/modules/wiki/models/light_cone.py b/modules/wiki/models/light_cone.py index 0cb5a60..a9c8fee 100644 --- a/modules/wiki/models/light_cone.py +++ b/modules/wiki/models/light_cone.py @@ -43,3 +43,7 @@ class LightCone(BaseModel): """命途""" promote: list[LightConePromote] """晋阶信息""" + + @property + def rarity(self) -> int: + return 5 - list(Quality).index(self.quality) diff --git a/modules/wiki/models/monster.py b/modules/wiki/models/monster.py index ccc20d6..474f3c7 100644 --- a/modules/wiki/models/monster.py +++ b/modules/wiki/models/monster.py @@ -23,4 +23,3 @@ class Monster(BaseModel): """抗性""" find_area: str """发现地点""" - diff --git a/modules/wiki/models/relic.py b/modules/wiki/models/relic.py index 7272640..7de7f33 100644 --- a/modules/wiki/models/relic.py +++ b/modules/wiki/models/relic.py @@ -1,13 +1,19 @@ # 遗器套装 +from typing import List + from pydantic import BaseModel class Relic(BaseModel): id: int """遗器套装ID""" + bbs_id: int + """WIKI ID""" name: str """套装名称""" icon: str """套装图标""" affect: str """套装效果""" + image_list: List[str] + """套装子图""" diff --git a/modules/wiki/models/relic_affix.py b/modules/wiki/models/relic_affix.py new file mode 100644 index 0000000..e2dbfe7 --- /dev/null +++ b/modules/wiki/models/relic_affix.py @@ -0,0 +1,53 @@ +from decimal import Decimal +from typing import Optional, Dict + +from pydantic import BaseModel, root_validator + +from .enums import RelicAffix, RelicPosition + + +class SingleRelicAffix(BaseModel): + id: int + property: RelicAffix + base_value: float + level_value: Optional[float] = None + step_value: Optional[float] = None + is_main: bool + max_step: Optional[int] = None + + def get_value(self, level_or_step: int, cnt: int = 1) -> float: + base_value = Decimal(self.base_value) * Decimal(cnt) + add_value = Decimal(self.level_value if self.is_main else self.step_value) + return float(base_value + add_value * Decimal(level_or_step)) + + +class RelicAffixAll(BaseModel): + id: int + set_id: int + """ 套装ID """ + type: RelicPosition + """ 遗器类型 """ + rarity: int + """ 星级 """ + main_affix_group: int + sub_affix_group: int + max_level: int + """ 最大等级 """ + main_affix: Dict[str, SingleRelicAffix] + """ 主词条 """ + sub_affix: Dict[str, SingleRelicAffix] + """ 副词条 """ + + @root_validator(pre=True) + def transform_dicts(cls, values): + for data in ["main_affix", "sub_affix"]: + affix = values.get(data) + if affix: + new_affix = {} + for key, value in affix.items(): + if isinstance(value, dict): + new_affix[key] = SingleRelicAffix(**value) + else: + new_affix[key] = value + values[data] = new_affix + return values diff --git a/modules/wiki/monster.py b/modules/wiki/monster.py index 98741e3..7c24d6b 100644 --- a/modules/wiki/monster.py +++ b/modules/wiki/monster.py @@ -37,10 +37,10 @@ class Monster(WikiModel): self.all_monsters_name[m.name] = m def get_by_id(self, cid: int) -> Optional[MonsterModel]: - return self.all_monsters_map.get(cid, None) + return self.all_monsters_map.get(cid) def get_by_name(self, name: str) -> Optional[MonsterModel]: - return self.all_monsters_name.get(name, None) + return self.all_monsters_name.get(name) def get_name_list(self) -> List[str]: return list(self.all_monsters_name.keys()) diff --git a/modules/wiki/relic.py b/modules/wiki/relic.py index 674e263..8c4106a 100644 --- a/modules/wiki/relic.py +++ b/modules/wiki/relic.py @@ -37,10 +37,10 @@ class Relic(WikiModel): self.all_relics_name[m.name] = m def get_by_id(self, cid: int) -> Optional[RelicModel]: - return self.all_relics_map.get(cid, None) + return self.all_relics_map.get(cid) def get_by_name(self, name: str) -> Optional[RelicModel]: - return self.all_relics_name.get(name, None) + return self.all_relics_name.get(name) def get_name_list(self) -> List[str]: return list(self.all_relics_name.keys()) diff --git a/plugins/account/account.py b/plugins/account/account.py index 4d99dfa..e69a093 100644 --- a/plugins/account/account.py +++ b/plugins/account/account.py @@ -87,7 +87,6 @@ class BindAccountPlugin(Plugin.Conversation): elif message.text == "HoYoLab": await message.reply_text("很抱歉,暂不支持HoYoLab服务器", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END - bind_account_plugin_data.region = RegionEnum.HOYOLAB else: await message.reply_text("选择错误,请重新选择") return CHECK_SERVER diff --git a/plugins/account/cookies.py b/plugins/account/cookies.py index 7d5b2ee..14b0613 100644 --- a/plugins/account/cookies.py +++ b/plugins/account/cookies.py @@ -131,8 +131,6 @@ class AccountCookiesPlugin(Plugin.Conversation): elif message.text == "HoYoLab": await message.reply_text("很抱歉,暂不支持HoYoLab服务器", reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END - bbs_name = "HoYoLab" - region = RegionEnum.HOYOLAB else: await message.reply_text("选择错误,请重新选择") return CHECK_SERVER diff --git a/plugins/app/inline.py b/plugins/app/inline.py index 099d619..e9dff32 100644 --- a/plugins/app/inline.py +++ b/plugins/app/inline.py @@ -12,7 +12,7 @@ from telegram import ( ) from telegram.constants import ParseMode from telegram.error import BadRequest -from telegram.ext import CallbackContext, InlineQueryHandler +from telegram.ext import CallbackContext from core.plugin import Plugin, handler from core.dependence.assets import AssetsService diff --git a/plugins/starrail/avatar_list.py b/plugins/starrail/avatar_list.py index e81830f..7b44114 100644 --- a/plugins/starrail/avatar_list.py +++ b/plugins/starrail/avatar_list.py @@ -14,7 +14,6 @@ from core.services.cookies import CookiesService from core.services.template.models import FileType from core.services.template.services import TemplateService from core.services.wiki.services import WikiService -from modules.wiki.models.enums import Quality from plugins.tools.genshin import CookiesNotFoundError, GenshinHelper, PlayerNotFoundError from utils.log import logger @@ -106,14 +105,7 @@ class AvatarListPlugin(Plugin): def get_light_cone_star(self, name: str) -> int: light_cone = self.wiki_service.light_cone.get_by_name(name) - star_int_map = { - Quality.Five: 5, - Quality.Four: 4, - Quality.Three: 3, - Quality.Two: 2, - Quality.One: 1, - } - return star_int_map[light_cone.quality] if light_cone else 3 + return light_cone.rarity if light_cone else 3 async def get_final_data(self, characters: List[StarRailDetailCharacter]) -> List[AvatarData]: data = [] diff --git a/plugins/starrail/challenge.py b/plugins/starrail/challenge.py index c32e15a..e82271b 100644 --- a/plugins/starrail/challenge.py +++ b/plugins/starrail/challenge.py @@ -128,9 +128,7 @@ class ChallengePlugin(Plugin): return async def reply_message_func(content: str) -> None: - _reply_msg = await message.reply_text( - f"开拓者 ({uid}) {content}", parse_mode=ParseMode.HTML - ) + _reply_msg = await message.reply_text(f"开拓者 ({uid}) {content}", parse_mode=ParseMode.HTML) reply_text: Optional[Message] = None @@ -180,7 +178,7 @@ class ChallengePlugin(Plugin): "floor": floor_data, "floor_time": floor_data.node_1.challenge_time.datetime.strftime("%Y-%m-%d %H:%M:%S"), "floor_nodes": [floor_data.node_1, floor_data.node_2], - "floor_num": floor + "floor_num": floor, } return render_data @@ -244,7 +242,7 @@ class ChallengePlugin(Plugin): 8: "#1D2A5D", 9: "#292B58", 10: "#382024", - } + }, } if total: diff --git a/plugins/starrail/ledger.py b/plugins/starrail/ledger.py index a6c5923..5fde825 100644 --- a/plugins/starrail/ledger.py +++ b/plugins/starrail/ledger.py @@ -97,7 +97,7 @@ class LedgerPlugin(Plugin): last_month = last_month.replace(day=1) - timedelta(days=1) allow_month_year[last_month.month] = last_month.year - if (month not in allow_month_year.keys()) or (not isinstance(month, int)): + if (month not in allow_month_year) or (not isinstance(month, int)): raise IndexError year = allow_month_year[month] except IndexError: diff --git a/plugins/starrail/light_cone.py b/plugins/starrail/light_cone.py index 11bf389..8c4225a 100644 --- a/plugins/starrail/light_cone.py +++ b/plugins/starrail/light_cone.py @@ -35,10 +35,7 @@ class LightConePlugin(Plugin): if len(args) >= 1: light_cone_name = args[0] else: - reply_message = await message.reply_text( - "请回复你要查询的光锥名称", - reply_markup=InlineKeyboardMarkup(self.KEYBOARD) - ) + reply_message = await message.reply_text("请回复你要查询的光锥名称", reply_markup=InlineKeyboardMarkup(self.KEYBOARD)) if filters.ChatType.GROUPS.filter(reply_message): self.add_delete_message_job(message) self.add_delete_message_job(reply_message) diff --git a/plugins/starrail/material.py b/plugins/starrail/material.py index 5cdac50..a8b0dca 100644 --- a/plugins/starrail/material.py +++ b/plugins/starrail/material.py @@ -36,8 +36,7 @@ class MaterialPlugin(Plugin): character_name = args[0] else: reply_message = await message.reply_text( - "请回复你要查询的角色培养素材图鉴的角色名", - reply_markup=InlineKeyboardMarkup(self.KEYBOARD) + "请回复你要查询的角色培养素材图鉴的角色名", reply_markup=InlineKeyboardMarkup(self.KEYBOARD) ) if filters.ChatType.GROUPS.filter(reply_message): self.add_delete_message_job(message) diff --git a/plugins/starrail/player_cards.py b/plugins/starrail/player_cards.py new file mode 100644 index 0000000..1598479 --- /dev/null +++ b/plugins/starrail/player_cards.py @@ -0,0 +1,563 @@ +import math +from typing import List, Tuple, Union, Optional, TYPE_CHECKING, Dict + +from pydantic import BaseModel +from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Message +from telegram.constants import ChatAction +from telegram.ext import filters +from telegram.helpers import create_deep_linked_url + +from core.config import config +from core.dependence.assets import AssetsService, AssetsCouldNotFound +from core.dependence.redisdb import RedisDB +from core.plugin import Plugin, handler +from core.services.players import PlayersService +from core.services.template.services import TemplateService +from core.services.wiki.services import WikiService +from metadata.shortname import roleToName, idToRole +from modules.apihelper.client.components.player_cards import PlayerCards as PlayerCardsClient, PlayerInfo, Avatar, Relic +from modules.playercards.fight_prop import EquipmentsStats +from modules.playercards.helpers import ArtifactStatsTheory +from utils.log import logger + +if TYPE_CHECKING: + from telegram.ext import ContextTypes + from telegram import Update + +try: + import ujson as jsonlib +except ImportError: + import json as jsonlib + + +class PlayerCards(Plugin): + def __init__( + self, + player_service: PlayersService, + template_service: TemplateService, + assets_service: AssetsService, + wiki_service: WikiService, + redis: RedisDB, + ): + self.player_service = player_service + self.client = PlayerCardsClient(redis) + self.cache = self.client.cache + self.assets_service = assets_service + self.template_service = template_service + self.wiki_service = wiki_service + self.kitsune: Optional[str] = None + + async def initialize(self): + await self.client.async_init() + + async def _load_history(self, uid) -> Optional[PlayerInfo]: + data = await self.client.player_cards_file.load_history_info(uid) + if data is None: + return None + return PlayerInfo.parse_obj(data) + + async def get_uid_and_ch( + self, user_id: int, args: List[str], reply: Optional[Message] + ) -> Tuple[Optional[int], Optional[str]]: + """通过消息获取 uid,优先级:args > reply > self""" + uid, ch_name, user_id_ = None, None, user_id + if args: + for i in args: + if i is not None: + if i.isdigit() and len(i) == 9: + uid = int(i) + else: + ch_name = roleToName(i) + if reply: + try: + user_id_ = reply.from_user.id + except AttributeError: + pass + if not uid: + player_info = await self.player_service.get_player(user_id_) + if player_info is not None: + uid = player_info.player_id + if (not uid) and (user_id_ != user_id): + player_info = await self.player_service.get_player(user_id) + if player_info is not None: + uid = player_info.player_id + return uid, ch_name + + @handler.command(command="player_card", block=False) + @handler.message(filters=filters.Regex("^角色卡片查询(.*)"), block=False) + async def player_cards(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None: + user = update.effective_user + message = update.effective_message + args = self.get_args(context) + await message.reply_chat_action(ChatAction.TYPING) + uid, ch_name = await self.get_uid_and_ch(user.id, args, message.reply_to_message) + if uid is None: + buttons = [ + [ + InlineKeyboardButton( + "点我绑定账号", + url=create_deep_linked_url(context.bot.username, "set_uid"), + ) + ] + ] + if filters.ChatType.GROUPS.filter(message): + reply_message = await message.reply_text( + "未查询到您所绑定的账号信息,请先私聊派蒙绑定账号", + reply_markup=InlineKeyboardMarkup(buttons), + ) + self.add_delete_message_job(reply_message, delay=30) + + self.add_delete_message_job(message, delay=30) + else: + await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号", reply_markup=InlineKeyboardMarkup(buttons)) + return + # 暂时只支持国服 + if not (100000000 < uid < 200000000): + await message.reply_text("此功能暂时只支持国服") + return + data = await self._load_history(uid) + if data is None or len(data.AvatarList) == 0: + if isinstance(self.kitsune, str): + photo = self.kitsune + else: + photo = open("resources/img/aaa.jpg", "rb") + buttons = [ + [ + InlineKeyboardButton( + "更新面板", + callback_data=f"update_player_card|{user.id}|{uid}", + ) + ] + ] + reply_message = await message.reply_photo( + photo=photo, + caption=f"角色列表未找到,请尝试点击下方按钮更新角色列表 - UID {uid}", + reply_markup=InlineKeyboardMarkup(buttons), + ) + if reply_message.photo: + self.kitsune = reply_message.photo[-1].file_id + return + if ch_name is not None: + logger.info( + "用户 %s[%s] 角色卡片查询命令请求 || character_name[%s] uid[%s]", + user.full_name, + user.id, + ch_name, + uid, + ) + else: + logger.info("用户 %s[%s] 角色卡片查询命令请求", user.full_name, user.id) + ttl = await self.cache.ttl(uid) + + buttons = self.gen_button(data, user.id, uid, update_button=ttl < 0) + if isinstance(self.kitsune, str): + photo = self.kitsune + else: + photo = open("resources/img/aaa.jpg", "rb") + reply_message = await message.reply_photo( + photo=photo, + caption=f"请选择你要查询的角色 - UID {uid}", + reply_markup=InlineKeyboardMarkup(buttons), + ) + if reply_message.photo: + self.kitsune = reply_message.photo[-1].file_id + return + for characters in data.AvatarList: + if idToRole(characters.AvatarID) == ch_name: + break + else: + await message.reply_text(f"角色展柜中未找到 {ch_name} ,请检查角色是否存在于角色展柜中,或者等待角色数据更新后重试") + return + if characters.AvatarID in {8001, 8002, 8003, 8004}: + await message.reply_text(f"暂不支持查询 {ch_name} 的角色卡片") + return + await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) + render_result = await RenderTemplate( + uid, + characters, + self.template_service, + self.assets_service, + self.wiki_service, + self.client, + ).render() # pylint: disable=W0631 + await render_result.reply_photo( + message, + filename=f"player_card_{uid}_{ch_name}.png", + ) + + @handler.callback_query(pattern=r"^update_player_card\|", block=False) + async def update_player_card(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None: + user = update.effective_user + message = update.effective_message + callback_query = update.callback_query + + async def get_player_card_callback(callback_query_data: str) -> Tuple[int, int]: + _data = callback_query_data.split("|") + _user_id = int(_data[1]) + _uid = int(_data[2]) + logger.debug("callback_query_data函数返回 user_id[%s] uid[%s]", _user_id, _uid) + return _user_id, _uid + + user_id, uid = await get_player_card_callback(callback_query.data) + if user.id != user_id: + await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True) + return + + ttl = await self.cache.ttl(uid) + + if ttl > 0: + await callback_query.answer(text=f"请等待 {ttl} 秒后再更新", show_alert=True) + return + + await message.reply_chat_action(ChatAction.TYPING) + await callback_query.answer(text="正在获取角色列表 请不要重复点击按钮") + data = await self.client.update_data(str(uid)) + if isinstance(data, str): + await callback_query.answer(text=data, show_alert=True) + return + if data.AvatarList is None: + await message.delete() + await callback_query.answer("请先将角色加入到角色展柜并允许查看角色详情后再使用此功能,如果已经添加了角色,请等待角色数据更新后重试", show_alert=True) + return + buttons = self.gen_button(data, user.id, uid, update_button=False) + render_data = await self.parse_holder_data(data) + holder = await self.template_service.render( + "starrail/player_card/holder.html", + render_data, + viewport={"width": 750, "height": 380}, + ttl=60 * 10, + caption=f"更新角色列表成功,请选择你要查询的角色 - UID {uid}", + ) + await holder.edit_media(message, reply_markup=InlineKeyboardMarkup(buttons)) + + @handler.callback_query(pattern=r"^get_player_card\|", block=False) + async def get_player_cards(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None: + callback_query = update.callback_query + user = callback_query.from_user + message = callback_query.message + + async def get_player_card_callback( + callback_query_data: str, + ) -> Tuple[str, int, int]: + _data = callback_query_data.split("|") + _user_id = int(_data[1]) + _uid = int(_data[2]) + _result = _data[3] + logger.debug( + "callback_query_data函数返回 result[%s] user_id[%s] uid[%s]", + _result, + _user_id, + _uid, + ) + return _result, _user_id, _uid + + result, user_id, uid = await get_player_card_callback(callback_query.data) + if user.id != user_id: + await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True) + return + if result == "empty_data": + await callback_query.answer(text="此按钮不可用", show_alert=True) + return + page = 0 + if result.isdigit(): + page = int(result) + logger.info( + "用户 %s[%s] 角色卡片查询命令请求 || page[%s] uid[%s]", + user.full_name, + user.id, + page, + uid, + ) + else: + logger.info( + "用户 %s[%s] 角色卡片查询命令请求 || character_name[%s] uid[%s]", + user.full_name, + user.id, + result, + uid, + ) + data = await self._load_history(uid) + if isinstance(data, str): + await message.reply_text(data) + return + if data.AvatarList is None: + await message.delete() + await callback_query.answer("请先将角色加入到角色展柜并允许查看角色详情后再使用此功能,如果已经添加了角色,请等待角色数据更新后重试", show_alert=True) + return + if page: + buttons = self.gen_button(data, user.id, uid, page, await self.cache.ttl(uid) <= 0) + await message.edit_reply_markup(reply_markup=InlineKeyboardMarkup(buttons)) + await callback_query.answer(f"已切换到第 {page} 页", show_alert=False) + return + for characters in data.AvatarList: + if idToRole(characters.AvatarID) == result: + break + else: + await message.delete() + await callback_query.answer(f"角色展柜中未找到 {result} ,请检查角色是否存在于角色展柜中,或者等待角色数据更新后重试", show_alert=True) + return + if characters.AvatarID in {8001, 8002, 8003, 8004}: + await callback_query.answer(f"暂不支持查询 {result} 的角色卡片") + return + await callback_query.answer(text="正在渲染图片中 请稍等 请不要重复点击按钮", show_alert=False) + await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) + render_result = await RenderTemplate( + uid, characters, self.template_service, self.assets_service, self.wiki_service, self.client + ).render() # pylint: disable=W0631 + render_result.filename = f"player_card_{uid}_{result}.png" + await render_result.edit_media(message) + + @staticmethod + def gen_button( + data: PlayerInfo, + user_id: Union[str, int], + uid: int, + page: int = 1, + update_button: bool = True, + ) -> List[List[InlineKeyboardButton]]: + """生成按钮""" + buttons = [] + + if data.AvatarList: + buttons = [ + InlineKeyboardButton( + idToRole(value.AvatarID), + callback_data=f"get_player_card|{user_id}|{uid}|{idToRole(value.AvatarID)}", + ) + for value in data.AvatarList + if value.AvatarID + ] + all_buttons = [buttons[i : i + 4] for i in range(0, len(buttons), 4)] + send_buttons = all_buttons[(page - 1) * 3 : page * 3] + last_page = page - 1 if page > 1 else 0 + all_page = math.ceil(len(all_buttons) / 3) + next_page = page + 1 if page < all_page and all_page > 1 else 0 + last_button = [] + if last_page: + last_button.append( + InlineKeyboardButton( + "<< 上一页", + callback_data=f"get_player_card|{user_id}|{uid}|{last_page}", + ) + ) + if last_page or next_page: + last_button.append( + InlineKeyboardButton( + f"{page}/{all_page}", + callback_data=f"get_player_card|{user_id}|{uid}|empty_data", + ) + ) + if update_button: + last_button.append( + InlineKeyboardButton( + "更新面板", + callback_data=f"update_player_card|{user_id}|{uid}", + ) + ) + if next_page: + last_button.append( + InlineKeyboardButton( + "下一页 >>", + callback_data=f"get_player_card|{user_id}|{uid}|{next_page}", + ) + ) + if last_button: + send_buttons.append(last_button) + return send_buttons + + async def parse_holder_data(self, data: PlayerInfo) -> dict: + """ + 生成渲染所需数据 + """ + characters_data = [] + for idx, character in enumerate(data.AvatarList): + cid = 8004 if character.AvatarID in {8001, 8002, 8003, 8004} else character.AvatarID + try: + characters_data.append( + { + "level": character.Level, + "constellation": character.Rank, + "icon": self.assets_service.avatar.square(cid).as_uri(), + } + ) + except AssetsCouldNotFound: + logger.warning("角色 %s 的头像资源获取失败", cid) + if idx > 6: + break + return { + "uid": data.UID, + "level": data.Level or 0, + "signature": data.Signature or "", + "characters": characters_data, + } + + +class Artifact(BaseModel): + equipment: Dict = {} + # 圣遗物评分 + score: float = 0 + # 圣遗物评级 + score_label: str = "E" + # 圣遗物评级颜色 + score_class: str = "" + # 圣遗物单行属性评分 + substat_scores: List[float] + + def __init__(self, **kwargs): + super().__init__(**kwargs) + for substat_scores in self.substat_scores: + self.score += substat_scores + self.score = round(self.score, 1) + + for r in ( + ("D", 10.0), + ("C", 16.5), + ("B", 23.1), + ("A", 29.7), + ("S", 36.3), + ("SS", 42.9), + ("SSS", 49.5), + ("ACE", 56.1), + ("ACE²", 66.0), + ): + if self.score >= r[1]: + self.score_label = r[0] + self.score_class = self.get_score_class(r[0]) + + @staticmethod + def get_score_class(label: str) -> str: + mapping = { + "D": "text-neutral-400", + "C": "text-neutral-200", + "B": "text-violet-400", + "A": "text-violet-400", + "S": "text-yellow-400", + "SS": "text-yellow-400", + "SSS": "text-yellow-400", + "ACE": "text-red-500", + "ACE²": "text-red-500", + } + return mapping.get(label, "text-neutral-400") + + +class RenderTemplate: + def __init__( + self, + uid: Union[int, str], + character: Avatar, + template_service: TemplateService, + assets_service: AssetsService, + wiki_service: WikiService, + client: PlayerCardsClient, + ): + self.uid = uid + self.template_service = template_service + self.character = character + self.assets_service = assets_service + self.wiki_service = wiki_service + self.client = client + + async def render(self): + images = await self.cache_images() + + artifacts = self.find_artifacts() + artifact_total_score: float = sum(artifact.score for artifact in artifacts) + artifact_total_score = round(artifact_total_score, 1) + + artifact_total_score_label: str = "E" + for r in ( + ("D", 10.0), + ("C", 16.5), + ("B", 23.1), + ("A", 29.7), + ("S", 36.3), + ("SS", 42.9), + ("SSS", 49.5), + ("ACE", 56.1), + ("ACE²", 66.0), + ): + if artifact_total_score / 5 >= r[1]: + artifact_total_score_label = r[0] + + weapon = None + weapon_detail = None + if self.character.EquipmentID and self.character.EquipmentID.ID: + weapon = self.character.EquipmentID + weapon_detail = self.wiki_service.light_cone.get_by_id(self.character.EquipmentID.ID) + skills = [0, 0, 0, 0, 0] + for index in range(5): + skills[index] = self.character.BehaviorList[index].Level + data = { + "uid": self.uid, + "character": self.character, + "character_detail": self.wiki_service.character.get_by_id(self.character.AvatarID), + "weapon": weapon, + "weapon_detail": weapon_detail, + # 圣遗物评分 + "artifact_total_score": artifact_total_score, + # 圣遗物评级 + "artifact_total_score_label": artifact_total_score_label, + # 圣遗物评级颜色 + "artifact_total_score_class": Artifact.get_score_class(artifact_total_score_label), + "artifacts": artifacts, + "skills": skills, + "images": images, + } + + return await self.template_service.render( + "starrail/player_card/player_card.html", + data, + {"width": 1000, "height": 1200}, + full_page=True, + query_selector=".text-neutral-200", + ttl=7 * 24 * 60 * 60, + ) + + async def cache_images(self): + c = self.character + cid = c.AvatarID + data = { + "banner_url": self.assets_service.avatar.gacha(cid).as_uri(), + "skills": [i.as_uri() for i in self.assets_service.avatar.skills(cid)][:-1], + "constellations": [i.as_uri() for i in self.assets_service.avatar.eidolons(cid)], + "equipment": "", + } + if c.EquipmentID and c.EquipmentID.ID: + data["equipment"] = self.assets_service.light_cone.icon(c.EquipmentID.ID).as_uri() + return data + + def find_artifacts(self) -> List[Artifact]: + """在 equipments 数组中找到圣遗物,并转换成带有分数的 model。equipments 数组包含圣遗物和武器""" + + stats = ArtifactStatsTheory(idToRole(self.character.AvatarID)) + + def substat_score(s: EquipmentsStats) -> float: + return stats.theory(s) + + def fix_equipment(e: Relic) -> Dict: + rid = e.ID + affix = self.client.get_affix_by_id(rid) + relic_set = self.wiki_service.relic.get_by_id(affix.set_id) + try: + icon = relic_set.image_list[affix.type.num] + except IndexError: + icon = relic_set.icon + return { + "id": rid, + "name": relic_set.name, + "icon": icon, + "level": e.Level, + "rank": affix.rarity, + "main_sub": self.client.get_affix(e, True, False)[0], + "sub": self.client.get_affix(e, False, True), + } + + relic_list = self.character.RelicList or [] + return [ + Artifact( + equipment=fix_equipment(e), + # 圣遗物单行属性评分 + substat_scores=[substat_score(s) for s in self.client.get_affix(e, False)], + ) + for e in relic_list + ] diff --git a/resources/bot/help/help.html b/resources/bot/help/help.html index 543b8a7..ccb0ded 100644 --- a/resources/bot/help/help.html +++ b/resources/bot/help/help.html @@ -53,13 +53,13 @@
玩家统计查询
- - - - - - - +
+
+ /player_card + +
+
角色卡片
+
@@ -75,13 +75,13 @@
查询当月开拓月历
- - - - - - - +
+
+ /challenge + +
+
查询当期混沌回忆战绩
+
diff --git a/resources/img/aaa.jpg b/resources/img/aaa.jpg new file mode 100644 index 0000000..6ee3968 Binary files /dev/null and b/resources/img/aaa.jpg differ diff --git a/resources/starrail/player_card/artifacts.html b/resources/starrail/player_card/artifacts.html new file mode 100644 index 0000000..f2c6eb0 --- /dev/null +++ b/resources/starrail/player_card/artifacts.html @@ -0,0 +1,43 @@ +{% for item in artifacts %} +
+
+
+ +
+ +{{ item.equipment.level }} +
+
+
+
{{ item.equipment.name }}
+
+
{{ item.equipment.main_sub.name }}
+
+ {{ item.equipment.main_sub.value }} +
+
+
+ {{ item.score }} 分 - {{ item.score_label + }} +
+
+
+
+ {% for stat in item.equipment.sub %} +
+
+ {{ stat.name }} +
+
+ +{{ stat.value }} +
+
+ {{ item.substat_scores[loop.index0] }} + +
+
+ {% endfor %} +
+
+{% endfor %} diff --git a/resources/starrail/player_card/constellations.html b/resources/starrail/player_card/constellations.html new file mode 100644 index 0000000..63a6358 --- /dev/null +++ b/resources/starrail/player_card/constellations.html @@ -0,0 +1,13 @@ +
+
+ {% for item in images.constellations %} +
+ +
+ {% endfor %} +
+
diff --git a/resources/starrail/player_card/holder.html b/resources/starrail/player_card/holder.html new file mode 100644 index 0000000..863c0cd --- /dev/null +++ b/resources/starrail/player_card/holder.html @@ -0,0 +1,37 @@ + + + + + holder + + + + +
+
角色展柜
+
+
+
UID: {{ uid }}
+
开拓等级: {{ level }} 级
+
+
+
签名: {{ signature }}
+
+
+
+ {% for character in characters %} +
+ {% if character.constellation > 0 %} + {% set bg = ['blue','blue', 'green','green', 'red', 'red'][character.constellation - 1] %} +
{{ character.constellation }} 命
+ {% endif %} +
+ +
+
Lv.{{ character.level }}
+
+ {% endfor %} +
+
+ + \ No newline at end of file diff --git a/resources/starrail/player_card/holder_example.html b/resources/starrail/player_card/holder_example.html new file mode 100644 index 0000000..01bcc43 --- /dev/null +++ b/resources/starrail/player_card/holder_example.html @@ -0,0 +1,58 @@ + + + + + holder_example + + + + +
+
角色展柜
+
+
+
UID: 123456789
+
冒险等阶: 55
+
+
+
签名: 貴方の運命は、すでに我が手中の糸が絡めとった!填充
+
+
+
+
+
4命
+
+
+ 荧 +
+
Lv.90
+
+
+
+ 荧 +
+
+
+
+ 荧 +
+
+
+
+ 荧 +
+
+
+
+ 荧 +
+
+
+
+ 荧 +
+
+
+
+ + \ No newline at end of file diff --git a/resources/starrail/player_card/img/bg-anemo.jpg b/resources/starrail/player_card/img/bg-anemo.jpg new file mode 100644 index 0000000..985a7d4 Binary files /dev/null and b/resources/starrail/player_card/img/bg-anemo.jpg differ diff --git a/resources/starrail/player_card/img/bg-cryo.jpg b/resources/starrail/player_card/img/bg-cryo.jpg new file mode 100644 index 0000000..cf4bd1d Binary files /dev/null and b/resources/starrail/player_card/img/bg-cryo.jpg differ diff --git a/resources/starrail/player_card/img/bg-dendro.jpg b/resources/starrail/player_card/img/bg-dendro.jpg new file mode 100644 index 0000000..b900d28 Binary files /dev/null and b/resources/starrail/player_card/img/bg-dendro.jpg differ diff --git a/resources/starrail/player_card/img/bg-electro.jpg b/resources/starrail/player_card/img/bg-electro.jpg new file mode 100644 index 0000000..5dec746 Binary files /dev/null and b/resources/starrail/player_card/img/bg-electro.jpg differ diff --git a/resources/starrail/player_card/img/bg-nombre.jpg b/resources/starrail/player_card/img/bg-nombre.jpg new file mode 100644 index 0000000..d68405f Binary files /dev/null and b/resources/starrail/player_card/img/bg-nombre.jpg differ diff --git a/resources/starrail/player_card/img/bg-physical.jpg b/resources/starrail/player_card/img/bg-physical.jpg new file mode 100644 index 0000000..4109e95 Binary files /dev/null and b/resources/starrail/player_card/img/bg-physical.jpg differ diff --git a/resources/starrail/player_card/img/bg-pyro.jpg b/resources/starrail/player_card/img/bg-pyro.jpg new file mode 100644 index 0000000..302d612 Binary files /dev/null and b/resources/starrail/player_card/img/bg-pyro.jpg differ diff --git a/resources/starrail/player_card/img/bg-quantum.jpg b/resources/starrail/player_card/img/bg-quantum.jpg new file mode 100644 index 0000000..cc262f7 Binary files /dev/null and b/resources/starrail/player_card/img/bg-quantum.jpg differ diff --git a/resources/starrail/player_card/img/holder_bg.png b/resources/starrail/player_card/img/holder_bg.png new file mode 100644 index 0000000..2b4fece Binary files /dev/null and b/resources/starrail/player_card/img/holder_bg.png differ diff --git a/resources/starrail/player_card/img/star.png b/resources/starrail/player_card/img/star.png new file mode 100644 index 0000000..80d1888 Binary files /dev/null and b/resources/starrail/player_card/img/star.png differ diff --git a/resources/starrail/player_card/img/talent-anemo.png b/resources/starrail/player_card/img/talent-anemo.png new file mode 100644 index 0000000..e9926f2 Binary files /dev/null and b/resources/starrail/player_card/img/talent-anemo.png differ diff --git a/resources/starrail/player_card/img/talent-cryo.png b/resources/starrail/player_card/img/talent-cryo.png new file mode 100644 index 0000000..2defb36 Binary files /dev/null and b/resources/starrail/player_card/img/talent-cryo.png differ diff --git a/resources/starrail/player_card/img/talent-dendro.png b/resources/starrail/player_card/img/talent-dendro.png new file mode 100644 index 0000000..2359d71 Binary files /dev/null and b/resources/starrail/player_card/img/talent-dendro.png differ diff --git a/resources/starrail/player_card/img/talent-electro.png b/resources/starrail/player_card/img/talent-electro.png new file mode 100644 index 0000000..8e3c59d Binary files /dev/null and b/resources/starrail/player_card/img/talent-electro.png differ diff --git a/resources/starrail/player_card/img/talent-nombre.png b/resources/starrail/player_card/img/talent-nombre.png new file mode 100644 index 0000000..87d7d0d Binary files /dev/null and b/resources/starrail/player_card/img/talent-nombre.png differ diff --git a/resources/starrail/player_card/img/talent-physical.png b/resources/starrail/player_card/img/talent-physical.png new file mode 100644 index 0000000..0c2e175 Binary files /dev/null and b/resources/starrail/player_card/img/talent-physical.png differ diff --git a/resources/starrail/player_card/img/talent-pyro.png b/resources/starrail/player_card/img/talent-pyro.png new file mode 100644 index 0000000..3349fde Binary files /dev/null and b/resources/starrail/player_card/img/talent-pyro.png differ diff --git a/resources/starrail/player_card/img/talent-quantum.png b/resources/starrail/player_card/img/talent-quantum.png new file mode 100644 index 0000000..9408117 Binary files /dev/null and b/resources/starrail/player_card/img/talent-quantum.png differ diff --git a/resources/starrail/player_card/player_card.html b/resources/starrail/player_card/player_card.html new file mode 100644 index 0000000..18de3eb --- /dev/null +++ b/resources/starrail/player_card/player_card.html @@ -0,0 +1,108 @@ + + + + + Title + + + + + +
+
+ +
+
+ {% include 'starrail/player_card/constellations.html' %} + +
+
+
+ {{ character_detail.name }} +
+
+
UID {{ uid }}
+
Lv.{{ character.Level }}
+
+
+ + {% include 'starrail/player_card/skills.html' %} +
+ {% if weapon != none %} + {% include 'starrail/player_card/weapon.html' %} + {% endif %} + {% include 'starrail/player_card/score.html' %} +
+ {% include 'starrail/player_card/stats.html' %} +
+
+
+ + +
+
+ {% include 'starrail/player_card/artifacts.html' %} +
+
+ + +
+
+ Inspired by Miao-Plugin +
+
+
+ + diff --git a/resources/starrail/player_card/score.html b/resources/starrail/player_card/score.html new file mode 100644 index 0000000..bd67c8e --- /dev/null +++ b/resources/starrail/player_card/score.html @@ -0,0 +1,12 @@ +
+
+
+ {{ artifact_total_score_label }} +
+
遗器评级
+
+
+
{{ artifact_total_score }}
+
遗器评分
+
+
diff --git a/resources/starrail/player_card/skills.html b/resources/starrail/player_card/skills.html new file mode 100644 index 0000000..4621624 --- /dev/null +++ b/resources/starrail/player_card/skills.html @@ -0,0 +1,17 @@ +
+ {% for item in images.skills %} +
+
+ +
+
+ {{ skills[loop.index0] }} +
+
+ {% endfor %} +
diff --git a/resources/starrail/player_card/stats.html b/resources/starrail/player_card/stats.html new file mode 100644 index 0000000..d1cc3f1 --- /dev/null +++ b/resources/starrail/player_card/stats.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/resources/starrail/player_card/style.css b/resources/starrail/player_card/style.css new file mode 100644 index 0000000..98703a3 --- /dev/null +++ b/resources/starrail/player_card/style.css @@ -0,0 +1,146 @@ +:root { + --white: rgb(246 248 249); + --bg-color: rgb(233 229 220); + --h-color: rgb(203 189 162); + --red: rgb(255 86 33/ 80%); + --blue: rgb(98 168 233/ 80%); + --green: rgb(67 185 124/ 80%); +} + +body { + margin: 0; + padding: 5px; +} + +.hr { + width: 100%; + height: 3px; + background-color: rgb(246 248 249 / 50%); +} + +.container { + width: 750px; + position: relative; + filter: drop-shadow(2px 2px 5px rgb(0 0 0 /70%)); +} + +.title { + text-align: center; + font-size: 27px; + font-weight: bold; + color: var(--h-color); +} + +.caption { + margin: 10px 0; + color: var(--h-color); + font-size: 20px; +} + +/* 概览 */ + +.overview { + height: 540px; + padding: 20px 30px; + background-size: cover; + background-repeat: no-repeat; + background-position: center; + background-image: linear-gradient(to top, rgb(0 0 0 / 10%), rgb(0 0 0 / 10%)), url("./img/holder_bg.png"); + background-attachment: local; + border-radius: 15px; + overflow: hidden; +} + +.summarize { + font-size: 20px; + margin: 10px; + padding: 20px; + border-radius: 5px; + border: 2px solid rgb(118 121 120 / 80%); + outline: 4px solid rgb(70, 80, 100); + background-color: rgb(70 80 100 / 60%); + background-image: url("../abyss/background/banner 01.png"), url("../abyss/background/banner 02.png"); + background-repeat: no-repeat, no-repeat; + background-position: right, left; + background-size: auto 100%, auto 100%; + backdrop-filter: blur(5px); +} + +.summarize > div { + width: 100%; + height: 50%; + padding: 5px; + color: var(--white); + display: flex; + align-items: center; +} + +.summarize > div > div { + flex: 1; +} + +.characters { + margin-left: 47px; + margin-top: 15px; + display: flex; + flex-wrap: wrap; +} + +.character { + width: 120px; + height: 150px; + margin: 15px 12px; + background-color: rgb(233 229 220); + overflow: hidden; + border-radius: 10px; + position: relative; +} + +.characters > .character > .element { + position: absolute; + top: 3px; + left: 3px; + width: 25px; + height: 25px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; +} + +.icon { + width: 100%; + height: 120px; + background-size: cover; + background-repeat: no-repeat; + background-position: center; + overflow: hidden; + border-radius: 0 0 20px 0; +} + +.character > .caption { + font-size: 16px; + margin: 4px 0 0; + padding: 0; + height: min-content; + text-align: center; + color: black; +} + + +.character > div:first-child:not(.icon, .element) { + position: absolute; + top: 0; + right: 0; + padding: 3px; + min-width: 27px; + text-align: center; + border-radius: 0 0 0 10px; + filter: drop-shadow(1px 1px 5px rgb(0 0 0/50%)); + font-weight: 500; + color: var(--white); +} + +.icon > img { + width: inherit; + height: inherit; +} diff --git a/resources/starrail/player_card/weapon.html b/resources/starrail/player_card/weapon.html new file mode 100644 index 0000000..9b15e49 --- /dev/null +++ b/resources/starrail/player_card/weapon.html @@ -0,0 +1,17 @@ +
+
+ +
+
+
{{ weapon_detail.name }}
+
+
+
Lv.{{ weapon.Level }}
+
+ 精{{ weapon.Rank }} +
+
+
+