This commit is contained in:
xtaodada 2022-06-04 12:32:48 +08:00
parent 827c3f0b5b
commit bb4338df1c
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
973 changed files with 1794 additions and 1 deletions

6
.gitignore vendored
View File

@ -150,5 +150,9 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
*.session
config.ini
data/
resources/player/

42
ci.py Normal file
View File

@ -0,0 +1,42 @@
from configparser import RawConfigParser
from os import mkdir, sep
from os.path import exists
import pyromod.listen
from pyrogram import Client
from httpx import AsyncClient, get
from sqlitedict import SqliteDict
try:
import uvloop
uvloop.install()
except ImportError:
pass
# init folders
if not exists("data"):
mkdir("data")
sqlite = SqliteDict(f"data{sep}data.sqlite", autocommit=True)
# 读取配置文件
config = RawConfigParser()
config.read("config.ini")
bot_token: str = ""
api_id: int = 0
api_hash: str = ""
channel_id: int = 0
admin_id: int = 0
bot_token = config.get("basic", "bot_token", fallback=bot_token)
channel_id = config.get("basic", "channel_id", fallback=channel_id)
admin_id = config.get("basic", "admin_id", fallback=admin_id)
api_id = config.get("pyrogram", "api_id", fallback=api_id)
api_hash = config.get("pyrogram", "api_hash", fallback=api_hash)
guess_time = 30 # 猜语音游戏持续时间
""" Init httpx client """
# 使用自定义 UA
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
}
client = AsyncClient(timeout=10.0, headers=headers)
me = get(f"https://api.telegram.org/bot{bot_token}/getme").json()
# 初始化客户端
app = Client("bot", bot_token=bot_token, api_id=api_id, api_hash=api_hash, plugins={"root": "plugins"})

8
config.gen.ini Normal file
View File

@ -0,0 +1,8 @@
[pyrogram]
api_id = 12345
api_hash = 0123456789abc0123456789abc
[basic]
bot_token = 111:abc
channel_id = 0
admin_id = 0

25
defs/bind.py Normal file
View File

@ -0,0 +1,25 @@
from ci import sqlite
def get_bind_list() -> dict:
return sqlite.get("bind", {})
def get_bind_uid(uid: int) -> str:
return get_bind_list().get(uid, None)
def set_bind(uid: int, player: str):
data = get_bind_list()
data[uid] = player
sqlite["bind"] = data
def remove_bind(uid: int):
data = get_bind_list()
data.pop(uid, None)
sqlite["bind"] = data
def check_bind(uid: int) -> bool:
return get_bind_uid(uid) is not None

301
defs/drawCharCard.py Normal file
View File

