From b6f603896b4449d0c81cc56736867c5c9253fbb8 Mon Sep 17 00:00:00 2001 From: CMHopeSunshine <277073121@qq.com> Date: Mon, 29 Aug 2022 18:54:24 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=8A=BD=E5=8D=A1=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=96=B0UI=EF=BC=8C=E6=97=A0=E6=B3=95=E6=8A=BD?= =?UTF-8?q?=E5=8D=A1=E9=93=BE=E6=8E=A5=EF=BC=8C=E9=80=9A=E8=BF=87Stoken?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LittlePaimon/__init__.py | 2 +- LittlePaimon/admin/bind_cookie.py | 2 +- LittlePaimon/config/data/weapon.json | 15 +- LittlePaimon/config/models/plugin.py | 55 +---- LittlePaimon/config/path.py | 1 + .../manager/plugin_manager/manager.py | 4 +- .../plugins/Paimon_Autobbs/coin_handle.py | 2 +- .../plugins/Paimon_Autobbs/sign_handle.py | 2 +- LittlePaimon/plugins/Paimon_Bind/__init__.py | 2 +- .../plugins/Paimon_DailyNote/handler.py | 2 +- .../plugins/Paimon_Gacha_Log/UIGF_and_XLSX.py | 120 ---------- .../plugins/Paimon_Gacha_Log/__init__.py | 138 ++++-------- LittlePaimon/plugins/Paimon_Gacha_Log/api.py | 59 ----- .../plugins/Paimon_Gacha_Log/data_source.py | 137 ++++++++++++ LittlePaimon/plugins/Paimon_Gacha_Log/draw.py | 209 ++++++++++++++++++ .../plugins/Paimon_Gacha_Log/gacha_logs.py | 83 ------- .../plugins/Paimon_Gacha_Log/get_img.py | 145 ------------ .../plugins/Paimon_Gacha_Log/meta_data.py | 10 - .../plugins/Paimon_Gacha_Log/models.py | 81 +++++++ .../plugins/Paimon_Info/draw_abyss.py | 14 +- .../plugins/Paimon_Info/draw_character_bag.py | 4 +- .../Paimon_Info/draw_character_detail.py | 8 +- .../plugins/Paimon_Info/draw_player_card.py | 4 +- .../plugins/Paimon_MonthInfo/handler.py | 2 +- .../plugins/Paimon_Wiki/abyss_rate_draw.py | 4 +- LittlePaimon/utils/alias.py | 15 +- LittlePaimon/utils/{genshin_api.py => api.py} | 52 ++++- LittlePaimon/utils/genshin.py | 2 +- 28 files changed, 570 insertions(+), 604 deletions(-) delete mode 100644 LittlePaimon/plugins/Paimon_Gacha_Log/UIGF_and_XLSX.py delete mode 100644 LittlePaimon/plugins/Paimon_Gacha_Log/api.py create mode 100644 LittlePaimon/plugins/Paimon_Gacha_Log/data_source.py create mode 100644 LittlePaimon/plugins/Paimon_Gacha_Log/draw.py delete mode 100644 LittlePaimon/plugins/Paimon_Gacha_Log/gacha_logs.py delete mode 100644 LittlePaimon/plugins/Paimon_Gacha_Log/get_img.py delete mode 100644 LittlePaimon/plugins/Paimon_Gacha_Log/meta_data.py create mode 100644 LittlePaimon/plugins/Paimon_Gacha_Log/models.py rename LittlePaimon/utils/{genshin_api.py => api.py} (88%) diff --git a/LittlePaimon/__init__.py b/LittlePaimon/__init__.py index ffd897c..9d92b5a 100644 --- a/LittlePaimon/__init__.py +++ b/LittlePaimon/__init__.py @@ -7,7 +7,7 @@ from LittlePaimon.utils.migration import migrate_database from LittlePaimon.utils.tool import check_resource DRIVER = get_driver() -__version__ = '3.0.0beta2' +__version__ = '3.0.0beta3' try: SUPERUSERS: List[int] = [int(s) for s in DRIVER.config.superusers] diff --git a/LittlePaimon/admin/bind_cookie.py b/LittlePaimon/admin/bind_cookie.py index 153e0bc..5ff8042 100644 --- a/LittlePaimon/admin/bind_cookie.py +++ b/LittlePaimon/admin/bind_cookie.py @@ -7,7 +7,7 @@ from pywebio.session import run_asyncio_coroutine from LittlePaimon.utils import logger from LittlePaimon.database.models import LastQuery, PrivateCookie -from LittlePaimon.utils.genshin_api import get_bind_game_info, get_stoken_by_cookie +from LittlePaimon.utils.api import get_bind_game_info, get_stoken_by_cookie css_style = 'body {background: #000000 url(https://static.cherishmoon.fun/blog/h-wallpaper/zhounianqing.png);} #input-container {background: rgba(0,0,0,0);} summary {background-color: rgba(255,255,255,1);} .markdown-body {background-color: rgba(255,255,255,1);}' diff --git a/LittlePaimon/config/data/weapon.json b/LittlePaimon/config/data/weapon.json index e2a21b0..739ba1e 100644 --- a/LittlePaimon/config/data/weapon.json +++ b/LittlePaimon/config/data/weapon.json @@ -328,6 +328,7 @@ "天目影打刀": "UI_EquipIcon_Sword_Bakufu", "辰砂之纺锤": "UI_EquipIcon_Sword_Opus", "笼钓瓶一心": "UI_EquipIcon_Sword_Youtou", + "原木刀": "UI_EquipIcon_Sword_Arakalari", "「一心传」名刀": "UI_EquipIcon_Sword_YoutouEnchanted", "风鹰剑": "UI_EquipIcon_Sword_Falcon", "天空之刃": "UI_EquipIcon_Sword_Dvalin", @@ -341,6 +342,7 @@ "铁影阔剑": "UI_EquipIcon_Claymore_Glaive", "沐浴龙血的剑": "UI_EquipIcon_Claymore_Siegfry", "白铁大剑": "UI_EquipIcon_Claymore_Tin", + "石英大剑": "UI_EquipIcon_Claymore_Quartz", "以理服人": "UI_EquipIcon_Claymore_Reasoning", "飞天大御剑": "UI_EquipIcon_Claymore_Mitsurugi", "西风大剑": "UI_EquipIcon_Claymore_Zephyrus", @@ -357,6 +359,7 @@ "衔珠海皇": "UI_EquipIcon_Claymore_MillenniaTuna", "桂木斩长正": "UI_EquipIcon_Claymore_Bakufu", "恶王丸": "UI_EquipIcon_Claymore_Maria", + "森林王器": "UI_EquipIcon_Claymore_Arakalari", "天空之傲": "UI_EquipIcon_Claymore_Dvalin", "狼的末路": "UI_EquipIcon_Claymore_Wolfmound", "松籁响起之时": "UI_EquipIcon_Claymore_Widsith", @@ -367,6 +370,7 @@ "白缨枪": "UI_EquipIcon_Pole_Ruby", "钺矛": "UI_EquipIcon_Pole_Halberd", "黑缨枪": "UI_EquipIcon_Pole_Noire", + "「旗杆」": "UI_EquipIcon_Pole_Flagpole", "匣里灭辰": "UI_EquipIcon_Pole_Stardust", "试作星镰": "UI_EquipIcon_Pole_Proto", "流月针": "UI_EquipIcon_Pole_Exotic", @@ -379,6 +383,7 @@ "喜多院十文字": "UI_EquipIcon_Pole_Bakufu", "「渔获」": "UI_EquipIcon_Pole_Mori", "断浪长鳍": "UI_EquipIcon_Pole_Maria", + "贯月矢": "UI_EquipIcon_Pole_Arakalari", "护摩之杖": "UI_EquipIcon_Pole_Homa", "天空之脊": "UI_EquipIcon_Pole_Dvalin", "贯虹之槊": "UI_EquipIcon_Pole_Kunwu", @@ -392,6 +397,7 @@ "异世界行记": "UI_EquipIcon_Catalyst_Lightnov", "翡玉法球": "UI_EquipIcon_Catalyst_Jade", "甲级宝珏": "UI_EquipIcon_Catalyst_Phoney", + "琥珀玥": "UI_EquipIcon_Catalyst_Amber", "西风秘典": "UI_EquipIcon_Catalyst_Zephyrus", "流浪乐章": "UI_EquipIcon_Catalyst_Troupe", "祭礼残章": "UI_EquipIcon_Catalyst_Fossil", @@ -406,6 +412,7 @@ "嘟嘟可故事集": "UI_EquipIcon_Catalyst_Ludiharpastum", "白辰之环": "UI_EquipIcon_Catalyst_Bakufu", "证誓之明瞳": "UI_EquipIcon_Catalyst_Jyanome", + "盈满之实": "UI_EquipIcon_Catalyst_Arakalari", "天空之卷": "UI_EquipIcon_Catalyst_Dvalin", "四风原典": "UI_EquipIcon_Catalyst_Fourwinds", "尘世之锁": "UI_EquipIcon_Catalyst_Kunwu", @@ -418,6 +425,7 @@ "反曲弓": "UI_EquipIcon_Bow_Curve", "弹弓": "UI_EquipIcon_Bow_Sling", "信使": "UI_EquipIcon_Bow_Msg", + "黑檀弓": "UI_EquipIcon_Bow_Hardwood", "西风猎弓": "UI_EquipIcon_Bow_Zephyrus", "绝弦": "UI_EquipIcon_Bow_Troupe", "祭礼弓": "UI_EquipIcon_Bow_Fossil", @@ -428,17 +436,22 @@ "黑岩战弓": "UI_EquipIcon_Bow_Blackrock", "苍翠猎弓": "UI_EquipIcon_Bow_Viridescent", "暗巷猎手": "UI_EquipIcon_Bow_Outlaw", + "落霞": "UI_EquipIcon_Bow_Fallensun", "幽夜华尔兹": "UI_EquipIcon_Bow_Nachtblind", "风花之颂": "UI_EquipIcon_Bow_Fleurfair", "破魔之弓": "UI_EquipIcon_Bow_Bakufu", "掠食者": "UI_EquipIcon_Bow_Predator", "曚云之月": "UI_EquipIcon_Bow_Maria", + "王下近侍": "UI_EquipIcon_Bow_Arakalari", + "竭泽": "UI_EquipIcon_Bow_Fin", "天空之翼": "UI_EquipIcon_Bow_Dvalin", "阿莫斯之弓": "UI_EquipIcon_Bow_Amos", "终末嗟叹之诗": "UI_EquipIcon_Bow_Widsith", "冬极白星": "UI_EquipIcon_Bow_Worldbane", "若水": "UI_EquipIcon_Bow_Kirin", "飞雷之弦振": "UI_EquipIcon_Bow_Narukami", - "落霞": "UI_EquipIcon_Bow_Fallensun" + "猎人之径": "UI_EquipIcon_Bow_Ayus", + "(test)竿测试": "UI_EquipIcon_FishingRod", + "(test)穿模测试": "UI_EquipIcon_Bow_Template" } } \ No newline at end of file diff --git a/LittlePaimon/config/models/plugin.py b/LittlePaimon/config/models/plugin.py index 316dffd..1958dd4 100644 --- a/LittlePaimon/config/models/plugin.py +++ b/LittlePaimon/config/models/plugin.py @@ -1,61 +1,8 @@ -from typing import List, Dict, Literal, DefaultDict, Optional +from typing import Literal, DefaultDict from collections import defaultdict from pydantic import BaseModel -# class Ban(BaseModel): -# """插件屏蔽列表""" -# all_groups: bool = False -# """屏蔽所有群组""" -# group: List[int] = [] -# """屏蔽群组列表""" -# all_privates: bool = False -# """屏蔽所有群组""" -# private: List[int] = [] -# """屏蔽私聊列表""" -# group_member: List[str] = [] -# """屏蔽群组中特定成员列表""" -# all_guilds: bool = False -# """屏蔽所有频道""" -# guild: List[int] = [] -# """屏蔽频道列表""" -# guild_channel: List[str] = [] -# """屏蔽频道中特定子频道列表""" -# invert_selection: Dict[Literal['group', 'private', 'group_member', 'guild', 'guild_channel'], bool] = { -# 'group': False, -# 'private': False, -# 'group_member': False, -# 'guild': False, -# 'guild_channel': False} -# """是否反选""" -# -# def check(self, event: MessageEvent) -> bool: -# if isinstance(event, GroupMessageEvent): -# if self.all_groups: -# return False -# if event.group_id in self.group: -# return self.invert_selection['group'] -# if f'{event.group_id}_{event.user_id}' in self.group_member: -# return self.invert_selection['group_member'] -# return True -# elif isinstance(event, PrivateMessageEvent): -# if self.all_privates: -# return False -# if event.user_id in self.private: -# return self.invert_selection['private'] -# return True -# elif event.message_type == 'guild': -# if self.all_guilds: -# return False -# if event.guild_id in self.guild: -# return self.invert_selection['guild'] -# if f'{event.guild_id}_{event.channel_id}' in self.guild_channel: -# return self.invert_selection['guild_channel'] -# return True -# else: -# raise TypeError(f'{event.message_type} is not supported by plugin manager') - - class Statistics(BaseModel): """ 插件调用统计 diff --git a/LittlePaimon/config/path.py b/LittlePaimon/config/path.py index 91fbf30..5a6b7f1 100644 --- a/LittlePaimon/config/path.py +++ b/LittlePaimon/config/path.py @@ -35,6 +35,7 @@ GACHA_RES = RESOURCE_BASE_PATH / 'gacha_res' GACHA_SIM = USER_DATA_PATH / 'gacha_sim_data' # 原神抽卡记录数据路径 GACHA_LOG = USER_DATA_PATH / 'gacha_log_data' +GACHA_LOG.mkdir(parents=True, exist_ok=True) # 字体路径 FONTS_PATH = Path() / 'resources' / 'fonts' FONTS_PATH.mkdir(parents=True, exist_ok=True) diff --git a/LittlePaimon/manager/plugin_manager/manager.py b/LittlePaimon/manager/plugin_manager/manager.py index 0dc9cf0..660176c 100644 --- a/LittlePaimon/manager/plugin_manager/manager.py +++ b/LittlePaimon/manager/plugin_manager/manager.py @@ -68,14 +68,12 @@ class PluginManager: self.save() return f'成功设置{config_name}为{value}' - async def init_plugins(self): plugin_list = nb_plugin.get_loaded_plugins() group_list = await get_bot().get_group_list() user_list = await get_bot().get_friend_list() for plugin in plugin_list: - if plugin.name not in hidden_plugins and not await PluginPermission.filter(name=plugin.name).exists(): - logger.info('插件管理器', f'新纳入插件{plugin.name}进行权限管理') + if plugin.name not in hidden_plugins: await asyncio.gather(*[PluginPermission.update_or_create(name=plugin.name, session_id=group['group_id'], session_type='group') for group in group_list]) await asyncio.gather(*[PluginPermission.update_or_create(name=plugin.name, session_id=user['user_id'], diff --git a/LittlePaimon/plugins/Paimon_Autobbs/coin_handle.py b/LittlePaimon/plugins/Paimon_Autobbs/coin_handle.py index bdb0a54..26a1fd6 100644 --- a/LittlePaimon/plugins/Paimon_Autobbs/coin_handle.py +++ b/LittlePaimon/plugins/Paimon_Autobbs/coin_handle.py @@ -6,7 +6,7 @@ from collections import defaultdict from LittlePaimon.database.models import PrivateCookie, MihoyoBBSSub from LittlePaimon.utils import logger, aiorequests from LittlePaimon.utils import scheduler -from LittlePaimon.utils.genshin_api import random_text, random_hex, get_old_version_ds, get_ds +from LittlePaimon.utils.api import random_text, random_hex, get_old_version_ds, get_ds from LittlePaimon.manager.plugin_manager import plugin_manager as pm # 米游社的API列表 diff --git a/LittlePaimon/plugins/Paimon_Autobbs/sign_handle.py b/LittlePaimon/plugins/Paimon_Autobbs/sign_handle.py index 7e54263..14fc82e 100644 --- a/LittlePaimon/plugins/Paimon_Autobbs/sign_handle.py +++ b/LittlePaimon/plugins/Paimon_Autobbs/sign_handle.py @@ -7,7 +7,7 @@ from typing import Tuple from LittlePaimon import DRIVER from LittlePaimon.database.models import MihoyoBBSSub from LittlePaimon.utils import logger, scheduler -from LittlePaimon.utils.genshin_api import get_mihoyo_private_data, get_sign_reward_list +from LittlePaimon.utils.api import get_mihoyo_private_data, get_sign_reward_list from LittlePaimon.manager.plugin_manager import plugin_manager as pm from .draw import SignResult, draw_result diff --git a/LittlePaimon/plugins/Paimon_Bind/__init__.py b/LittlePaimon/plugins/Paimon_Bind/__init__.py index b5725ab..96f96ce 100644 --- a/LittlePaimon/plugins/Paimon_Bind/__init__.py +++ b/LittlePaimon/plugins/Paimon_Bind/__init__.py @@ -12,7 +12,7 @@ from LittlePaimon import NICKNAME from LittlePaimon.database.models import LastQuery, PrivateCookie, PublicCookie, Character, PlayerInfo, GeneralSub, \ DailyNoteSub, MihoyoBBSSub from LittlePaimon.utils import logger -from LittlePaimon.utils.genshin_api import get_bind_game_info, get_stoken_by_cookie +from LittlePaimon.utils.api import get_bind_game_info, get_stoken_by_cookie from LittlePaimon.utils.message import recall_message from LittlePaimon.manager.plugin_manager import plugin_manager as pm diff --git a/LittlePaimon/plugins/Paimon_DailyNote/handler.py b/LittlePaimon/plugins/Paimon_DailyNote/handler.py index fce5038..f93f4c3 100644 --- a/LittlePaimon/plugins/Paimon_DailyNote/handler.py +++ b/LittlePaimon/plugins/Paimon_DailyNote/handler.py @@ -10,7 +10,7 @@ from nonebot.params import Arg, Depends from LittlePaimon.database.models import DailyNoteSub, Player from LittlePaimon.utils import logger, scheduler -from LittlePaimon.utils.genshin_api import get_mihoyo_private_data +from LittlePaimon.utils.api import get_mihoyo_private_data from LittlePaimon.manager.plugin_manager import plugin_manager as pm from .draw import draw_daily_note_card diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/UIGF_and_XLSX.py b/LittlePaimon/plugins/Paimon_Gacha_Log/UIGF_and_XLSX.py deleted file mode 100644 index a72c2e6..0000000 --- a/LittlePaimon/plugins/Paimon_Gacha_Log/UIGF_and_XLSX.py +++ /dev/null @@ -1,120 +0,0 @@ -import time - -import xlsxwriter - -from LittlePaimon.config import GACHA_LOG -from .meta_data import * - - -def id_generator(): - id = 1000000000000000000 - while True: - id = id + 1 - yield str(id) - - -def convertUIGF(gachaLog, uid): - if 'gachaLog' in gachaLog: - gachaLog = gachaLog['gachaLog'] - UIGF_data = {"info": {}} - UIGF_data["info"]["uid"] = uid - UIGF_data["info"]["lang"] = "zh-cn" - UIGF_data["info"]["export_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) - UIGF_data["info"]["export_app"] = "genshin-gacha-export" - UIGF_data["info"]["export_app_version"] = 'v2.5.0.02221942' - UIGF_data["info"]["uigf_version"] = "v2.2" - UIGF_data["info"]["export_timestamp"] = int(time.time()) - all_gachaDictList = [] - - for gacha_type in gachaQueryTypeIds: - gacha_log = gachaLog.get(gacha_type, []) - gacha_log = sorted(gacha_log, key=lambda gacha: gacha["time"], reverse=True) - gacha_log.reverse() - for gacha in gacha_log: - gacha["uigf_gacha_type"] = gacha_type - all_gachaDictList.extend(gacha_log) - all_gachaDictList = sorted(all_gachaDictList, key=lambda gacha: gacha["time"]) - - id = id_generator() - for gacha in all_gachaDictList: - if gacha.get("id", "") == "": - gacha["id"] = next(id) - all_gachaDictList = sorted(all_gachaDictList, key=lambda gacha: gacha["id"]) - UIGF_data["list"] = all_gachaDictList - return UIGF_data - - -def writeXLSX(uid, gachaLog, gachaTypeIds): - t = time.strftime("%Y%m%d%H%M%S", time.localtime()) - workbook = xlsxwriter.Workbook(GACHA_LOG / f"gachaExport-{uid}.xlsx") - first_row = 1 # 不包含表头第一行 (zero indexed) - first_col = 0 # 第一列 - for id in gachaTypeIds: - gachaDictList = gachaLog[id] - gachaTypeName = gachaQueryTypeDict[id] - gachaDictList.reverse() - worksheet = workbook.add_worksheet(gachaTypeName) - content_css = workbook.add_format( - {"align": "left", "font_name": "微软雅黑", "border_color": "#c4c2bf", "bg_color": "#ebebeb", "border": 1}) - title_css = workbook.add_format( - {"align": "left", "font_name": "微软雅黑", "color": "#757575", "bg_color": "#dbd7d3", "border_color": "#c4c2bf", - "border": 1, "bold": True}) - excel_header = ["时间", "名称", "类别", "星级", "祈愿类型", "总次数", "保底内"] - worksheet.set_column("A:A", 22) - worksheet.set_column("B:B", 14) - worksheet.set_column("E:E", 14) - worksheet.write_row(0, 0, excel_header, title_css) - worksheet.freeze_panes(1, 0) - counter = 0 - pity_counter = 0 - for gacha in gachaDictList: - time_str = gacha["time"] - name = gacha["name"] - item_type = gacha["item_type"] - rank_type = gacha["rank_type"] - gacha_type = gacha["gacha_type"] - uid = gacha["uid"] - gacha_type_name = gacha_type_dict.get(gacha_type, "") - counter = counter + 1 - pity_counter = pity_counter + 1 - excel_data = [time_str, name, item_type, rank_type, gacha_type_name, counter, pity_counter] - excel_data[3] = int(excel_data[3]) - worksheet.write_row(counter, 0, excel_data, content_css) - if excel_data[3] == 5: - pity_counter = 0 - - star_5 = workbook.add_format({"color": "#bd6932", "bold": True}) - star_4 = workbook.add_format({"color": "#a256e1", "bold": True}) - star_3 = workbook.add_format({"color": "#8e8e8e"}) - last_row = len(gachaDictList) # 最后一行 - last_col = len(excel_header) - 1 # 最后一列,zero indexed 所以要减 1 - worksheet.conditional_format(first_row, first_col, last_row, last_col, - {"type": "formula", "criteria": "=$D2=5", "format": star_5}) - worksheet.conditional_format(first_row, first_col, last_row, last_col, - {"type": "formula", "criteria": "=$D2=4", "format": star_4}) - worksheet.conditional_format(first_row, first_col, last_row, last_col, - {"type": "formula", "criteria": "=$D2=3", "format": star_3}) - - worksheet = workbook.add_worksheet("原始数据") - raw_data_header = ["count", "gacha_type", "id", "item_id", "item_type", "lang", "name", "rank_type", "time", "uid", - "uigf_gacha_type"] - worksheet.write_row(0, 0, raw_data_header) - - UIGF_data = convertUIGF(gachaLog, uid) - all_gachaDictList = UIGF_data["list"] - for all_counter, gacha in enumerate(all_gachaDictList): - count = gacha.get("count", "") - gacha_type = gacha.get("gacha_type", "") - id = gacha.get("id", "") - item_id = gacha.get("item_id", "") - item_type = gacha.get("item_type", "") - lang = gacha.get("lang", "") - name = gacha.get("name", "") - rank_type = gacha.get("rank_type", "") - time_str = gacha.get("time", "") - uid = gacha.get("uid", "") - uigf_gacha_type = gacha.get("uigf_gacha_type", "") - - excel_data = [count, gacha_type, id, item_id, item_type, lang, name, rank_type, time_str, uid, uigf_gacha_type] - worksheet.write_row(all_counter + 1, 0, excel_data) - workbook.close() diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/__init__.py b/LittlePaimon/plugins/Paimon_Gacha_Log/__init__.py index 91a8d0c..258e9a7 100644 --- a/LittlePaimon/plugins/Paimon_Gacha_Log/__init__.py +++ b/LittlePaimon/plugins/Paimon_Gacha_Log/__init__.py @@ -1,113 +1,61 @@ -import json -import re - from nonebot import on_command -from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent, GroupMessageEvent -from nonebot.params import CommandArg, Arg +from nonebot.adapters.onebot.v11 import MessageEvent from nonebot.plugin import PluginMetadata -from LittlePaimon.config import GACHA_LOG -from LittlePaimon.utils.files import load_json, save_json +from LittlePaimon.utils import logger from LittlePaimon.utils.message import CommandPlayer -from .api import toApi, checkApi -from .gacha_logs import get_data -from .get_img import get_gacha_log_img +from .data_source import get_gacha_log_img, get_gacha_log_data __plugin_meta__ = PluginMetadata( name="原神抽卡记录分析", description="小派蒙的原神抽卡记录模块", - usage=( - "1.[获取抽卡记录 (uid) (url)]提供url,获取原神抽卡记录,需要一定时间" - "2.[查看抽卡记录 (uid)]查看抽卡记录分析" - "3.[导出抽卡记录 (uid) (xlsx/json)]导出抽卡记录文件,上传到群文件中" - ), + usage='', extra={ - 'type': '原神抽卡记录', - "author": "惜月 <277073121@qq.com>", - "version": "0.1.3", + 'type': '原神抽卡记录', + "author": "惜月 <277073121@qq.com>", + "version": "3.0.0", 'priority': 10, }, ) -gacha_log_export = on_command('ckjldc', aliases={'抽卡记录导出', '导出抽卡记录'}, priority=12, block=True, state={ - 'pm_name': '抽卡记录导出', - 'pm_description': '将抽卡记录导出到群文件中', - 'pm_usage': '抽卡记录导出(uid)[xlsx/json]', - 'pm_priority': 3 - }) -gacha_log_update = on_command('ckjlgx', aliases={'抽卡记录更新', '更新抽卡记录', '获取抽卡记录'}, priority=12, block=True, state={ - 'pm_name': '获取抽卡记录', - 'pm_description': '从抽卡链接获取抽卡记录,链接可以通过祈愿页面断网取得', - 'pm_usage': '获取抽卡记录(uid)<链接>', - 'pm_priority': 1 - }) -gacha_log_show = on_command('ckjl', aliases={'抽卡记录', '查看抽卡记录'}, priority=12, block=True, state={ - 'pm_name': '查看抽卡记录', - 'pm_description': '查看你的抽卡记录分析', - 'pm_usage': '查看抽卡记录', - 'pm_priority': 2 - }) +update_log = on_command('更新抽卡记录', aliases={'抽卡记录更新', '获取抽卡记录'}, priority=12, block=True, state={ + 'pm_name': '更新抽卡记录', + 'pm_description': '*通过stoken更新原神抽卡记录', + 'pm_usage': '更新抽卡记录(uid)', + 'pm_priority': 1 +}) +show_log = on_command('查看抽卡记录', aliases={'抽卡记录'}, priority=12, block=True, state={ + 'pm_name': '查看抽卡记录', + 'pm_description': '*查看你的抽卡记录分析', + 'pm_usage': '查看抽卡记录(uid)', + 'pm_priority': 2 +}) + +running_update = [] -@gacha_log_export.handle() -async def ckjl(bot: Bot, event: GroupMessageEvent, player=CommandPlayer(1), msg: str = Arg('msg')): - player = player[0] - if match := re.search(r'(?Pxlsx|json)', msg): - filetype = match['filetype'] +@update_log.handle() +async def _(event: MessageEvent, player=CommandPlayer(1)): + if f'{player[0].user_id}-{player[0].uid}' in running_update: + await update_log.finish(f'UID{player[0].uid}已经在获取抽卡记录中,请勿重复发送') else: - filetype = 'xlsx' - filetype = f'gachaExport-{player.uid}.xlsx' if filetype == 'xlsx' else f'UIGF_gachaData-{player.uid}.json' - local_data = GACHA_LOG / filetype - if not local_data.exists(): - await gacha_log_export.finish('你在派蒙这里还没有抽卡记录哦,使用 更新抽卡记录 吧!', at_sender=True) - else: - await bot.upload_group_file(group_id=event.group_id, file=local_data, name=filetype) + running_update.append(f'{player[0].user_id}-{player[0].uid}') + await update_log.send(f'开始为UID{player[0].uid}更新抽卡记录,请稍候...') + try: + result = await get_gacha_log_data(player[0].user_id, player[0].uid) + await update_log.send(result, at_sender=True) + except Exception as e: + logger.info('原神抽卡记录', f'➤➤更新抽卡记录时出现错误:{e}') + await update_log.send(f'更新抽卡记录时出现错误:{e}') + running_update.remove(f'{player[0].user_id}-{player[0].uid}') -@gacha_log_update.handle() -async def update_ckjl(event: MessageEvent, msg: Message = CommandArg()): - url = None - if msg := msg.extract_plain_text().strip(): - if log_url := re.search(r'(https://webstatic.mihoyo.com/.*#/log)', msg): - url = log_url[1] - msg = msg.replace(url, '') - if not url: - await gacha_log_update.finish('你这个抽卡链接不对哦,应该是以https://开头、#/log结尾的!', at_sender=True) - user_data = load_json(GACHA_LOG / 'gacha_log_url.json') - if not url and str(event.user_id) in user_data: - url = user_data[str(event.user_id)] - await gacha_log_update.send('发现历史抽卡记录链接,尝试使用...') - else: - await gacha_log_update.finish('拿到游戏抽卡记录链接后,对派蒙说[获取抽卡记录 uid 链接]就可以啦\n获取抽卡记录链接的方式和vx小程序的是一样的,还请旅行者自己搜方法', - at_sender=True) - if str(event.user_id) not in user_data: - user_data[str(event.user_id)] = url - save_json(user_data, path=GACHA_LOG / 'gacha_log_url.json') - - url = toApi(url) - apiRes = await checkApi(url) - if apiRes != 'OK': - await gacha_log_update.finish(apiRes, at_sender=True) - await gacha_log_update.send('抽卡记录开始获取,请给派蒙一点时间...') - uid = await get_data(url) - - local_data = GACHA_LOG / f'gachaData-{uid}.json' - gacha_data = load_json(local_data) - gacha_img = await get_gacha_log_img(gacha_data, 'all') - await gacha_log_update.finish(gacha_img, at_sender=True) - - -@gacha_log_show.handle() -async def get_ckjl(event: MessageEvent, player=CommandPlayer(1), msg: str = Arg('msg')): - player = player[0] - if pool_type := re.search(r'(all|角色|武器|常驻|新手)', msg): - pool_type = pool_type[1] - else: - pool_type = 'all' - local_data = GACHA_LOG / f'gachaData-{player.uid}.json' - if not local_data.exists(): - await gacha_log_update.finish('你在派蒙这里还没有抽卡记录哦,对派蒙说 获取抽卡记录 吧!', at_sender=True) - with open(local_data, 'r', encoding="utf-8") as f: - gacha_data = json.load(f) - gacha_img = await get_gacha_log_img(gacha_data, pool_type) - await gacha_log_update.finish(gacha_img, at_sender=True) +@show_log.handle() +async def _(event: MessageEvent, player=CommandPlayer(1)): + logger.info('原神抽卡记录', '➤', {'用户': player[0].user_id, 'UID': player[0].uid}, '开始绘制抽卡记录图片', True) + try: + result = await get_gacha_log_img(player[0].user_id, player[0].uid, event.sender.nickname) + await show_log.finish(result, at_sender=True) + except Exception as e: + logger.info('原神抽卡记录', f'➤➤绘制抽卡记录图片时出现错误:{e}') + await update_log.finish(f'绘制抽卡记录分析时出现错误:{e}') diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/api.py b/LittlePaimon/plugins/Paimon_Gacha_Log/api.py deleted file mode 100644 index f772c01..0000000 --- a/LittlePaimon/plugins/Paimon_Gacha_Log/api.py +++ /dev/null @@ -1,59 +0,0 @@ -from urllib import parse - -from LittlePaimon.utils import aiorequests - - -def toApi(url): - spliturl = str(url).replace('amp;', '').split("?") - if "webstatic-sea" in spliturl[0] or "hk4e-api-os" in spliturl[0]: - spliturl[0] = "https://hk4e-api-os.mihoyo.com/event/gacha_info/api/getGachaLog" - else: - spliturl[0] = "https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog" - url = "?".join(spliturl) - return url - - -def getApi(url, gachaType, size, page, end_id=""): - parsed = parse.urlparse(url) - querys = parse.parse_qsl(str(parsed.query)) - param_dict = dict(querys) - param_dict["size"] = size - param_dict["gacha_type"] = gachaType - param_dict["page"] = page - param_dict["lang"] = "zh-cn" - param_dict["end_id"] = end_id - param = parse.urlencode(param_dict) - path = str(url).split("?")[0] - return f"{path}?{param}" - - -async def checkApi(url): - try: - j = await aiorequests.get(url=url) - j = j.json() - except Exception as e: - return f'API请求解析出错:{e}' - - if not j["data"]: - if j["message"] == "authkey error": - return "authkey错误,请重新获取链接给派蒙!" - elif j["message"] == "authkey timeout": - return "authkey已过期,请重新获取链接给派蒙!" - else: - return f'数据为空,错误代码:{j["message"]}' - return 'OK' - - -def getQueryVariable(url, variable): - query = str(url).split("?")[1] - vars = query.split("&") - return next((v.split("=")[1] for v in vars if v.split("=")[0] == variable), "") - - -async def getGachaInfo(url): - region = getQueryVariable(url, "region") - lang = getQueryVariable(url, "lang") - gachaInfoUrl = f"https://webstatic.mihoyo.com/hk4e/gacha_info/{region}/items/{lang}.json" - - resp = await aiorequests.get(url=gachaInfoUrl) - return resp.json() diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/data_source.py b/LittlePaimon/plugins/Paimon_Gacha_Log/data_source.py new file mode 100644 index 0000000..7910cb1 --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Gacha_Log/data_source.py @@ -0,0 +1,137 @@ +import asyncio +import time +import datetime +from typing import Dict, Union, Tuple + +from LittlePaimon import DRIVER +from LittlePaimon.database.models import PlayerInfo +from LittlePaimon.config import GACHA_LOG +from LittlePaimon.utils.api import get_authkey_by_stoken +from LittlePaimon.utils import aiorequests, logger +from LittlePaimon.utils.files import load_json +from .models import GachaItem, GachaLogInfo, GACHA_TYPE_LIST +from .draw import draw_gacha_log + +GACHA_LOG_API = 'https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog' +HEADERS: Dict[str, str] = { + 'x-rpc-app_version': '2.11.1', + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (' + 'KHTML, like Gecko) miHoYoBBS/2.11.1', + 'x-rpc-client_type': '5', + 'Referer': 'https://webstatic.mihoyo.com/', + 'Origin': 'https://webstatic.mihoyo.com', +} +PARAMS: Dict[str, Union[str, int]] = { + 'authkey_ver': '1', + 'sign_type': '2', + 'auth_appid': 'webview_gacha', + 'init_type': '200', + 'gacha_id': 'fecafa7b6560db5f3182222395d88aaa6aaac1bc', + 'lang': 'zh-cn', + 'device_type': 'mobile', + 'plat_type': 'ios', + 'game_biz': 'hk4e_cn', + 'size': '20', +} + + +def load_history_info(user_id: str, uid: str) -> Tuple[GachaLogInfo, bool]: + """ + 读取历史抽卡记录数据 + :param user_id: 用户id + :param uid: 原神uid + :return: 抽卡记录数据 + """ + file_path = GACHA_LOG / f'gacha_log-{user_id}-{uid}.json' + if file_path.exists(): + return GachaLogInfo.parse_obj(load_json(file_path)), True + else: + return GachaLogInfo(user_id=user_id, + uid=uid, + update_time=datetime.datetime.now()), False + + +def save_gacha_log_info(user_id: str, uid: str, info: GachaLogInfo): + """ + 保存抽卡记录数据 + :param user_id: 用户id + :param uid: 原神uid + :param info: 抽卡记录数据 + """ + save_path = GACHA_LOG / f'gacha_log-{user_id}-{uid}.json' + save_path_bak = GACHA_LOG / f'gacha_log-{user_id}-{uid}.json.bak' + # 将旧数据备份一次 + if save_path.exists(): + if save_path_bak.exists(): + save_path_bak.unlink() + save_path.rename(save_path.parent / f'{save_path.name}.bak') + # 写入新数据 + with save_path.open('w', encoding='utf-8') as f: + f.write(info.json(ensure_ascii=False, indent=4)) + + +async def get_gacha_log_data(user_id: str, uid: str): + """ + 使用authkey获取抽卡记录数据,并合并旧数据 + :param user_id: 用户id + :param uid: 原神uid + :return: 更新结果 + """ + new_num = 0 + server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01' + authkey, state, cookie_info = await get_authkey_by_stoken(user_id, uid) + if not state: + return authkey + gacha_log, _ = load_history_info(user_id, uid) + params = PARAMS.copy() + params['region'] = server_id + params['authkey'] = authkey + logger.info('原神抽卡记录', '➤', {'用户': user_id, 'UID': uid}, '开始更新抽卡记录', True) + for pool_id, pool_name in GACHA_TYPE_LIST.items(): + params['gacha_type'] = pool_id + end_id = 0 + for page in range(1, 999): + params['page'] = page + params['end_id'] = end_id + params['timestamp'] = str(int(time.time())) + data = await aiorequests.get(url=GACHA_LOG_API, + headers=HEADERS, + params=params) + data = data.json() + if 'data' not in data or 'list' not in data['data']: + logger.info('原神抽卡记录', '➤➤', {}, 'Stoken已失效,更新失败', False) + cookie_info.stoken = None + await cookie_info.save() + return f'UID{uid}的Stoken已失效,请重新绑定后再更新抽卡记录' + data = data['data']['list'] + if not data: + break + for item in data: + item_info = GachaItem.parse_obj(item) + if item_info not in gacha_log.item_list[pool_name]: + gacha_log.item_list[pool_name].append(item_info) + new_num += 1 + end_id = data[-1]['id'] + await asyncio.sleep(1) + logger.info('原神抽卡记录', f'➤➤{pool_name}', {}, '获取完成', True) + for i in gacha_log.item_list.values(): + i.sort(key=lambda x: x.time) + gacha_log.update_time = datetime.datetime.now() + save_gacha_log_info(user_id, uid, gacha_log) + if new_num == 0: + return f'UID{uid}更新完成,本次没有新增数据' + else: + return f'UID{uid}更新完成,本次共新增{new_num}条抽卡记录' + + +async def get_gacha_log_img(user_id: str, uid: str, nickname: str): + data, state = load_history_info(user_id, uid) + if not state: + return f'UID{uid}还没有抽卡记录数据,请先更新' + player_info = await PlayerInfo.get_or_none(user_id=user_id, uid=uid) + if player_info: + return await draw_gacha_log(player_info.user_id, player_info.uid, player_info.nickname, player_info.signature, data) + else: + return await draw_gacha_log(user_id, uid, nickname, None, data) + + diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/draw.py b/LittlePaimon/plugins/Paimon_Gacha_Log/draw.py new file mode 100644 index 0000000..68b3ee8 --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Gacha_Log/draw.py @@ -0,0 +1,209 @@ +import asyncio +import datetime +import math +from typing import Tuple, List, Optional + +from LittlePaimon.config import RESOURCE_BASE_PATH +from LittlePaimon.utils.files import load_image +from LittlePaimon.utils.message import MessageBuild +from LittlePaimon.utils.image import PMImage, get_qq_avatar, font_manager as fm +from .models import GachaLogInfo, FiveStarItem, FourStarItem + +avatar_point = [69, 156, 259, 358, 456, 558, 645, 746, 840, 945] +line_point = [88, 182, 282, 378, 477, 574, 673, 769, 864, 967] +bar_color = [('#b6d6f2', '#3d6e99'), ('#c8b6f2', '#593d99'), ('#abede0', '#3a9382')] +name_level_color = ['#ff3600', '#ff7800', '#ffb400', 'black'] +small_avatar_cache = {} +avatar_cache = {} + + +async def get_avatar(qid: str, size: Tuple[int, int] = (146, 146)) -> PMImage: + avatar = await get_qq_avatar(qid) + await avatar.resize(size) + await avatar.to_circle('circle') + await avatar.add_border(6, '#ddcdba', 'circle') + return avatar + + +async def small_avatar(info: FiveStarItem): + if info.name in small_avatar_cache: + return small_avatar_cache[info.name] + bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'small_circle.png')) + img = PMImage( + await load_image(RESOURCE_BASE_PATH / ('avatar' if info.type == '角色' else 'weapon') / f'{info.icon}.png', + size=(42, 42))) + await img.to_circle('circle') + await bg.paste(img.image, (2, 2)) + small_avatar_cache[info.name] = bg + return bg + + +async def detail_avatar(info: FiveStarItem): + if info.name in avatar_cache: + bg = avatar_cache[info.name] + else: + bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'item_avatar_5.png')) + img = PMImage( + await load_image(RESOURCE_BASE_PATH / ('avatar' if info.type == '角色' else 'weapon') / f'{info.icon}.png', + size=(123, 123))) + await img.to_circle('circle') + await bg.paste(img.image, (14, 26)) + await bg.text(info.name, (0, bg.width), 162, fm.get('hywh', 24), + '#ff3600' if info.name not in {'迪卢克', '刻晴', '莫娜', '七七', '琴'} else '#33231a', 'center') + avatar_cache[info.name] = bg.copy() + if info.count < (20 if info.type == '角色' else 15): + color = name_level_color[0] + elif (20 if info.type == '角色' else 15) <= info.count < (40 if info.type == '角色' else 30): + color = name_level_color[1] + elif (40 if info.type == '角色' else 30) <= info.count < (70 if info.type == '角色' else 60): + color = name_level_color[2] + else: + color = name_level_color[3] + await bg.text(str(info.count), 144, 6, fm.get('bahnschrift_regular', 48, 'Bold'), color, 'right') + return bg + + +async def draw_pool_detail(pool_name: str, data: List[FiveStarItem], total_count: int, not_out: int) -> Optional[ + PMImage]: + if not data: + return None + total_height = 181 + (446 if len(data) > 3 else 0) + 47 + 204 * math.ceil(len(data) / 6) + 20 + 60 + img = PMImage(size=(1009, total_height), mode='RGBA', color=(255, 255, 255, 0)) + # 橙线 + await img.paste(await load_image(RESOURCE_BASE_PATH / 'general' / 'line.png'), (0, 0)) + await img.text(f'{pool_name[:2]}卡池', 25, 11, fm.get('hywh', 30), 'white') + # 数据 + await img.text('平均出货', 174, 142, fm.get('hywh', 24), (24, 24, 24, 102)) + await img.text(str(round((total_count - not_out) / len(data), 2)), (176, 270), 84, + fm.get('bahnschrift_regular', 48, 'Regular'), + '#252525', 'center') + await img.text('总抽卡数', 471, 142, fm.get('hywh', 24), (24, 24, 24, 102)) + await img.text(str(total_count), (472, 567), 84, fm.get('bahnschrift_regular', 48, 'Regular'), + '#252525', 'center') + await img.text('未出五星', 753, 142, fm.get('hywh', 24), (24, 24, 24, 102)) + await img.text(str(not_out), (755, 848), 84, fm.get('bahnschrift_regular', 48, 'Regular'), + '#252525', 'center') + # 折线图 + if len(data) > 3: + last_point = None + await img.paste(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'broken_line_bg.png'), (1, 181)) + for chara in data: + height = int(583 - (chara.count / 90) * 340) + if last_point: + await img.draw_line(last_point, (line_point[data.index(chara)], height), '#ff6f30', 4) + last_point = (line_point[data.index(chara)], height) + for chara in data: + height = int(583 - (chara.count / 90) * 340) + point = avatar_point[data.index(chara)] + await img.paste(await small_avatar(chara), (point, height - 23)) + await img.text(str(chara.count), (point, point + 44), height - 48, fm.get('bahnschrift_regular', 30, + 'Regular'), '#040404', 'center') + # 详细数据统计 + chara_bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'detail_bg.png')) + await chara_bg.stretch((47, chara_bg.height - 20), 204 + 204 * (len(data) // 6), 'height') + await img.paste(chara_bg, (1, 655 if len(data) > 3 else 181)) + await asyncio.gather( + *[img.paste(await detail_avatar(chara), + (18 + data.index(chara) % 6 * 163, (708 if len(data) > 3 else 234) + data.index(chara) // 6 * 204)) + for chara in data]) + return img + + +async def draw_four_star(info: FourStarItem) -> PMImage: + bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'item_avatar_4.png')) + img = PMImage( + await load_image(RESOURCE_BASE_PATH / ('avatar' if info.type == '角色' else 'weapon') / f'{info.icon}.png', + size=(123, 123))) + await img.to_circle('circle') + await bg.paste(img, (34, 26)) + await bg.text(info.name, (0, bg.width), 163, fm.get('hywh', 24), '#221a33', 'center') + await bg.text(str(info.num['角色祈愿']), (4, 64), 209, fm.get('bahnschrift_regular', 36, 'Bold'), '#3d6e99', + 'center') + await bg.text(str(info.num['武器祈愿']), (65, 125), 209, fm.get('bahnschrift_regular', 36, 'Bold'), '#593d99', + 'center') + await bg.text(str(info.num['常驻祈愿'] + info.num['新手祈愿']), (126, 186), 209, fm.get('bahnschrift_regular', 36, 'Bold'), + '#3a9381', + 'center') + return bg + + +async def draw_four_star_detail(data: List[FourStarItem]): + bar = await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'four_star_bar.png') + total_height = 105 + 260 * math.ceil(len(data) / 5) + bg = PMImage(size=(1008, total_height), mode='RGBA', color=(255, 255, 255, 0)) + await bg.paste(bar, (0, 0)) + await asyncio.gather(*[bg.paste(await draw_four_star(chara), (data.index(chara) % 5 * 204, 105 + data.index(chara) // 5 * 260)) for chara in data]) + return bg + + +async def draw_gacha_log(user_id: str, uid: str, nickname: Optional[str], signature: Optional[str], gacha_log: GachaLogInfo): + img = PMImage(size=(1080, 8000), mode='RGBA', color=(255, 255, 255, 0)) + bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'bg.png')) + line = await load_image(RESOURCE_BASE_PATH / 'general' / 'line.png') + # 头像 + avatar = await get_avatar(user_id, (108, 108)) + await img.paste(avatar, (38, 45)) + # 昵称 + await img.text(nickname, 166, 49, fm.get('hywh', 48), '#252525') + # 签名和uid + if signature: + await img.text(signature, 165, 116, fm.get('hywh', 32), '#252525') + nickname_length = img.text_length(nickname, fm.get('hywh', 40)) + await img.text(f'UID{uid}', 166 + nickname_length + 36, 58, + fm.get('bahnschrift_regular', 48, 'Regular'), + '#252525') + else: + await img.text(f'UID{uid}', 165, 103, fm.get('bahnschrift_regular', 48, 'Regular'), '#252525') + + data5, data4, data_not = gacha_log.get_statistics() + # 数据总览 + await img.paste(line, (36, 181)) + await img.text('数据总览', 60, 192, fm.get('hywh', 30), 'white') + await img.text(f'CREATED BY LITTLEPAIMON AT {datetime.datetime.now().strftime("%m-%d %H:%M")}', 1025, 196, + fm.get('bahnschrift_regular', 30), '#252525', 'right') + total_gacha_count = sum(len(pool) for pool in gacha_log.item_list.values()) + out_gacha_count = total_gacha_count - sum(data_not.values()) + total_five_star_count = sum(len(pool) for pool in data5.values()) + five_star_average = round(out_gacha_count / total_five_star_count, 2) if total_five_star_count else 0 + await img.text('平均出货', 209, 335, fm.get('hywh', 24), (24, 24, 24, 102)) + await img.text(str(five_star_average), (211, 305), 286, fm.get('bahnschrift_regular', 48), '#040404', 'center') + await img.text('总抽卡数', 506, 335, fm.get('hywh', 24), (24, 24, 24, 102)) + await img.text(str(total_gacha_count), (507, 602), 286, fm.get('bahnschrift_regular', 48), '#040404', 'center') + await img.text('总计出金', 788, 335, fm.get('hywh', 24), (24, 24, 24, 102)) + await img.text(str(total_five_star_count), (789, 884), 286, fm.get('bahnschrift_regular', 48), '#040404', 'center') + four_star_detail = await draw_four_star_detail(list(data4.values())) + if total_five_star_count: + chara_pool_per = round(len(data5['角色祈愿']) / total_five_star_count, 3) + weapon_pool_per = round(len(data5['武器祈愿']) / total_five_star_count, 3) + new_pool_per = round((len(data5['常驻祈愿']) + len(data5['新手祈愿'])) / total_five_star_count, 3) + now_used_width = 56 + pers = [chara_pool_per, weapon_pool_per, new_pool_per] + for per in pers: + if per >= 0.03: + await img.draw_rectangle((now_used_width, 399, now_used_width + int(per * 967), 446), + bar_color[pers.index(per)][0]) + if per >= 0.1: + await img.text(f'{per * 100}%', now_used_width + 18, 410, fm.get('bahnschrift_regular', 30, 'Bold'), + bar_color[pers.index(per)][1]) + now_used_width += int(per * 967) + await img.paste(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'text.png'), (484, 464)) + now_height = 525 + for pool_name, data in data5.items(): + pool_detail_img = await draw_pool_detail(pool_name, data, len(gacha_log.item_list[pool_name]), + data_not[pool_name]) + if pool_detail_img: + await img.paste(pool_detail_img, (36, now_height)) + now_height += pool_detail_img.height + now_height += 10 + await img.paste(four_star_detail, (36, now_height)) + now_height += four_star_detail.height + 30 + + await img.crop((0, 0, 1080, now_height)) + await bg.stretch((50, bg.height - 50), now_height - 100, 'height') + + else: + await img.paste(four_star_detail, (36, 410)) + await img.crop((0, 0, 1080, 410 + four_star_detail.height + 30)) + await bg.stretch((50, bg.height - 50), img.height - 100, 'height') + await bg.paste(img, (0, 0)) + return MessageBuild.Image(bg, mode='RGB', quality=80) diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/gacha_logs.py b/LittlePaimon/plugins/Paimon_Gacha_Log/gacha_logs.py deleted file mode 100644 index 1d3ee20..0000000 --- a/LittlePaimon/plugins/Paimon_Gacha_Log/gacha_logs.py +++ /dev/null @@ -1,83 +0,0 @@ -from asyncio import sleep -from pathlib import Path - -from LittlePaimon.utils import aiorequests -from LittlePaimon.utils.files import load_json, save_json -from .UIGF_and_XLSX import convertUIGF, writeXLSX -from .api import getApi -from .meta_data import gachaQueryTypeIds, gachaQueryTypeDict - -data_path = Path() / 'data' / 'LittlePaimon' / 'user_data' / 'gacha_log_data' - - -async def getGachaLogs(url, gachaTypeId): - size = "20" - # api限制一页最大20 - gachaList = [] - end_id = "0" - for page in range(1, 9999): - api = getApi(url, gachaTypeId, size, page, end_id) - resp = await aiorequests.get(url=api) - j = resp.json() - gacha = j["data"]["list"] - if not len(gacha): - break - gachaList.extend(iter(gacha)) - end_id = j["data"]["list"][-1]["id"] - await sleep(0.5) - - return gachaList - - -def mergeDataFunc(localData, gachaData): - for banner in gachaQueryTypeDict: - bannerLocal = localData["gachaLog"][banner] - bannerGet = gachaData["gachaLog"][banner] - if bannerGet != bannerLocal: - flaglist = [1] * len(bannerGet) - loc = [[i["time"], i["name"]] for i in bannerLocal] - for i in range(len(bannerGet)): - gachaGet = bannerGet[i] - get = [gachaGet["time"], gachaGet["name"]] - if get not in loc: - flaglist[i] = 0 - tempData = [] - for i in range(len(bannerGet)): - if flaglist[i] == 0: - gachaGet = bannerGet[i] - tempData.insert(0, gachaGet) - for i in tempData: - localData["gachaLog"][banner].insert(0, i) - return localData - - -async def get_data(url): - gachaData = {"gachaLog": {}} - for gachaTypeId in gachaQueryTypeIds: - gachaLog = await getGachaLogs(url, gachaTypeId) - gachaData["gachaLog"][gachaTypeId] = gachaLog - - uid_flag = 1 - for gachaType in gachaData["gachaLog"]: - for log in gachaData["gachaLog"][gachaType]: - if uid_flag and log["uid"]: - gachaData["uid"] = log["uid"] - uid_flag = 0 - - uid = gachaData["uid"] - localDataFilePath = data_path / f"gachaData-{uid}.json" - - if localDataFilePath.is_file(): - localData = load_json(localDataFilePath) - mergeData = mergeDataFunc(localData, gachaData) - else: - mergeData = gachaData - mergeData["gachaType"] = gachaQueryTypeDict - # 写入json - save_json(mergeData, localDataFilePath) - # 写入UIGF、json - UIGF_data = convertUIGF(mergeData['gachaLog'], uid) - save_json(UIGF_data, data_path / f"UIGF_gachaData-{uid}.json") - # 写入xlsx - writeXLSX(uid, mergeData['gachaLog'], gachaQueryTypeIds) - return uid diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/get_img.py b/LittlePaimon/plugins/Paimon_Gacha_Log/get_img.py deleted file mode 100644 index 7f0dfd3..0000000 --- a/LittlePaimon/plugins/Paimon_Gacha_Log/get_img.py +++ /dev/null @@ -1,145 +0,0 @@ -from pathlib import Path - -from PIL import Image, ImageDraw, ImageFont - -from LittlePaimon.utils.alias import get_short_name -from LittlePaimon.utils.message import MessageBuild - -res_path = Path() / 'resources' / 'LittlePaimon' - - -def get_font(size): - return ImageFont.truetype(str(res_path / 'msyh.ttc'), size) - - -async def get_circle_avatar(avatar, size): - avatar = Image.open(res_path / 'thumb' / f'{avatar}.png') - w, h = avatar.size - bg = Image.new('RGBA', (w, h), (213, 153, 77, 255)) - bg.alpha_composite(avatar, (0, 0)) - bg = bg.resize((size, size)) - scale = 5 - mask = Image.new('L', (size * scale, size * scale), 0) - draw = ImageDraw.Draw(mask) - draw.ellipse((0, 0, size * scale, size * scale), fill=255) - mask = mask.resize((size, size), Image.ANTIALIAS) - ret_img = bg.copy() - ret_img.putalpha(mask) - return ret_img - - -async def sort_data(gacha_data): - sprog_data = {'type': '新手', 'total_num': 0, '5_star': [], '4_star': [], '5_gacha': 0, '4_gacha': 0} - permanent_data = {'type': '常驻', 'total_num': 0, '5_star': [], '4_star': [], '5_gacha': 0, '4_gacha': 0} - role_data = {'type': '角色', 'total_num': 0, '5_star': [], '4_star': [], '5_gacha': 0, '4_gacha': 0} - weapon_data = {'type': '武器', 'total_num': 0, '5_star': [], '4_star': [], '5_gacha': 0, '4_gacha': 0} - new_gacha_data = [sprog_data, permanent_data, role_data, weapon_data] - i = 0 - for pool in gacha_data['gachaLog'].values(): - pool.reverse() - new_gacha_data[i]['total_num'] = len(pool) - for p in pool: - if p['rank_type'] == "5": - new_gacha_data[i]['5_star'].append((p['name'], new_gacha_data[i]['5_gacha'] + 1)) - new_gacha_data[i]['5_gacha'] = 0 - new_gacha_data[i]['4_gacha'] = 0 - elif p['rank_type'] == "4": - new_gacha_data[i]['4_star'].append((p['name'], new_gacha_data[i]['4_gacha'] + 1)) - new_gacha_data[i]['5_gacha'] += 1 - new_gacha_data[i]['4_gacha'] = 0 - else: - new_gacha_data[i]['5_gacha'] += 1 - new_gacha_data[i]['4_gacha'] += 1 - i += 1 - return new_gacha_data - - -async def draw_gacha_log(data): - if data['total_num'] == 0: - return None - top = Image.open(res_path / 'player_card' / 'gacha_log_top.png') - mid = Image.open(res_path / 'player_card' / '卡片身体.png').resize((768, 80)) - bottom = Image.open(res_path / 'player_card' / '卡片底部.png').resize((768, 51)) - five_star = data['5_star'] - col = int(len(five_star) / 6) - if not len(five_star) % 6 == 0: - col += 1 - top_draw = ImageDraw.Draw(top) - top_draw.text((348, 30), f'{data["type"]}池', font=get_font(24), fill='#F8F5F1') - top_draw.text((145 - 6 * len(str(data["total_num"])), 88), f'{data["total_num"]}', font=get_font(24), fill='black') - five_ave = round(sum([x[1] for x in five_star]) / len(five_star), 1) if five_star else ' ' - top_draw.text((321 - 10 * len(str(five_ave)), 88), f'{five_ave}', font=get_font(24), - fill='black' if five_ave != ' ' and five_ave > 60 else 'red') - five_per = round(len(five_star) / (data['total_num'] - data['5_gacha']) * 100, 2) if five_star else -1 - five_per_str = str(five_per) + '%' if five_per > -1 else ' ' - top_draw.text((427, 88), f'{five_per_str}', font=get_font(24), fill='black' if five_per < 1.7 else 'red') - five_up = round(len([x[0] for x in five_star if not x[0] in ['刻晴', '迪卢克', '七七', '莫娜', '琴']]) / len(five_star) * 100, - 1) if five_star else -1 - five_up_str = str(five_up) + '%' if five_per > -1 else ' ' - top_draw.text((578 if len(five_up_str) != 6 else 569, 88), f'{five_up_str}', font=get_font(24), - fill='black' if five_up < 75 else 'red') - most_five = sorted(five_star, key=lambda x: x[1], reverse=False)[0][0] if five_star else ' ' - top_draw.text((152 - 14 * len(most_five), 163), f'{most_five}', font=get_font(24), fill='red') - four_ave = round(sum([x[1] for x in data['4_star']]) / len(data['4_star']), 1) if data['4_star'] else ' ' - top_draw.text((316 - 10 * len(str(four_ave)), 163), f'{four_ave}', font=get_font(24), - fill='black' if four_ave != ' ' and four_ave > 7 else 'red') - top_draw.text((461 - 6 * len(str(data['5_gacha'])), 163), f'{data["5_gacha"]}', font=get_font(24), fill='black') - top_draw.text((604, 163), f'{data["4_gacha"]}', font=get_font(24), fill='black') - bg_img = Image.new('RGBA', (768, 288 + col * 80 - (20 if col > 0 else 0) + 51), (0, 0, 0, 0)) - bg_img.paste(top, (0, 0)) - for i in range(0, col): - bg_img.paste(mid, (0, 288 + i * 80)) - bg_img.paste(bottom, (0, 288 + col * 80 - (20 if col > 0 else 0))) - bg_draw = ImageDraw.Draw(bg_img) - n = 0 - for c in five_star: - avatar = await get_circle_avatar(c[0], 45) - f = 10 if data['type'] == '武器' else 0 - if c[1] <= 20: - color = 'red' - elif 20 < c[1] <= 50 - f: - color = 'orangered' - elif 50 - f < c[1] < 70 - f: - color = 'darkorange' - else: - color = 'black' - bg_img.alpha_composite(avatar, (30 + 120 * (n % 6), 298 + 80 * int(n / 6))) - name = get_short_name(c[0]) - bg_draw.text((111 + 120 * (n % 6) - 8 * len(name), 298 + 80 * int(n / 6)), name, font=get_font(16), fill=color) - bg_draw.text((107 - 5 * len(str(c[1])) + 120 * (n % 6), 317 + 80 * int(n / 6)), f'[{c[1]}]', font=get_font(16), - fill=color) - n += 1 - return bg_img - - -async def get_gacha_log_img(gacha_data, pool): - all_gacha_data = await sort_data(gacha_data) - if pool != 'all': - img = None - for pd in all_gacha_data: - if pd['type'] == pool: - img = await draw_gacha_log(pd) - break - if not img: - return '这个池子没有抽卡记录哦' - total_height = img.size[1] - else: - img_list = [] - total_height = 0 - now_height = 0 - for pd in all_gacha_data: - p_img = await draw_gacha_log(pd) - if p_img: - img_list.append(p_img) - total_height += p_img.size[1] - if not img_list: - return '没有找到任何抽卡记录诶!' - img = Image.new('RGBA', (768, total_height), (0, 0, 0, 255)) - for i in img_list: - img.paste(i, (0, now_height)) - now_height += i.size[1] - img_draw = ImageDraw.Draw(img) - img_draw.text((595, 44), f'UID:{gacha_data["uid"]}', font=get_font(16), fill='black') - img_draw.text((530, total_height - 45), 'Created by LittlePaimon', font=get_font(16), fill='black') - - return MessageBuild.Image(img, mode='RGB') diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/meta_data.py b/LittlePaimon/plugins/Paimon_Gacha_Log/meta_data.py deleted file mode 100644 index 8520fee..0000000 --- a/LittlePaimon/plugins/Paimon_Gacha_Log/meta_data.py +++ /dev/null @@ -1,10 +0,0 @@ -gachaQueryTypeIds = ["100", "200", "301", "302"] -gachaQueryTypeNames = ["新手祈愿", "常驻祈愿", "角色活动祈愿", "武器活动祈愿"] -gachaQueryTypeDict = dict(zip(gachaQueryTypeIds, gachaQueryTypeNames)) -gacha_type_dict = { - "100": "新手祈愿", - "200": "常驻祈愿", - "301": "角色活动祈愿", - "302": "武器活动祈愿", - "400": "角色活动祈愿-2", -} diff --git a/LittlePaimon/plugins/Paimon_Gacha_Log/models.py b/LittlePaimon/plugins/Paimon_Gacha_Log/models.py new file mode 100644 index 0000000..7df542f --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Gacha_Log/models.py @@ -0,0 +1,81 @@ +import datetime +from typing import List, Dict, Union, Tuple +from pydantic import BaseModel +from LittlePaimon.utils.alias import get_chara_icon, get_weapon_icon + +GACHA_TYPE_LIST = {'100': '新手祈愿', '200': '常驻祈愿', '302': '武器祈愿', '301': '角色祈愿', '400': '角色祈愿'} + + +class FiveStarItem(BaseModel): + name: str + icon: str + count: int + type: str + + +class FourStarItem(BaseModel): + name: str + icon: str + type: str + num: Dict[str, int] = { + '角色祈愿': 0, + '武器祈愿': 0, + '常驻祈愿': 0, + '新手祈愿': 0} + + +class GachaItem(BaseModel): + id: str + name: str + gacha_type: str + item_type: str + rank_type: str + time: datetime.datetime + + +class GachaLogInfo(BaseModel): + user_id: str + uid: str + update_time: datetime.datetime + item_list: Dict[str, List[GachaItem]] = { + '角色祈愿': [], + '武器祈愿': [], + '常驻祈愿': [], + '新手祈愿': [], + } + + def get_statistics(self) -> Tuple[Dict[str, List[FiveStarItem]], Dict[str, FourStarItem], Dict[str, int]]: + gacha_data_five: Dict[str, List[FiveStarItem]] = { + '角色祈愿': [], + '武器祈愿': [], + '常驻祈愿': [], + '新手祈愿': [], + } + gacha_data_four: Dict[str, FourStarItem] = {} + gacha_not_out: Dict[str, int] = {} + for pool_name, item_list in self.item_list.items(): + count_now = 0 + for item in item_list: + if item.rank_type == '5': + gacha_data_five[pool_name].append( + FiveStarItem( + name=item.name, + icon=get_chara_icon(name=item.name) if item.item_type == '角色' else get_weapon_icon( + item.name), + count=count_now + 1, + type=item.item_type)) + count_now = 0 + else: + count_now += 1 + if item.rank_type == '4': + if item.name in gacha_data_four: + gacha_data_four[item.name].num[pool_name] += 1 + else: + gacha_data_four[item.name] = FourStarItem( + name=item.name, + icon=get_chara_icon(name=item.name) if item.item_type == '角色' else get_weapon_icon( + item.name), + type=item.item_type) + gacha_data_four[item.name].num[pool_name] = 1 + gacha_not_out[pool_name] = count_now + return gacha_data_five, gacha_data_four, gacha_not_out diff --git a/LittlePaimon/plugins/Paimon_Info/draw_abyss.py b/LittlePaimon/plugins/Paimon_Info/draw_abyss.py index f82d4ec..9288a06 100644 --- a/LittlePaimon/plugins/Paimon_Info/draw_abyss.py +++ b/LittlePaimon/plugins/Paimon_Info/draw_abyss.py @@ -58,35 +58,35 @@ async def draw_abyss_card(info: AbyssInfo): # 最多击破 await img.text(str(info.max_defeat.value), (370, 473), 357, fm.get('bahnschrift_regular.ttf', 56), '#40342d', 'center') - chara_img = PMImage(await load_image(RESOURCE_BASE_PATH / 'thumb' / f'{info.max_defeat.name}.png', size=(96, 96))) + chara_img = PMImage(await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{info.max_defeat.icon}.png', size=(96, 96))) await chara_img.to_circle('circle') await img.paste(chara_img, (373, 248)) # 战技次数 await img.text(str(info.max_normal_skill.value), (532, 635), 357, fm.get('bahnschrift_regular.ttf', 56), '#40342d', 'center') chara_img = PMImage( - await load_image(RESOURCE_BASE_PATH / 'thumb' / f'{info.max_normal_skill.name}.png', size=(96, 96))) + await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{info.max_normal_skill.icon}.png', size=(96, 96))) await chara_img.to_circle('circle') await img.paste(chara_img, (536, 248)) # 爆发次数 await img.text(str(info.max_energy_skill.value), (693, 796), 357, fm.get('bahnschrift_regular.ttf', 56), '#40342d', 'center') chara_img = PMImage( - await load_image(RESOURCE_BASE_PATH / 'thumb' / f'{info.max_energy_skill.name}.png', size=(96, 96))) + await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{info.max_energy_skill.icon}.png', size=(96, 96))) await chara_img.to_circle('circle') await img.paste(chara_img, (696, 248)) # 最深抵达 await img.text(str(info.max_floor), (838, 1038), 298, fm.get('bahnschrift_regular.ttf', 60), '#40342d', 'center') # 最强一击 circle = await load_image(RESOURCE_BASE_PATH / 'general' / 'orange_circle.png') - chara_img = PMImage(await load_image(RESOURCE_BASE_PATH / 'thumb' / f'{info.max_damage.name}.png', size=(205, 205))) + chara_img = PMImage(await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{info.max_damage.icon}.png', size=(205, 205))) await chara_img.to_circle('circle') await img.text('最强一击', 270, 520, fm.get('SourceHanSansCN-Bold.otf', 48), '#40342d') await img.text(str(info.max_damage.value), 270, 590, fm.get('bahnschrift_bold.ttf', 72, 'Bold'), '#40342d') await img.paste(circle, (46, 485)) await img.paste(chara_img, (49, 488)) # 最多承伤 - chara_img = PMImage(await load_image(RESOURCE_BASE_PATH / 'thumb' / f'{info.max_take_damage.name}.png', size=(205, 205))) + chara_img = PMImage(await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{info.max_take_damage.icon}.png', size=(205, 205))) await chara_img.to_circle('circle') await img.text('最多承伤', 791, 520, fm.get('SourceHanSansCN-Bold.otf', 48), '#40342d') await img.text(str(info.max_take_damage.value), 791, 590, fm.get('bahnschrift_bold.ttf', 72, 'Bold'), '#40342d') @@ -113,7 +113,7 @@ async def draw_abyss_card(info: AbyssInfo): await load_image(RESOURCE_BASE_PATH / 'icon' / f'star{character.rarity}.png', size=(95, 95)), (192 + (j % 4) * 103, 832 + (i - 9) * 194)) await img.paste( - await load_image(RESOURCE_BASE_PATH / 'thumb' / f'{character.name}.png', size=(95, 95)), + await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{character.icon}.png', size=(95, 95)), (192 + (j % 4) * 103, 832 + (i - 9) * 194)) await img.draw_rounded_rectangle2((192 + (j % 4) * 103, 903 + (i - 9) * 194), (30, 23), 10, '#333333', @@ -128,7 +128,7 @@ async def draw_abyss_card(info: AbyssInfo): await load_image(RESOURCE_BASE_PATH / 'icon' / f'star{character.rarity}.png', size=(95, 95)), (637 + (j % 4) * 103, 832 + (i - 9) * 194)) await img.paste( - await load_image(RESOURCE_BASE_PATH / 'thumb' / f'{character.name}.png', size=(95, 95)), + await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{character.icon}.png', size=(95, 95)), (637 + (j % 4) * 103, 832 + (i - 9) * 194)) await img.draw_rounded_rectangle2((637 + (j % 4) * 103, 903 + (i - 9) * 194), (30, 23), 10, '#333333', diff --git a/LittlePaimon/plugins/Paimon_Info/draw_character_bag.py b/LittlePaimon/plugins/Paimon_Info/draw_character_bag.py index cca7a4f..b3a1803 100644 --- a/LittlePaimon/plugins/Paimon_Info/draw_character_bag.py +++ b/LittlePaimon/plugins/Paimon_Info/draw_character_bag.py @@ -6,6 +6,7 @@ from typing import List from LittlePaimon.config import RESOURCE_BASE_PATH from LittlePaimon.database.models import Character, PlayerInfo, Player from LittlePaimon.utils.files import load_image +from LittlePaimon.utils.alias import get_chara_icon from LittlePaimon.utils.genshin import GenshinTools from LittlePaimon.utils.image import PMImage, font_manager as fm from LittlePaimon.utils.message import MessageBuild @@ -13,7 +14,6 @@ from .draw_player_card import get_avatar, draw_weapon_icon RESOURCES = RESOURCE_BASE_PATH / 'chara_bag' ICON = RESOURCE_BASE_PATH / 'icon' -THUMB = RESOURCE_BASE_PATH / 'thumb' ARTIFACT_ICON = RESOURCE_BASE_PATH / 'artifact' talent_color = [('#d5f2b6', '#6d993d'), ('#d5f2b6', '#6d993d'), ('#d5f2b6', '#6d993d'), @@ -68,7 +68,7 @@ async def draw_chara_card(info: Character) -> PMImage: :return: 角色卡片图 """ # 头像 - avatar = PMImage(await load_image(THUMB / f'{info.name}.png')) + avatar = PMImage(await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{get_chara_icon(name=info.name)}.png')) await avatar.to_circle('circle') await avatar.resize((122, 122)) # 背景 diff --git a/LittlePaimon/plugins/Paimon_Info/draw_character_detail.py b/LittlePaimon/plugins/Paimon_Info/draw_character_detail.py index 643fa5f..831dfe3 100644 --- a/LittlePaimon/plugins/Paimon_Info/draw_character_detail.py +++ b/LittlePaimon/plugins/Paimon_Info/draw_character_detail.py @@ -5,7 +5,7 @@ from LittlePaimon.utils import load_image from LittlePaimon.utils.genshin import GenshinTools from LittlePaimon.utils.image import PMImage, font_manager as fm from LittlePaimon.utils.message import MessageBuild -from LittlePaimon.utils.alias import get_icon +from LittlePaimon.utils.alias import get_chara_icon from LittlePaimon.database.models import Character from .damage_cal import get_role_dmg @@ -34,10 +34,12 @@ async def draw_chara_detail(uid: str, info: Character): await img.paste(dmg_img, (42, 1820)) # 立绘 - chara_img = await load_image(RESOURCE_BASE_PATH / 'splash' / f'{get_icon(chara_id=info.character_id, icon_type="splash")}.png') + chara_img = await load_image(RESOURCE_BASE_PATH / 'splash' / f'{get_chara_icon(chara_id=info.character_id, icon_type="splash")}.png') if chara_img.height >= 630: chara_img = chara_img.resize((chara_img.width * 630 // chara_img.height, 630)) - await img.paste(chara_img, (770 - chara_img.width // 2, 20)) + else: + chara_img = chara_img.resize((chara_img.width, chara_img.height * 630 // chara_img.height)) + await img.paste(chara_img, (770 - chara_img.width // 2, 20)) await img.paste(await load_image(ENKA_RES / '底遮罩.png'), (0, 0)) # 地区 if info.name not in ['荧', '空', '埃洛伊']: diff --git a/LittlePaimon/plugins/Paimon_Info/draw_player_card.py b/LittlePaimon/plugins/Paimon_Info/draw_player_card.py index 3892f3e..f159d39 100644 --- a/LittlePaimon/plugins/Paimon_Info/draw_player_card.py +++ b/LittlePaimon/plugins/Paimon_Info/draw_player_card.py @@ -4,12 +4,12 @@ from typing import List, Tuple, Optional from LittlePaimon.config import RESOURCE_BASE_PATH from LittlePaimon.database.models import PlayerInfo, Character, PlayerWorldInfo, Weapon, Player from LittlePaimon.utils.files import load_image +from LittlePaimon.utils.alias import get_chara_icon from LittlePaimon.utils.image import PMImage, get_qq_avatar, font_manager as fm from LittlePaimon.utils.message import MessageBuild RESOURCES = RESOURCE_BASE_PATH / 'player_card' ICON = RESOURCE_BASE_PATH / 'icon' -THUMB = RESOURCE_BASE_PATH / 'thumb' WEAPON = RESOURCE_BASE_PATH / 'weapon' @@ -44,7 +44,7 @@ async def draw_character_card(info: Character) -> Optional[PMImage]: if info is None: return None # 头像 - avatar = PMImage(await load_image(THUMB / f'{info.name}.png')) + avatar = PMImage(await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{get_chara_icon(name=info.name)}.png')) await avatar.to_circle('circle') await avatar.resize((122, 122)) # 背景 diff --git a/LittlePaimon/plugins/Paimon_MonthInfo/handler.py b/LittlePaimon/plugins/Paimon_MonthInfo/handler.py index 8eb65ec..2a0d7ca 100644 --- a/LittlePaimon/plugins/Paimon_MonthInfo/handler.py +++ b/LittlePaimon/plugins/Paimon_MonthInfo/handler.py @@ -1,5 +1,5 @@ from LittlePaimon.database.models import Player -from LittlePaimon.utils.genshin_api import get_mihoyo_private_data +from LittlePaimon.utils.api import get_mihoyo_private_data from LittlePaimon.utils import logger from .draw import draw_monthinfo_card diff --git a/LittlePaimon/plugins/Paimon_Wiki/abyss_rate_draw.py b/LittlePaimon/plugins/Paimon_Wiki/abyss_rate_draw.py index 271ef55..d86e010 100644 --- a/LittlePaimon/plugins/Paimon_Wiki/abyss_rate_draw.py +++ b/LittlePaimon/plugins/Paimon_Wiki/abyss_rate_draw.py @@ -2,7 +2,7 @@ from PIL import Image, ImageDraw, ImageFont from LittlePaimon.utils.files import load_image from LittlePaimon.utils.image import PMImage, font_manager as fm -from LittlePaimon.utils.alias import get_icon +from LittlePaimon.utils.alias import get_chara_icon from LittlePaimon.utils.message import MessageBuild from LittlePaimon.config import RESOURCE_BASE_PATH from .abyss_rate_data import get_rate, get_formation_rate @@ -29,7 +29,7 @@ async def draw_rate_rank(type: str = 'role'): font=fm.get('msyh.ttc', 35)) for n, role in enumerate(data['result']['rateList']): role_card = PMImage(size=(160, 200), color=(200, 200, 200, 255), mode='RGBA') - role_img = await load_image(RESOURCE_BASE_PATH / 'avatar_card' / f'{get_icon(name=role["name"], icon_type="card")}.png', size=(160, 160)) + role_img = await load_image(RESOURCE_BASE_PATH / 'avatar_card' / f'{get_chara_icon(name=role["name"], icon_type="card")}.png', size=(160, 160)) await role_card.paste(role_img, (0, 0)) await role_card.text((28 if len(role['rate']) == 6 else 38, 158), role['rate'], font=fm.get('msyh.ttc', 30), color='black') diff --git a/LittlePaimon/utils/alias.py b/LittlePaimon/utils/alias.py index 1b28ef2..96e6c66 100644 --- a/LittlePaimon/utils/alias.py +++ b/LittlePaimon/utils/alias.py @@ -6,6 +6,7 @@ from LittlePaimon.config import JSON_DATA alias_file = load_json(JSON_DATA / 'alias.json') info_file = load_json(JSON_DATA / 'genshin_info.json') +weapon_file = load_json(JSON_DATA / 'weapon.json') def get_short_name(name: str) -> str: @@ -52,7 +53,8 @@ def get_alias_by_name(name: str) -> List[str]: return next((r for r in name_list.values() if name in r), None) -def get_match_alias(msg: str, type: Literal['角色', '武器', '原魔', '圣遗物'] = '角色', single_to_dict: bool = False) -> Union[str, list, dict]: +def get_match_alias(msg: str, type: Literal['角色', '武器', '原魔', '圣遗物'] = '角色', single_to_dict: bool = False) -> Union[ + str, list, dict]: """ 根据字符串消息,获取与之相似或匹配的角色、武器、原魔名 :param msg: 消息 @@ -72,7 +74,8 @@ def get_match_alias(msg: str, type: Literal['角色', '武器', '原魔', '圣 elif match_list: possible[alias[0]] = role_id if len(possible) == 1: - return {list(possible.keys())[0]: possible[list(possible.keys())[0]]} if single_to_dict else list(possible.keys())[0] + return {list(possible.keys())[0]: possible[list(possible.keys())[0]]} if single_to_dict else \ + list(possible.keys())[0] return possible elif type in {'武器', '圣遗物'}: possible = [] @@ -88,8 +91,8 @@ def get_match_alias(msg: str, type: Literal['角色', '武器', '原魔', '圣 return match_list[0] if len(match_list) == 1 else match_list -def get_icon(name: Optional[str] = None, chara_id: Optional[int] = None, - icon_type: Literal['avatar', 'card', 'splash', 'slice', 'side'] = 'avatar') -> Optional[str]: +def get_chara_icon(name: Optional[str] = None, chara_id: Optional[int] = None, + icon_type: Literal['avatar', 'card', 'splash', 'slice', 'side'] = 'avatar') -> Optional[str]: """ 根据角色名字或id获取角色的图标 :param name: 角色名 @@ -114,3 +117,7 @@ def get_icon(name: Optional[str] = None, chara_id: Optional[int] = None, elif icon_type == 'slice': return side_icon.replace('_Side', '').replace('UI_', 'UI_Gacha_') + +def get_weapon_icon(name: str) -> Optional[str]: + icon_list = weapon_file['Icon'] + return icon_list.get(name) diff --git a/LittlePaimon/utils/genshin_api.py b/LittlePaimon/utils/api.py similarity index 88% rename from LittlePaimon/utils/genshin_api.py rename to LittlePaimon/utils/api.py index f6d5ba3..e124879 100644 --- a/LittlePaimon/utils/genshin_api.py +++ b/LittlePaimon/utils/api.py @@ -4,7 +4,7 @@ import re import string import time import json -from typing import Optional, Literal +from typing import Optional, Literal, Union, Tuple from LittlePaimon.utils import logger from nonebot import logger as nb_logger @@ -25,6 +25,7 @@ GAME_RECORD_API = 'https://api-takumi-record.mihoyo.com/game_record/card/wapi/ge SIGN_INFO_API = 'https://api-takumi.mihoyo.com/event/bbs_sign_reward/info' SIGN_REWARD_API = 'https://api-takumi.mihoyo.com/event/bbs_sign_reward/home' SIGN_ACTION_API = 'https://api-takumi.mihoyo.com/event/bbs_sign_reward/sign' +AUTHKEY_API = 'https://api-takumi.mihoyo.com/binding/api/genAuthKey' def md5(text: str) -> str: @@ -173,7 +174,8 @@ async def check_retcode(data: dict, cookie_info, cookie_type: str, user_id: str, return True -async def get_cookie(user_id: str, uid: str, check: bool = True, own: bool = False): +async def get_cookie(user_id: str, uid: str, check: bool = True, own: bool = False) -> Tuple[ + Union[None, PrivateCookie, PublicCookie, CookieCache], str]: """ 获取可用的cookie :param user_id: 用户id @@ -222,7 +224,7 @@ async def get_mihoyo_public_data( user_id: Optional[str], mode: Literal['abyss', 'player_card', 'role_detail'], schedule_type: Optional[str] = '1'): - server_id = "cn_qd01" if uid[0] == '5' else "cn_gf01" + server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01' check = True while True: cookie_info, cookie_type = await get_cookie(user_id, uid, check) @@ -270,7 +272,7 @@ async def get_mihoyo_private_data( mode: Literal['role_skill', 'month_info', 'daily_note', 'sign_info', 'sign_action'], role_id: Optional[str] = None, month: Optional[str] = None): - server_id = "cn_qd01" if uid[0] == '5' else "cn_gf01" + server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01' cookie_info, _ = await get_cookie(user_id, uid, True, True) if not cookie_info: return '未绑定私人cookie,获取cookie的教程:\ndocs.qq.com/doc/DQ3JLWk1vQVllZ2Z1\n获取后,使用[ysb cookie]指令绑定' @@ -361,11 +363,49 @@ async def get_stoken_by_cookie(cookie: str) -> Optional[str]: bbs_cookie_url2 = 'https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}' data2 = (await aiorequests.get(url=bbs_cookie_url2.format(login_ticket[0].split('=')[1], stuid))).json() return data2['data']['list'][0]['token'] - else: - return None return None +async def get_authkey_by_stoken(user_id: str, uid: str) -> Tuple[Optional[str], bool, Optional[PrivateCookie]]: + """ + 根据stoken获取authkey + :param user_id: 用户id + :param uid: 原神uid + :return: authkey + """ + server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01' + cookie_info, _ = await get_cookie(user_id, uid, True, True) + if not cookie_info: + return '未绑定私人cookie,获取cookie的教程:\ndocs.qq.com/doc/DQ3JLWk1vQVllZ2Z1\n获取后,使用[ysb cookie]指令绑定', False, cookie_info + if not cookie_info.stoken: + return 'cookie中没有stoken字段,请重新绑定', False, cookie_info + headers = { + 'Cookie': cookie_info.stoken, + 'DS': get_old_version_ds(True), + 'User-Agent': 'okhttp/4.8.0', + 'x-rpc-app_version': '2.35.2', + 'x-rpc-sys_version': '12', + 'x-rpc-client_type': '5', + 'x-rpc-channel': 'mihoyo', + 'x-rpc-device_id': random_hex(32), + 'x-rpc-device_name': random_text(random.randint(1, 10)), + 'x-rpc-device_model': 'Mi 10', + 'Referer': 'https://app.mihoyo.com', + 'Host': 'api-takumi.mihoyo.com'} + data = await aiorequests.post(url=AUTHKEY_API, + headers=headers, + json={ + 'auth_appid': 'webview_gacha', + 'game_biz': 'hk4e_cn', + 'game_uid': uid, + 'region': server_id}) + data = data.json() + if 'data' in data and 'authkey' in data['data']: + return data['data']['authkey'], True, cookie_info + else: + return None, False, cookie_info + + async def get_enka_data(uid): try: url = f'https://enka.network/u/{uid}/__data.json' diff --git a/LittlePaimon/utils/genshin.py b/LittlePaimon/utils/genshin.py index bbc733a..3f1077c 100644 --- a/LittlePaimon/utils/genshin.py +++ b/LittlePaimon/utils/genshin.py @@ -10,7 +10,7 @@ from LittlePaimon.database.models import PlayerInfo, Character, LastQuery, Priva from LittlePaimon.database.models import Artifact, CharacterProperty, Artifacts, Talents, Talent from LittlePaimon.utils import logger, scheduler from LittlePaimon.utils.files import load_json -from LittlePaimon.utils.genshin_api import get_enka_data, get_mihoyo_public_data, get_mihoyo_private_data +from LittlePaimon.utils.api import get_enka_data, get_mihoyo_public_data, get_mihoyo_private_data from LittlePaimon.utils.typing import DataSourceType from LittlePaimon.utils.alias import get_name_by_id from LittlePaimon.utils.typing import CHARACTERS