新增抽卡记录分析和导出功能

This commit is contained in:
CMHopeSunshine 2022-03-24 19:33:18 +08:00
parent 625758ef40
commit 9a6ff7fff5
83 changed files with 578 additions and 189 deletions

View File

@ -18,8 +18,6 @@ sv=hoshino.Service('原神模拟抽卡')
@sv.on_rex(r'^抽((?P<num>\d+)|(?:.*))十连(?P<pool>.*?)$')
async def gacha(bot, ev):
if ev.message_type == 'guild' and ev.channel_id != '1983536' and ev.channel_id != '1916789':
return
gid = ev.group_id
uid = ev.user_id
init_user_info(uid)
@ -61,10 +59,8 @@ async def gacha(bot, ev):
save_user_info()
await bot.send(ev, MessageSegment.image(img), at_sender=True)
@sv.on_prefix('查看抽卡记录')
@sv.on_prefix('模拟抽卡记录')
async def gacharecord(bot, ev):
if ev.channel_id != '1983536' and ev.channel_id != '1916789':
return
uid = ev.user_id
init_user_info(uid)
if user_info[uid]['gacha_list']['wish_total'] == 0:
@ -127,10 +123,8 @@ async def getrwrecord(msg,uid):
res = res.replace(',',' ')
return res
@sv.on_fullmatch('删除抽卡记录')
@sv.on_fullmatch('删除模拟抽卡记录')
async def deleterecord(bot,ev):
if ev.channel_id != '1983536' and ev.channel_id !='1916789':
return
uid = ev.user_id
init_user_info(uid)
try:
@ -142,8 +136,6 @@ async def deleterecord(bot,ev):
@sv.on_prefix('选择定轨')
async def choosedg(bot,ev):
if ev.channel_id != '1983536' and ev.channel_id !='1916789':
return
uid = ev.user_id
init_user_info(uid)
dg_weapon = ev.message.extract_plain_text().strip()
@ -161,8 +153,6 @@ async def choosedg(bot,ev):
@sv.on_fullmatch('删除定轨')
async def deletedg(bot,ev):
if ev.channel_id != '1983536' and ev.channel_id !='1916789':
return
uid = ev.user_id
init_user_info(uid)
if user_info[uid]['gacha_list']['dg_name'] == '':
@ -175,8 +165,6 @@ async def deletedg(bot,ev):
@sv.on_fullmatch('查看定轨')
async def deletedg(bot,ev):
if ev.channel_id != '1983536' and ev.channel_id !='1916789':
return
uid = ev.user_id
init_user_info(uid)
weapon_up_list = await getdg_weapon()

View File

@ -0,0 +1,115 @@
from .meta_data import *
import time
import os
import sys
import json
import xlsxwriter
data_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),'user_data', 'gacha_log_data')
def id_generator():
id = 1000000000000000000
while True:
id = id + 1
yield str(id)
def convertUIGF(gachaLog, uid):
UIGF_data = {}
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(os.path.join(data_path, f"gachaExport-{uid}.xlsx"))
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"})
first_row = 1 # 不包含表头第一行 (zero indexed)
first_col = 0 # 第一列
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"]
all_counter = 0
for gacha in 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)
all_counter += 1
workbook.close()

View File