@ -0,0 +1,301 @@
from PIL import Image, ImageDraw, ImageFont
from os import sep
import math
from defs.sources import TEXT_PATH, GACHA_PATH, ICON_PATH, RELIC_PATH
COLOR_MAP = {"Anemo": (3, 90, 77),
"Cryo": (5, 85, 151),
"Dendro": (4, 87, 3),
"Electro": (47, 1, 85),
"Geo": (85, 34, 1),
"Hydro": (4, 6, 114),
"Pyro": (88, 4, 4)}
def genshin_font_origin(size: int) -> ImageFont:
return ImageFont.truetype(str(TEXT_PATH / 'yuanshen_origin.ttf'), size=size)
def get_star_png(star: int) -> Image:
png = Image.open(TEXT_PATH / 's-{}.png'.format(str(star)))
return png
def str_len(r: str, size: int, limit: int = 540) -> str:
result = ''
temp = 0
for i in r:
if temp >= limit:
result += '\n' + i
temp = 0
else:
result += i
if i.isdigit():
temp += round(size / 10 * 6)
elif i == '/':
temp += round(size / 10 * 2.2)
elif i == '.':
temp += round(size / 10 * 3)
elif i == '%':
temp += round(size / 10 * 9.4)
else:
temp += size
return result
async def draw_char_card(raw_data: dict) -> str:
img = Image.open(TEXT_PATH / '{}.png'.format(raw_data['avatarElement']))
char_info_1 = Image.open(TEXT_PATH / 'char_info_1.png')
char_imfo_mask = Image.open(TEXT_PATH / 'char_info_mask.png')
based_w, based_h = 600, 1200
try:
char_img = Image.open(GACHA_PATH / 'UI_Gacha_AvatarImg_{}.png'.format(raw_data['avatarEnName'])) # 角色图像
except FileNotFoundError:
char_img = Image.open(GACHA_PATH / 'default.jpg') # 角色图像
# 确定图片的长宽
w, h = char_img.size
if (w, h) != (based_w, based_h):
offset = 200
based_new_w, based_new_h = based_w + offset, based_h + offset
based_scale = '%.3f' % (based_new_w / based_new_h)
scale_f = '%.3f' % (w / h)
new_w = math.ceil(based_new_h * float(scale_f))
new_h = math.ceil(based_new_w / float(scale_f))
if scale_f > based_scale:
bg_img2 = char_img.resize((new_w, based_new_h), Image.Resampling.LANCZOS)
char_img = bg_img2.crop(
(new_w / 2 - based_new_w / 2 + offset, 0, new_w / 2 + based_new_w / 2, based_new_h - offset))
else:
bg_img2 = char_img.resize((based_new_w, new_h), Image.Resampling.LANCZOS)
char_img = bg_img2.crop(
(0 + offset, new_h / 2 - based_new_h / 2, based_new_w, new_h / 2 + based_new_h / 2 - offset))
else:
pass
img_temp = Image.new('RGBA', (based_w, based_h), (0, 0, 0, 0))
img_temp.paste(char_img, (0, 0), char_imfo_mask)
img.paste(img_temp, (0, 0), img_temp)
img.paste(char_info_1, (0, 0), char_info_1)
# holo_img = Image.open(TEXT_PATH / 'icon_holo.png')
# skill_holo_img = Image.open(TEXT_PATH / 'skillHolo.png')
lock_img = Image.open(TEXT_PATH / 'icon_lock.png')
# color_soild = Image.new('RGBA', (950, 1850), COLOR_MAP[raw_data['avatarElement']])
# img.paste(color_soild, (0, 0), skill_holo_img)
# color_holo_img = Image.new('RGBA', (100, 100), COLOR_MAP[raw_data['avatarElement']])
# 命座处理
for talent_num in range(0, 6):
if talent_num + 1 <= len(raw_data['talentList']):
talent = raw_data['talentList'][talent_num]
# img.paste(color_holo_img, (13,270 + talent_num * 66), holo_img)
talent_img = Image.open(ICON_PATH / '{}.png'.format(talent['talentIcon']))
talent_img_new = talent_img.resize((50, 50), Image.Resampling.LANCZOS).convert("RGBA")
img.paste(talent_img_new, (850, 375 + talent_num * 81), talent_img_new)
else:
img.paste(lock_img, (850, 375 + talent_num * 81), lock_img)
# 天赋处理
skillList = raw_data['avatarSkill']
a_skill_name = skillList[0]['skillName'].replace('普通攻击·', '')
a_skill_level = skillList[0]['skillLevel']
e_skill_name = skillList[1]['skillName']
e_skill_level = skillList[1]['skillLevel']
q_skill_name = skillList[-1]['skillName']
q_skill_level = skillList[-1]['skillLevel']
for skill_num, skill in enumerate(skillList[0:2] + [skillList[-1]]):
skill_img = Image.open(ICON_PATH / '{}.png'.format(skill['skillIcon']))
skill_img_new = skill_img.resize((50, 50), Image.Resampling.LANCZOS).convert("RGBA")
img.paste(skill_img_new, (78, 756 + 101 * skill_num), skill_img_new)
# 武器部分
weapon_img = Image.open(TEXT_PATH / 'char_info_weapon.png')
weapon_star_img = get_star_png(raw_data['weaponInfo']['weaponStar'])
weaponName = raw_data['weaponInfo']['weaponName']
weaponAtk = raw_data['weaponInfo']['weaponStats'][0]['statValue']
weaponLevel = raw_data['weaponInfo']['weaponLevel']
weaponAffix = raw_data['weaponInfo']['weaponAffix']
weaponEffect = raw_data['weaponInfo']['weaponEffect']
weapon_type = raw_data['weaponInfo']['weaponType']
weapon_img.paste(weapon_star_img, (25, 235), weapon_star_img)
weapon_text = ImageDraw.Draw(weapon_img)
weapon_text.text((35, 80), weaponName, (255, 255, 255), genshin_font_origin(50), anchor='lm')
weapon_text.text((35, 120), weapon_type, (255, 255, 255), genshin_font_origin(20), anchor='lm')
weapon_text.text((35, 160), '基础攻击力', (255, 255, 255), genshin_font_origin(32), anchor='lm')
weapon_text.text((368, 160), str(weaponAtk), (255, 255, 255), genshin_font_origin(32), anchor='rm')
if len(raw_data['weaponInfo']['weaponStats']) == 2:
weapon_sub_info = raw_data['weaponInfo']['weaponStats'][1]['statName']
weapon_sub_value = raw_data['weaponInfo']['weaponStats'][1]['statValue']
weapon_text.text((35, 211), weapon_sub_info, (255, 255, 255), genshin_font_origin(32), anchor='lm')
weapon_text.text((368, 211), str(weapon_sub_value), (255, 255, 255), genshin_font_origin(32), anchor='rm')
else:
weapon_text.text((35, 211), '该武器无副词条', (255, 255, 255), genshin_font_origin(32), anchor='lm')
weapon_text.text((73, 303), f'Lv.{weaponLevel}', (255, 255, 255), genshin_font_origin(28), anchor='mm')
weapon_text.text((130, 305), f'精炼{str(weaponAffix)}', (255, 239, 173), genshin_font_origin(28), anchor='lm')
weaponEffect = str_len(weaponEffect, 25, 455)
weapon_text.text((25, 335), weaponEffect, (255, 255, 255), genshin_font_origin(25))
img.paste(weapon_img, (387, 570), weapon_img)
# 圣遗物部分
artifactsAllScore = 0
for aritifact in raw_data['equipList']:
artifacts_img = Image.open(TEXT_PATH / 'char_info_artifacts.png')
artifacts_piece_img = Image.open(RELIC_PATH / '{}.png'.format(aritifact['icon']))
artifacts_piece_new_img = artifacts_piece_img.resize((180, 180), Image.Resampling.LANCZOS).convert("RGBA")
artifacts_piece_new_img.putalpha(
artifacts_piece_new_img.getchannel('A').point(lambda x: round(x * 0.5) if x > 0 else 0))
artifacts_img.paste(artifacts_piece_new_img, (100, 35), artifacts_piece_new_img)
aritifactStar_img = get_star_png(aritifact['aritifactStar'])
artifactsPos = aritifact['aritifactPieceName']
artifacts_img.paste(aritifactStar_img, (20, 165), aritifactStar_img)
artifacts_text = ImageDraw.Draw(artifacts_img)
artifacts_text.text((30, 66), aritifact['aritifactName'], (255, 255, 255), genshin_font_origin(34), anchor='lm')
artifacts_text.text((30, 102), artifactsPos, (255, 255, 255), genshin_font_origin(20), anchor='lm')
mainValue = aritifact['reliquaryMainstat']['statValue']
mainName = aritifact['reliquaryMainstat']['statName']
mainLevel = aritifact['aritifactLevel']
if mainName in ['攻击力', '血量', '防御力', '元素精通']:
mainValueStr = str(mainValue)
else:
mainValueStr = str(mainValue) + '%'
mainNameNew = mainName.replace('百分比', '')
artifacts_text.text((26, 140), mainNameNew, (255, 255, 255), genshin_font_origin(28), anchor='lm')
artifacts_text.text((268, 140), mainValueStr, (255, 255, 255), genshin_font_origin(28), anchor='rm')
artifacts_text.text((55, 219), '+{}'.format(str(mainLevel)), (255, 255, 255), genshin_font_origin(24),
anchor='mm')
artifactsScore = 0
for index, i in enumerate(aritifact['reliquarySubstats']):
subName = i['statName']
subValue = i['statValue']
if subName in ['攻击力', '血量', '防御力', '元素精通']:
subValueStr = str(subValue)
if subName == '血量':
artifactsScore += subValue * 0.014
elif subName == '攻击力':
artifactsScore += subValue * 0.12
elif subName == '防御力':
artifactsScore += subValue * 0.18
elif subName == '元素精通':
artifactsScore += subValue * 0.25
else:
subValueStr = str(subValue) + '%'
if subName == '暴击率':
artifactsScore += subValue * 2
elif subName == '暴击伤害':
artifactsScore += subValue * 1
elif subName == '元素精通':
artifactsScore += subValue * 0.25
elif subName == '元素充能效率':
artifactsScore += subValue * 0.65
elif subName == '百分比血量':
artifactsScore += subValue * 0.86
elif subName == '百分比攻击力':
artifactsScore += subValue * 1
elif subName == '百分比防御力':
artifactsScore += subValue * 0.7
artifacts_text.text((20, 263 + index * 30), '·{}+{}'.format(subName, subValueStr), (255, 255, 255),
genshin_font_origin(25), anchor='lm')
artifactsAllScore += artifactsScore
artifacts_text.text((268, 190), f'{math.ceil(artifactsScore)}', (255, 255, 255), genshin_font_origin(23),
anchor='rm')
if artifactsPos == '生之花':
img.paste(artifacts_img, (18, 1075), artifacts_img)
elif artifactsPos == '死之羽':
img.paste(artifacts_img, (318, 1075), artifacts_img)
elif artifactsPos == '时之沙':
img.paste(artifacts_img, (618, 1075), artifacts_img)
elif artifactsPos == '空之杯':
img.paste(artifacts_img, (18, 1447), artifacts_img)
elif artifactsPos == '理之冠':
img.paste(artifacts_img, (318, 1447), artifacts_img)
char_name = raw_data['avatarName']
char_level = raw_data['avatarLevel']
char_fetter = raw_data['avatarFetter']
# 评分算法
# 圣遗物总分 + 角色等级 + (a+e+q)*4 + 武器等级 * 1+(武器精炼数 -1 * 0.25
charAllScore = artifactsAllScore + int(char_level) + \
(a_skill_level + e_skill_level + q_skill_level) * 4 + \
int(weaponLevel) * (1 + ((int(weaponAffix) - 1) * 0.25))
# 角色基本信息
img_text = ImageDraw.Draw(img)
img_text.text((411, 72), char_name, (255, 255, 255), genshin_font_origin(55), anchor='lm')
img_text.text((411, 122), '等级{}'.format(char_level), (255, 255, 255), genshin_font_origin(40), anchor='lm')
img_text.text((747, 126), str(char_fetter), (255, 255, 255), genshin_font_origin(28), anchor='lm')
# aeq
# img_text.text((110, 771), a_skill_name, (255, 255, 255), genshin_font_origin(26), anchor='lm')
img_text.text((103, 812), f'{str(a_skill_level)}', (255, 255, 255), genshin_font_origin(30), anchor='mm')
# img_text.text((110, 872), e_skill_name, (255, 255, 255), genshin_font_origin(26), anchor='lm')
img_text.text((103, 915), f'{str(e_skill_level)}', (255, 255, 255), genshin_font_origin(30), anchor='mm')
# img_text.text((110, 973), q_skill_name, (255, 255, 255), genshin_font_origin(26), anchor='lm')
img_text.text((103, 1016), f'{str(q_skill_level)}', (255, 255, 255), genshin_font_origin(30), anchor='mm')
fight_prop = raw_data['avatarFightProp']
hp = fight_prop['hp']
attack = fight_prop['atk']
defense = fight_prop['def']
em = fight_prop['elementalMastery']
critrate = fight_prop['critRate']
critdmg = fight_prop['critDmg']
ce = fight_prop['energyRecharge']
dmgBonus = fight_prop['dmgBonus']
hp_green = fight_prop['addHp']
attack_green = fight_prop['addAtk']
defense_green = fight_prop['addDef']
# 属性
img_text.text((785, 174), str(round(hp)), (255, 255, 255), genshin_font_origin(28), anchor='rm')
img_text.text((785, 227), str(round(attack)), (255, 255, 255), genshin_font_origin(28), anchor='rm')
img_text.text((785, 280), str(round(defense)), (255, 255, 255), genshin_font_origin(28), anchor='rm')
img_text.text((785, 333), str(round(em)), (255, 255, 255), genshin_font_origin(28), anchor='rm')
img_text.text((785, 386), f'{str(round(critrate * 100, 2))}%', (255, 255, 255), genshin_font_origin(28),
anchor='rm')
img_text.text((785, 439), f'{str(round(critdmg * 100, 2))}%', (255, 255, 255), genshin_font_origin(28), anchor='rm')
img_text.text((785, 492), f'{str(round(ce * 100, 1))}%', (255, 255, 255), genshin_font_origin(28), anchor='rm')
img_text.text((785, 545), f'{str(round(dmgBonus * 100, 1))}%', (255, 255, 255), genshin_font_origin(28),
anchor='rm')
img_text.text((805, 174), f'(+{str(round(hp_green))})', (95, 251, 80), genshin_font_origin(28), anchor='lm')
img_text.text((805, 227), f'(+{str(round(attack_green))})', (95, 251, 80), genshin_font_origin(28), anchor='lm')
img_text.text((805, 280), f'(+{str(round(defense_green))})', (95, 251, 80), genshin_font_origin(28), anchor='lm')
uid = raw_data['playerUid']
data_time = raw_data['dataTime']
# uid
img_text.text((350, 1035), f'UID{uid}', (255, 255, 255), genshin_font_origin(24), anchor='rm')
# 数据最后更新时间
img_text.text((780, 600), f'数据最后更新于{data_time}', (255, 255, 255), genshin_font_origin(22), anchor='rm')
# 角色评分
img_text.text((904, 1505), f'圣遗物总分', (255, 255, 255), genshin_font_origin(45), anchor='rm')
img_text.text((904, 1570), f'{round(artifactsAllScore, 1)}', (255, 255, 255), genshin_font_origin(60), anchor='rm')
img_text.text((904, 1655), f'角色评分', (255, 255, 255), genshin_font_origin(45), anchor='rm')
img_text.text((904, 1720), f'{round(charAllScore, 1)}', (255, 255, 255), genshin_font_origin(60), anchor='rm')
img.save(f"data{sep}{uid}.png", format='PNG', subsampling=0, quality=100)
return f"data{sep}{uid}.png"

