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