@ -0,0 +1,153 @@
import json,os,re
from hoshino import R,MessageSegment,logger, Service
from hoshino.typing import CQEvent, Message
from ..util import get_uid_by_qq, update_last_query_to_qq
from .gacha_logs import get_data
from .get_img import get_gacha_log_img
from .api import toApi, checkApi
sv = Service('原神抽卡记录导出')
data_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),'user_data', 'gacha_log_data')
if not os.path.exists(data_path):
os.makedirs(data_path)
if not os.path.exists(os.path.join(data_path, 'user_gacha_log.json')):
with open(os.path.join(data_path, 'user_gacha_log.json'),'w',encoding='UTF-8') as f:
json.dump({},f,ensure_ascii=False)
@sv.on_prefix('导出抽卡记录', 'dcckjl', 'exportgachalog')
async def ckjl(bot,ev):
if ev.message_type != 'group':
await bot.send(ev,'在群聊中才能导出抽卡记录文件哦!')
return
msg = ev.message.extract_plain_text().strip().split(' ')
uid = ''
if len(msg[0]) == 9 and msg[0].isdigit():
uid = msg[0]
if len(msg) >= 2:
filetype = msg[1]
else:
filetype = 'xlsx'
else:
filetype = msg[0]
if not filetype or filetype not in ['xlsx', 'json']:
filetype = 'xlsx'
qq = str(ev.user_id)
if ev.message_type == 'guild':
rm = str(ev.message)
else:
rm = str(ev.raw_message)
match = re.search(r"\[CQ:at,qq=(.*)\]", rm)
if match:
uid = ''
qq = str(match.group(1))
if not uid:
uid = get_uid_by_qq(qq)
if not uid:
await bot.send(ev,'请把uid给派蒙哦比如抽卡记录导出100000001 xlsx',at_sender=True)
return
update_last_query_to_qq(qq, uid)
if filetype == 'xlsx':
filetype = f'gachaExport-{uid}.xlsx'
else:
filetype = f'UIGF_gachaData-{uid}.json'
local_data = os.path.join(data_path, filetype)
if not os.path.exists(local_data):
await bot.send(ev, '你在派蒙这里还没有抽卡记录哦,使用 更新抽卡记录 吧!', at_sender=True)
return
await bot.upload_group_file(group_id=ev.group_id, file=local_data, name=filetype)
@sv.on_prefix(('更新抽卡记录', '获取抽卡记录', 'updategachalog', 'gxckjl'))
async def update_ckjl(bot,ev):
msg = ev.message.extract_plain_text().strip().split(' ')
uid = ''
if len(msg[0]) == 9 and msg[0].isdigit():
uid = msg[0]
if len(msg) >= 2:
url = msg[1]
else:
url = ''
else:
url = msg[0]
qq = str(ev.user_id)
if not uid:
uid = get_uid_by_qq(qq)
if not uid:
await bot.send(ev,'请把uid给派蒙哦比如获取抽卡记录100000001 链接',at_sender=True)
return
if url:
match = re.search(r'(https://webstatic.mihoyo.com/.*#/log)', url)
if match:
url = str(match.group(1))
else:
await bot.send(ev,'你这个抽卡链接不对哦应该是以https://webstatic.mihoyo开头的', at_sender=True)
return
else:
with open(os.path.join(data_path, 'user_gacha_log.json'), 'r', encoding="utf-8") as f:
user_data = json.load(f)
if qq in user_data and uid in user_data[qq]:
url = user_data[qq][uid]
await bot.send(ev,'发现历史抽卡记录链接,尝试使用...')
else:
await bot.send(ev, '拿到游戏抽卡记录链接后,对派蒙说[获取抽卡记录 uid 链接]就可以啦\n获取抽卡记录链接的方式和vx小程序的是一样的还请旅行者自己搜方法', at_sender=True)
return
with open(os.path.join(data_path, 'user_gacha_log.json'), 'r', encoding="utf-8") as f:
user_data = json.load(f)
if qq not in user_data:
user_data[qq] = {}
user_data[qq][uid] = url
with open(os.path.join(data_path, 'user_gacha_log.json'), 'w', encoding="utf-8") as f:
json.dump(user_data, f, ensure_ascii=False, sort_keys=False, indent=4)
url = toApi(url)
apiRes = await checkApi(url)
if apiRes != 'OK':
await bot.send(ev,apiRes,at_sender=True)
return
await bot.send(ev, '抽卡记录开始获取,请给派蒙一点时间...')
await get_data(url)
local_data = os.path.join(data_path, f'gachaData-{uid}.json')
with open(local_data, 'r', encoding="utf-8") as f:
gacha_data = json.load(f)
gacha_img = await get_gacha_log_img(gacha_data, 'all')
await bot.send(ev, gacha_img, at_sender=True)
@sv.on_prefix('抽卡记录', 'ckjl', 'gachalog')
async def get_ckjl(bot,ev):
msg = ev.message.extract_plain_text().strip().split(' ')
uid = ''
if len(msg[0]) == 9 and msg[0].isdigit():
uid = msg[0]
if len(msg) >= 2:
pool = msg[1]
else:
pool = 'all'
else:
pool = msg[0]
if not pool or pool not in ['all', '角色', '武器', '常驻', '新手']:
pool = 'all'
qq = str(ev.user_id)
if ev.message_type == 'guild':
rm = str(ev.message)
else:
rm = str(ev.raw_message)
match = re.search(r"\[CQ:at,qq=(.*)\]", rm)
if match:
uid = ''
qq = str(match.group(1))
if not uid:
uid = get_uid_by_qq(qq)
if not uid:
await bot.send(ev,'请把uid给派蒙哦比如抽卡记录100000001 all',at_sender=True)
return
update_last_query_to_qq(qq, uid)
local_data = os.path.join(data_path, f'gachaData-{uid}.json')
if not os.path.exists(local_data):
await bot.send(ev, '你在派蒙这里还没有抽卡记录哦,对派蒙说 获取抽卡记录 吧!', at_sender=True)
return
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)
await bot.send(ev, gacha_img, at_sender=True)