194
defs/enkaToData.py Normal file
View File

@ -0,0 +1,194 @@
from typing import Optional
from ci import client, sqlite
import json
import time
from defs.sources import PLAYER_PATH, avatarId2Name, avatarName2Element, skillId2Name, talentId2Name, weaponHash2Type, \
weaponHash2Name, propId2Name, artifact2attr, artifactId2Piece, icon2Name
async def enkaToData(uid: str) -> Optional[str]:
enka_data = await client.get(f'https://enka.shinshin.moe/u/{str(uid)}/__data.json')
enka_data = enka_data.json()
if not enka_data:
return None
now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
playerInfo = enka_data['playerInfo']
path = PLAYER_PATH / str(uid)
path.mkdir(parents=True, exist_ok=True)
with open(path / '{}.json'.format(str(uid)), 'w', encoding='UTF-8') as file:
json.dump(playerInfo, file, ensure_ascii=False)
with open(path / 'rawData.json', 'w', encoding='UTF-8') as file:
json.dump(enka_data, file, ensure_ascii=False)
if 'avatarInfoList' not in enka_data:
return f'UID{uid}刷新失败!未打开角色展柜!'
char_name_list = []
for char in enka_data['avatarInfoList']:
# 处理基本信息
char_data = {}
avatarId = char['avatarId']
char_data['playerUid'] = str(uid)
char_data['playerName'] = enka_data['playerInfo']['nickname']
char_data['avatarId'] = avatarId
avatarName = avatarId2Name[str(char['avatarId'])]
char_data['avatarName'] = avatarId2Name[str(char['avatarId'])]
char_name_list.append(char_data['avatarName'])
char_data['avatarFetter'] = char['fetterInfo']['expLevel']
char_data['avatarLevel'] = char['propMap']['4001']['val']
try:
char_data['avatarElement'] = avatarName2Element[char_data['avatarName']]
except KeyError:
check = skillId2Name['Name'][str(list(char['skillLevelMap'].keys())[0])]
if '' in check:
char_data['avatarElement'] = 'Anemo'
elif '' in check:
char_data['avatarElement'] = 'Electro'
elif '' in check:
char_data['avatarElement'] = 'Geo'
elif '' in check:
char_data['avatarElement'] = 'Dendro'
elif '' in check:
char_data['avatarElement'] = 'Cryo'
elif '' in check:
char_data['avatarElement'] = 'Hydro'
else:
char_data['avatarElement'] = 'Pyro'
char_data['dataTime'] = now
char_data['avatarSkill'] = []
# 处理天赋
for skill in char['skillLevelMap']:
skill_temp = {}
skill_temp['skillId'] = skill
skill_temp['skillName'] = skillId2Name['Name'][skill_temp['skillId']]
skill_temp['skillLevel'] = char['skillLevelMap'][skill]
skill_temp['skillIcon'] = skillId2Name['Icon'][skill_temp['skillId']]
char_data['avatarSkill'].append(skill_temp)
if char_data['avatarName'] == '神里绫华':
char_data['avatarSkill'][0], char_data['avatarSkill'][-1] = char_data['avatarSkill'][-1], \
char_data['avatarSkill'][0]
char_data['avatarSkill'][2], char_data['avatarSkill'][-1] = char_data['avatarSkill'][-1], \
char_data['avatarSkill'][2]
char_data['avatarEnName'] = char_data['avatarSkill'][1]['skillIcon'].split('_')[-2]
else:
char_data['avatarEnName'] = char_data['avatarSkill'][-1]['skillIcon'].split('_')[-2]
# 处理命座
talent_temp = []
if 'talentIdList' in char:
for index, talent in enumerate(char['talentIdList']):
talentTemp = {}
talentTemp['talentId'] = char['talentIdList'][index]
talentTemp['talentName'] = talentId2Name['Name'][str(talent)]
talentTemp['talentIcon'] = talentId2Name['Icon'][str(talent)]
talent_temp.append(talentTemp)
char_data['talentList'] = talent_temp
# 处理属性
fight_prop = {}
# 血量
fight_prop['hp'] = char["fightPropMap"]["2000"]
fight_prop['baseHp'] = char["fightPropMap"]["1"]
fight_prop['addHp'] = char["fightPropMap"]["2000"] - char["fightPropMap"]["1"]
# 攻击力
fight_prop['atk'] = char["fightPropMap"]["2001"]
fight_prop['baseAtk'] = char["fightPropMap"]["4"]
fight_prop['addAtk'] = char["fightPropMap"]["2001"] - char["fightPropMap"]["4"]
# 防御力
fight_prop['def'] = char["fightPropMap"]["2002"]
fight_prop['baseDef'] = char["fightPropMap"]["7"]
fight_prop['addDef'] = char["fightPropMap"]["2002"] - char["fightPropMap"]["7"]
# 元素精通
fight_prop['elementalMastery'] = char["fightPropMap"]["28"]
# 暴击率
fight_prop['critRate'] = char["fightPropMap"]["20"]
# 暴击伤害
fight_prop['critDmg'] = char["fightPropMap"]["22"]
# 充能效率
fight_prop['energyRecharge'] = char["fightPropMap"]["23"]
# 治疗&受治疗
fight_prop['healBonus'] = char["fightPropMap"]["26"]
fight_prop['healedBonus'] = char["fightPropMap"]["27"]
# 物理伤害加成 & 抗性
fight_prop['physicalDmgSub'] = char["fightPropMap"]["29"]
fight_prop['physicalDmgBonus'] = char["fightPropMap"]["30"]
# 伤害加成
for i in range(40, 47):
if char["fightPropMap"][str(i)] > 0:
fight_prop['dmgBonus'] = char["fightPropMap"][str(i)]
break
else:
fight_prop['dmgBonus'] = 0
char_data['avatarFightProp'] = fight_prop
# 处理武器
weapon_info = {}
weapon_data = char['equipList'][-1]
weapon_info['itemId'] = weapon_data['itemId']
weapon_info['nameTextMapHash'] = weapon_data['flat']['nameTextMapHash']
weapon_info['weaponIcon'] = weapon_data['flat']['icon']
weapon_info['weaponType'] = weaponHash2Type[weapon_info['nameTextMapHash']]
weapon_info['weaponName'] = weaponHash2Name[weapon_info['nameTextMapHash']]
weapon_info['weaponStar'] = weapon_data['flat']['rankLevel']
# 防止未精炼
if 'promoteLevel' in weapon_data['weapon']:
weapon_info['promoteLevel'] = weapon_data['weapon']['promoteLevel']
else:
weapon_info['promoteLevel'] = 0
weapon_info['weaponLevel'] = weapon_data['weapon']['level']
if 'affixMap' in weapon_data['weapon']:
weapon_info['weaponAffix'] = list(weapon_data['weapon']['affixMap'].values())[0] + 1
else:
weapon_info['weaponAffix'] = 1
weapon_info['weaponStats'] = []
for k in weapon_data['flat']['weaponStats']:
weapon_prop_temp = {}
weapon_prop_temp['appendPropId'] = k['appendPropId']
weapon_prop_temp['statName'] = propId2Name[k['appendPropId']]
weapon_prop_temp['statValue'] = k['statValue']
weapon_info['weaponStats'].append(weapon_prop_temp)
# 武器特效须请求API
effect_raw = await client.get('https://info.minigg.cn/weapons?query={}'.format(weapon_info['weaponName']))
effect_raw = effect_raw.json()
if 'effect' in effect_raw:
effect = effect_raw['effect'].format(*effect_raw['r{}'.format(str(weapon_info['weaponAffix']))])
else:
effect = '无特效。'
weapon_info['weaponEffect'] = effect
char_data['weaponInfo'] = weapon_info
# 处理圣遗物
artifacts_info = []
artifacts_data = char['equipList'][:-1]
for artifact in artifacts_data:
artifact_temp = {}
artifact_temp['itemId'] = artifact['itemId']
artifact_temp['nameTextMapHash'] = artifact['flat']['nameTextMapHash']
artifact_temp['icon'] = artifact['flat']['icon']
artifact_temp['aritifactName'] = icon2Name[artifact['flat']['icon']]
artifact_temp['aritifactSetsName'] = artifact2attr['mapping'].get(artifact_temp['aritifactName'], "")
artifact_temp['aritifactSetPiece'] = artifactId2Piece[artifact_temp['icon'].split('_')[-1]][0]
artifact_temp['aritifactPieceName'] = artifactId2Piece[artifact_temp['icon'].split('_')[-1]][1]
artifact_temp['aritifactStar'] = artifact['flat']['rankLevel']
artifact_temp['aritifactLevel'] = artifact['reliquary']['level'] - 1
artifact_temp['reliquaryMainstat'] = artifact['flat']['reliquaryMainstat']
artifact_temp['reliquaryMainstat']['statName'] = propId2Name[
artifact_temp['reliquaryMainstat']['mainPropId']]
artifact_temp['reliquarySubstats'] = artifact['flat']['reliquarySubstats']
for sub in artifact_temp['reliquarySubstats']:
sub['statName'] = propId2Name[sub['appendPropId']]
artifacts_info.append(artifact_temp)
char_data['equipList'] = artifacts_info
with open(path / '{}.json'.format(avatarName), 'w', encoding='UTF-8') as file:
json.dump(char_data, file, ensure_ascii=False)
char_name_list_str = ','.join(char_name_list)
return f'UID {uid} 刷新成功!刷新角色:{char_name_list_str}'

