mirror of
https://github.com/xuthus83/LittlePaimon.git
synced 2024-12-16 13:40:53 +08:00
✨ 抽卡记录新UI,无法抽卡链接,通过Stoken更新
This commit is contained in:
parent
ee24302a13
commit
b6f603896b
@ -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]
|
||||
|
@ -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);}'
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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):
|
||||
"""
|
||||
插件调用统计
|
||||
|
@ -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)
|
||||
|
@ -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'新纳入插件<m>{plugin.name}</m>进行权限管理')
|
||||
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'],
|
||||
|
@ -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列表
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
@ -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'(?P<filetype>xlsx|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'➤➤更新抽卡记录时出现错误:<r>{e}</r>')
|
||||
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'➤➤绘制抽卡记录图片时出现错误:<r>{e}</r>')
|
||||
await update_log.finish(f'绘制抽卡记录分析时出现错误:{e}')
|
||||
|
@ -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()
|
137
LittlePaimon/plugins/Paimon_Gacha_Log/data_source.py
Normal file
137
LittlePaimon/plugins/Paimon_Gacha_Log/data_source.py
Normal file
@ -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'➤➤<m>{pool_name}</m>', {}, '获取完成', 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)
|
||||
|
||||
|
209
LittlePaimon/plugins/Paimon_Gacha_Log/draw.py
Normal file
209
LittlePaimon/plugins/Paimon_Gacha_Log/draw.py
Normal file
@ -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)
|
@ -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
|
@ -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')
|
@ -1,10 +0,0 @@
|
||||
gachaQueryTypeIds = ["100", "200", "301", "302"]
|
||||
gachaQueryTypeNames = ["新手祈愿", "常驻祈愿", "角色活动祈愿", "武器活动祈愿"]
|
||||
gachaQueryTypeDict = dict(zip(gachaQueryTypeIds, gachaQueryTypeNames))
|
||||
gacha_type_dict = {
|
||||
"100": "新手祈愿",
|
||||
"200": "常驻祈愿",
|
||||
"301": "角色活动祈愿",
|
||||
"302": "武器活动祈愿",
|
||||
"400": "角色活动祈愿-2",
|
||||
}
|
81
LittlePaimon/plugins/Paimon_Gacha_Log/models.py
Normal file
81
LittlePaimon/plugins/Paimon_Gacha_Log/models.py
Normal file
@ -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
|
@ -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',
|
||||
|
@ -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))
|
||||
# 背景
|
||||
|
@ -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 ['荧', '空', '埃洛伊']:
|
||||
|
@ -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))
|
||||
# 背景
|
||||
|
@ -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
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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'
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user