View File

@ -0,0 +1,65 @@
import json
from urllib import parse
from hoshino import aiorequests
def toApi(url):
spliturl = str(url).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]
api = path + "?" + param
return api
async def checkApi(url):
try:
r = await aiorequests.get(url)
s = (await r.content).decode("utf-8")
j = json.loads(s)
except Exception as e:
# print("API请求解析出错\n", traceback.format_exc())
return f'API请求解析出错{e}'
if not j["data"]:
if j["message"] == "authkey valid error":
#print("authkey错误")
return "authkey错误请重新获取链接给派蒙"
elif j["message"] == "authkey timeout":
return "authkey已过期请重新获取链接给派蒙"
else:
# print("数据为空,错误代码:" + j["message"])
return f'数据为空,错误代码:{j["message"]}'
return 'OK'
def getQueryVariable(variable):
query = str(url).split("?")[1]
vars = query.split("&")
for v in vars:
if v.split("=")[0] == variable:
return v.split("=")[1]
return ""
def getGachaInfo():
region = getQueryVariable("region")
lang = getQueryVariable("lang")
gachaInfoUrl = "https://webstatic.mihoyo.com/hk4e/gacha_info/{}/items/{}.json".format(region, lang)
r = requests.get(gachaInfoUrl)
s = r.content.decode("utf-8")
gachaInfo = json.loads(s)
return gachaInfo

View File

@ -0,0 +1,95 @@
import os
import json
from asyncio import sleep
from hoshino import aiorequests
from .api import toApi, getApi, checkApi
from .meta_data import gachaQueryTypeIds, gachaQueryTypeNames, gachaQueryTypeDict
from .UIGF_and_XLSX import convertUIGF, writeXLSX
data_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),'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)
r = await aiorequests.get(api)
s = (await r.content).decode("utf-8")
j = json.loads(s)
gacha = j["data"]["list"]
if not len(gacha):
break
for i in gacha:
gachaList.append(i)
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:
pass
else:
# print("合并", gachaQueryTypeDict[banner], end=": ", flush=True)
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 in loc:
pass
else:
flaglist[i] = 0
tempData = []
for i in range(len(bannerGet)):
if flaglist[i] == 0:
gachaGet = bannerGet[i]
tempData.insert(0, gachaGet)
# print("追加", len(tempData), "条记录", flush=True)
for i in tempData:
localData["gachaLog"][banner].insert(0, i)
return localData
async def get_data(url):
gachaData = {}
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 = os.path.join(data_path, f"gachaData-{uid}.json")
if os.path.isfile(localDataFilePath):
with open(localDataFilePath, "r", encoding="utf-8") as f:
localData = json.load(f)
mergeData = mergeDataFunc(localData, gachaData)
else:
mergeData = gachaData
mergeData["gachaType"] = gachaQueryTypeDict
# 写入json
with open(localDataFilePath, "w", encoding="utf-8") as f:
json.dump(mergeData, f, ensure_ascii=False, sort_keys=False, indent=4)
# 写入UIFG json
with open(os.path.join(data_path, f"UIGF_gachaData-{uid}.json"), "w", encoding="utf-8") as f:
UIGF_data = convertUIGF(mergeData['gachaLog'], uid)
json.dump(UIGF_data, f, ensure_ascii=False, sort_keys=False, indent=4)
# 写入xlsx
writeXLSX(uid, mergeData['gachaLog'], gachaQueryTypeIds)