102
defs/player.py Normal file
View File

@ -0,0 +1,102 @@
import json
import time
from datetime import datetime
from os import listdir
from os.path import exists
from typing import List
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from defs.drawCharCard import draw_char_card
from defs.sources import PLAYER_PATH
from ci import channel_id, app, sqlite
def gen_char_dict(name: str, file_id: str) -> dict:
return {"name": name, "file_id": file_id, "time": int(time.time())}
class Player:
name: str = ""
uid: str = ""
all_char: List[dict] = []
time: int = 0
def __init__(self, uid: str):
self.uid = uid
self.time = int(time.time())
if not exists(PLAYER_PATH / uid / f"{uid}.json"):
return
with open(PLAYER_PATH / uid / f"{uid}.json", "r", encoding="utf-8") as fp:
data = json.load(fp)
self.name = data.get("nickname", "")
def update_name(self):
with open(PLAYER_PATH / self.uid / f"{self.uid}.json", "r", encoding="utf-8") as fp:
data = json.load(fp)
self.name = data.get("nickname", "")
async def update_char(self):
all_char = listdir(PLAYER_PATH / self.uid)
try:
all_char.remove(f"{self.uid}.json")
except ValueError:
pass
try:
all_char.remove("rawData.json")
except ValueError:
pass
all_char = [i[:-5] for i in all_char]
for i in all_char:
for f in self.all_char:
if f["name"] == i:
self.all_char.remove(f)
break
try:
with open(PLAYER_PATH / self.uid / f"{i}.json", "r", encoding="utf-8") as fp:
data = json.load(fp)
path = await draw_char_card(data)
msg = await app.send_photo(channel_id, path)
self.all_char.append(gen_char_dict(i, msg.photo.file_id))
except Exception as e:
print(e)
continue
def export(self):
return {"name": self.name, "uid": self.uid, "time": int(time.time()), "all_char": self.all_char}
def restore(self):
sources = sqlite.get(self.uid, None)
if sources:
self.name = sources.get("name", "")
self.time = sources.get("time", 0)
self.all_char = sources.get("all_char", [])
def gen_keyboard(self) -> InlineKeyboardMarkup:
data = []
temp_ = []
num = 0
for i in self.all_char:
name = i.get("name", "")
temp_.append(InlineKeyboardButton(name, callback_data=f"{self.uid}|{name}"))
num += 1
if num == 3:
data.append(temp_)
temp_ = []
num = 0
return InlineKeyboardMarkup(data)
def gen_back(self) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup([[InlineKeyboardButton("返回", callback_data=self.uid)]])
@staticmethod
def parse_time(time_stamp: int) -> str:
return datetime.strftime(datetime.fromtimestamp(time_stamp), '%Y-%m-%d %H:%M:%S')
def gen_all_char(self) -> str:
if not self.all_char:
return ""
text = "缓存角色有:\n"
for i in self.all_char:
text += "🔸 " + i.get("name", "") + f" `{self.parse_time(i.get('time', time.time()))}`\n"
return text

18
defs/refresh.py Normal file
View File

@ -0,0 +1,18 @@
from defs.enkaToData import enkaToData
from defs.player import Player
from ci import sqlite
import time
async def refresh_player(uid: str) -> str:
data = Player(uid)
data.restore()
if data.time + 60 * 5 > int(time.time()):
return "刷新过快,请稍等一会儿再试"
text = await enkaToData(uid)
if not text:
return "数据刷新失败,请重试"
data.update_name()
await data.update_char()
sqlite[uid] = data.export()
return text

50
defs/sources.py Normal file
View File

@ -0,0 +1,50 @@
from pathlib import Path
import json
R_PATH = Path("resources")
MAP_PATH = R_PATH / "map"
TEXT_PATH = R_PATH / "texture2D"
ICON_PATH = R_PATH / "icon"
GACHA_PATH = R_PATH / "gachaImg"
PLAYER_PATH = R_PATH / "player"
RELIC_PATH = R_PATH / "relicIcon"
verison = '2.7.0'
avatarName2Element_fileName = f'avatarName2Element_mapping_{verison}.json'
weaponHash2Name_fileName = f'weaponHash2Name_mapping_{verison}.json'
weaponHash2Type_fileName = f'weaponHash2Type_mapping_{verison}.json'
skillId2Name_fileName = f'skillId2Name_mapping_{verison}.json'
talentId2Name_fileName = f'talentId2Name_mapping_{verison}.json'
avatarId2Name_fileName = f'avatarId2Name_mapping_{verison}.json'
with open(MAP_PATH / avatarId2Name_fileName, "r", encoding='UTF-8') as f:
avatarId2Name = json.load(f)
with open(MAP_PATH / 'icon2Name_mapping_2.6.0.json', "r", encoding='UTF-8') as f:
icon2Name = json.load(f)
with open(MAP_PATH / 'artifact2attr_mapping_2.6.0.json', "r", encoding='UTF-8') as f:
artifact2attr = json.load(f)
with open(MAP_PATH / 'propId2Name_mapping.json', "r", encoding='UTF-8') as f:
propId2Name = json.load(f)
with open(MAP_PATH / weaponHash2Name_fileName, "r", encoding='UTF-8') as f:
weaponHash2Name = json.load(f)
with open(MAP_PATH / weaponHash2Type_fileName, "r", encoding='UTF-8') as f:
weaponHash2Type = json.load(f)
with open(MAP_PATH / 'artifactId2Piece_mapping.json', "r", encoding='UTF-8') as f:
artifactId2Piece = json.load(f)
with open(MAP_PATH / skillId2Name_fileName, "r", encoding='UTF-8') as f:
skillId2Name = json.load(f)
with open(MAP_PATH / talentId2Name_fileName, "r", encoding='UTF-8') as f:
talentId2Name = json.load(f)
with open(MAP_PATH / avatarName2Element_fileName, 'r', encoding='UTF-8') as f:
avatarName2Element = json.load(f)