View File

@ -0,0 +1,135 @@
from PIL import Image, ImageDraw, ImageFont
import os
from ..util import pil2b64
from hoshino.typing import MessageSegment
res_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'res')
def get_font(size):
return ImageFont.truetype(os.path.join(res_path, 'msyh.ttc'), size)
async def get_circle_avatar(avatar, size):
avatar = Image.open(os.path.join(res_path, 'role_avatar', 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(os.path.join(res_path, 'player_card', 'gacha_log_top.png'))
mid = Image.open(os.path.join(res_path, 'player_card', '卡片身体.png')).resize((768, 80))
bottom = Image.open(os.path.join(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((146 - 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']), 4) if five_star else -1
five_per_str = str(five_per * 100) + '%' if five_per > -1 else ' '
top_draw.text((427, 88), f'{five_per_str}', font=get_font(24), fill='black' if five_per < 0.016 else 'red')
five_up = round(len([x[0] for x in five_star if not x[0] in ['刻晴', '迪卢克', '七七', '莫娜', '']]) / len(five_star),
4) if five_star else -1
five_up_str = str(five_up * 100) + '%' 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 < 0.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)
if c[1] <= 20:
color = 'red'
elif 20 < c[1] <= 50:
color = 'orangered'
elif 50 < c[1] < 70:
color = 'darkorange'
else:
color = 'black'
bg_img.alpha_composite(avatar, (30 + 120 * (n % 6), 298 + 80 * int(n / 6)))
bg_draw.text((111 + 120 * (n % 6) - 8 * len(c[0]), 298 + 80 * int(n / 6)), f'{c[0]}', 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':
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 惜月の小派蒙', font=get_font(16), fill='black')
img = pil2b64(img, 95)
img = MessageSegment.image(img)
return img

View File

@ -0,0 +1,10 @@
gachaQueryTypeIds = ["100", "200", "301", "302"]
gachaQueryTypeNames = ["新手祈愿", "常驻祈愿", "角色活动祈愿", "武器活动祈愿"]
gachaQueryTypeDict = dict(zip(gachaQueryTypeIds, gachaQueryTypeNames))
gacha_type_dict = {
"100": "新手祈愿",
"200": "常驻祈愿",
"301": "角色活动祈愿",
"302": "武器活动祈愿",
"400": "角色活动祈愿-2",
}

View File

@ -1,174 +0,0 @@
import json
import urllib
import requests
from asyncio import sleep
import time
import os
import hoshino,os
from hoshino import R,MessageSegment,aiorequests,logger,Service
sv = Service('原神抽卡记录导出')
def checkApi(url) -> bool:
if not url:
#print("url为空")
return "url为空"
if "getGachaLog" not in url:
#print("错误的url检查是否包含getGachaLog")
return "错误的url检查是否包含getGachaLog"
try:
r = requests.get(url)
s = r.content.decode("utf-8")
j = json.loads(s)
except Exception as e:
#print("API请求解析出错" + str(e))
return "API请求解析出错" + str(e)
if not j["data"]:
if j["message"] == "authkey valid error":
return "authkey错误"
else:
return "数据为空,错误代码:" + j["message"]
return "OK"
def getApi(url: str, gachaType: str, size: str, page: int, end_id="") -> str:
parsed = urllib.parse.urlparse(url)
querys = urllib.parse.parse_qsl(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 = urllib.parse.urlencode(param_dict)
path = url.split("?")[0]
api = path + "?" + param
return api
async def getGachaLogs(url: str, gachaTypeId: str, gachaTypeDict: dict) -> list:
size = "20"
# api限制一页最大20
gachaList = []
gachaList_temp = []
end_id = "0"
c = 0
for page in range(1, 9999):
#print(f"正在获取 {gachaTypeDict[gachaTypeId]} 第 {page} 页", flush=True)
api = getApi(url, gachaTypeId, size, page, end_id)
r = requests.get(api)
s = r.content.decode("utf-8")
j = json.loads(s)
gacha = j["data"]["list"]
if not len(gacha):
break
for i in gacha:
gachaList_temp.append(i)
end_id = j["data"]["list"][-1]["id"]
await sleep(0.3)
if gachaList_temp:
gachaList.append({'uid':gachaList_temp[0]['uid']})
gachaList_temp.reverse()
for i in gachaList_temp:
c += 1
if i['rank_type'] == "5":
fivechar = {}
fivechar['name'] = i['name']
fivechar['item_type'] = i['item_type']
fivechar['count'] = c
c = 0
gachaList.append(fivechar)
gachaList.append({'未出货':c})
return gachaList
# def getGachaTypes(url: str) -> list:
# tmp_url = url.replace("getGachaLog", "getConfigList")
# parsed = urllib.parse.urlparse(tmp_url)
# querys = urllib.parse.parse_qsl(parsed.query)
# param_dict = dict(querys)
# param_dict["lang"] = "zh-cn"
# param = urllib.parse.urlencode(param_dict)
# path = tmp_url.split("?")[0]
# tmp_url = path + "?" + param
# r = requests.get(tmp_url)
# s = r.content.decode("utf-8")
# configList = json.loads(s)
# return configList["data"]["gacha_type_list"]
# def mergeDataFunc(localData: dict, gachaData: dict) -> dict:
# gachaTypes = gachaData["gachaType"]
# gachaTypeIds = [banner["key"] for banner in gachaTypes]
# gachaTypeNames = [banner["name"] for banner in gachaTypes]
# gachaTypeDict = dict(zip(gachaTypeIds, gachaTypeNames))
# for banner in gachaTypeDict:
# bannerLocal = localData["gachaLog"][banner]
# bannerGet = gachaData["gachaLog"][banner]
# if bannerGet == bannerLocal:
# pass
# else:
# #print("合并", gachaTypeDict[banner])
# 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 in loc:
# pass
# else:
# flaglist[i] = 0
# print("获取到", len(flaglist), "条记录")
# tempData = []
# for i in range(len(bannerGet)):
# if flaglist[i] == 0:
# gachaGet = bannerGet[i]
# tempData.insert(0, gachaGet)
# print("追加", len(tempData), "条记录")
# for i in tempData:
# localData["gachaLog"][banner].insert(0, i)
# return localData
@sv.on_prefix('原神抽卡记录导出')
async def get_gacha_record(bot,ev):
url = ev.message.extract_plain_text().strip()
spliturl = url.split("?")
spliturl[0] = "https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog"
url = "?".join(spliturl)
checkApimsg = checkApi(url)
if checkApimsg == 'OK':
await bot.send(ev,'获取抽卡记录中,请稍候...',at_sender=True)
gachaTypes = {'200': '常驻祈愿', '301': '角色活动祈愿', '302': '武器活动祈愿'}
gachaData = {}
gachaData["gachaLog"] = {}
for gachaType in gachaTypes.items():
gachaLog = await getGachaLogs(url, gachaType[0], gachaTypes)
gachaData["gachaLog"][gachaType[1]] = 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
#gen_path = os.path.dirname(os.path.realpath(sys.argv[0]))
uid = gachaData["uid"]
mergeData = gachaData
msg = f'你的抽卡记录如下\nuid: {uid}\n'
for gachaType in gachaTypes.values():
if gachaType in mergeData['gachaLog']:
msg += gachaType +': '
if len(mergeData['gachaLog'][gachaType]) <= 2:
msg += '没有五星记录'
else:
for role in mergeData['gachaLog'][gachaType]:
if 'name' in role:
msg += f"{role['name']}({role['count']}) "
msg += '\n'
await bot.send(ev,msg,at_sender=True)
else:
await bot.send(ev,checkApimsg,at_sender=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@ -17,4 +17,6 @@ PyYAML>=5.4.1
uuid>=1.30
filetype
imageio
loguru
loguru
xlsxwriter>=1.3.7
urllib3>=1.26.8