7
main.py Normal file
View File

@ -0,0 +1,7 @@
import logging
from ci import app
# 日志记录
logging.basicConfig(level=logging.ERROR)
logging.info("Bot 已启动")
app.run()

18
plugins/admin.py Normal file
View File

@ -0,0 +1,18 @@
from pyrogram import filters, Client
from pyrogram.types import Message
from ci import app, admin_id
from defs.refresh import refresh_player
@app.on_message(filters.command(["refresh_admin"]) & filters.private)
async def refresh_command(_: Client, message: Message):
if message.from_user.id != admin_id:
return
if len(message.command) == 1:
return await message.reply("请输入 uid", quote=True)
if not message.command[1].isnumeric():
return await message.reply("请输入正确的 uid", quote=True)
uid = message.command[1]
msg = await message.reply(f"正在刷新数据,请稍等。。。", quote=True)
text = await refresh_player(uid)
await msg.edit(text)

28
plugins/bind.py Normal file
View File

@ -0,0 +1,28 @@
from pyrogram import filters, Client
from pyrogram.types import Message
from defs.bind import check_bind, get_bind_uid, set_bind, remove_bind
from defs.refresh import refresh_player
from defs.player import Player
from ci import app, me
@app.on_message(filters.command(["bind", f"bind@{me['result']['username']}"]) & filters.private)
async def bind_command(_: Client, message: Message):
if len(message.command) == 1:
if check_bind(message.from_user.id):
data = Player(get_bind_uid(message.from_user.id))
data.restore()
return await message.reply(f"您绑定的游戏 uid 为:{get_bind_uid(message.from_user.id)}\n\n"
f"{data.gen_all_char()}", quote=True)
else:
return await message.reply(f"请使用 <code>/bind [uid]</code> 绑定游戏 uid", quote=True)
if not message.command[1].isdigit():
if message.command[1] == "remove":
remove_bind(message.from_user.id)
return await message.reply("已解除绑定", quote=True)
return await message.reply("uid 非数字", quote=True)
uid = message.command[1]
set_bind(message.from_user.id, uid)
msg = await message.reply(f"绑定成功,您绑定的游戏 uid 为:{uid},正在刷新数据。。。", quote=True)
text = await refresh_player(uid)
await msg.edit(text)

33
plugins/callback.py Normal file
View File

@ -0,0 +1,33 @@
from os import sep
from pyrogram import Client
from pyrogram.types import CallbackQuery, InputMediaPhoto
from ci import app
from defs.player import Player
@app.on_callback_query()
async def answer_callback(_: Client, callback_query: CallbackQuery):
data = callback_query.data.split("|")
uid = data[0]
char = None
if len(data) > 1:
char = callback_query.data.split("|")[1]
data = Player(uid)
data.restore()
if not data.all_char:
return await callback_query.answer("没有可展示的角色,可能是数据未刷新", show_alert=True)
if not char:
await callback_query.message.edit_media(InputMediaPhoto(media=f"resources{sep}Kitsune.png",
caption=f"请选择 {data.name} 的一个角色:"))
return await callback_query.message.edit_reply_markup(reply_markup=data.gen_keyboard())
char_data = None
for i in data.all_char:
if i.get("name", "") == char:
char_data = i
break
if not char_data:
return await callback_query.answer("没有可展示的角色,可能是数据未刷新", show_alert=True)
await callback_query.message.edit_media(InputMediaPhoto(media=char_data["file_id"]))
await callback_query.message.edit_reply_markup(reply_markup=data.gen_back())

37
plugins/inline.py Normal file
View File

@ -0,0 +1,37 @@
from pyrogram import Client, emoji
from pyrogram.types import InlineQuery, InlineQueryResultCachedPhoto
from ci import app
from defs.bind import check_bind, get_bind_uid
from defs.player import Player
@app.on_inline_query()
async def answer_callback(_: Client, query: InlineQuery):
uid = None
if check_bind(query.from_user.id):
uid = get_bind_uid(query.from_user.id)
if query.query:
uid = query.query
if not uid:
return await query.answer(
results=[],
switch_pm_text=f'{emoji.CROSS_MARK} 没有搜索到任何结果',
switch_pm_parameter="start",
)
data = Player(uid)
data.restore()
if not data.all_char:
return await query.answer(
results=[],
switch_pm_text=f'{emoji.CROSS_MARK} 没有搜索到任何结果',
switch_pm_parameter="start",
)
inline_data = []
for i in data.all_char:
inline_data.append(InlineQueryResultCachedPhoto(photo_file_id=i["file_id"],
title=i["name"],
description=data.name))
await query.answer(inline_data,
switch_pm_text=f'{emoji.KEY} 搜索到了 {len(data.all_char)} 个角色',
switch_pm_parameter="start")

15
plugins/refresh.py Normal file
View File

@ -0,0 +1,15 @@
from pyrogram import filters, Client
from pyrogram.types import Message
from defs.bind import check_bind, get_bind_uid
from ci import app, me
from defs.refresh import refresh_player
@app.on_message(filters.command(["refresh", f"refresh@{me['result']['username']}"]) & filters.private)
async def refresh_command(_: Client, message: Message):
if not check_bind(message.from_user.id):
return await message.reply(f"请使用 <code>/bind [uid]</code> 绑定游戏 uid", quote=True)
uid = get_bind_uid(message.from_user.id)
msg = await message.reply(f"正在刷新数据,请稍等。。。", quote=True)
text = await refresh_player(uid)
await msg.edit(text)

30
plugins/search.py Normal file
View File

@ -0,0 +1,30 @@
from os import sep
from pyrogram import filters, Client
from pyrogram.types import Message
from defs.bind import check_bind, get_bind_uid
from ci import app, me
from defs.player import Player
@app.on_message(filters.command(["search", f"search@{me['result']['username']}"]))
async def search_command(_: Client, message: Message):
if message.sender_chat or not message.from_user:
return
uid = None
if check_bind(message.from_user.id):
uid = get_bind_uid(message.from_user.id)
if len(message.command) > 1:
if message.command[1].isnumeric():
uid = message.command[1]
if not uid:
return await message.reply("请使用 /search [uid] 或 /bind [uid] 绑定账号后搜索", quote=True)
data = Player(uid)
data.restore()
if not data.all_char:
return await message.reply("没有可展示的角色,可能是数据未刷新", quote=True)
await message.reply_photo(f"resources{sep}Kitsune.png",
caption=f"请选择 {data.name} 的一个角色:",
quote=True,
reply_markup=data.gen_keyboard())

29
plugins/start.py Normal file
View File

@ -0,0 +1,29 @@
from pyrogram import filters, Client
from pyrogram.types import Message
from ci import app, me
des = """
你好{} 我是 [{}]({})
> 请先使用 `/bind [uid]` 绑定游戏 uid 进行更新数据然后使用 `/search [uid可选]` 获取角色卡片
我基于公共 API 提供的数据来合成图片支持以下数据
`
- 等级
- 天赋
- 武器
- 面板数据
- 圣遗物
`
角色数据基于 [enka](https://enka.shinshin.moe)
图片模板基于 [GenshinUID](https://github.com/KimigaiiWuyi/GenshinUID)
"""
@app.on_message(filters.command(["start", f"start@{me['result']['username']}"]) & filters.private)
async def start_command(_: Client, message: Message):
await message.reply(des.format(message.from_user.mention(),
me["result"]["first_name"],
f"https://t.me/{me['result']['username']}"),
disable_web_page_preview=True,
quote=True)

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
httpx
pillow
pyrogram==2.0.26
pyromod
TGCrypto
sqlitedict

BIN
resources/Kitsune.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Some files were not shown because too many files have changed in this diff Show More