Archived
1
0
Fork 0
This repository has been archived on 2024-04-26. You can view files and clone it, but cannot push or open issues or pull requests.
akari-bot/modules/maimai/libraries/maimaidx_project.py
多羅狼 a560add7ab
fix
2023-12-07 01:35:44 +08:00

489 lines
No EOL
23 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
from core.utils.http import get_url
from core.utils.cache import random_cache_path
from .maimaidx_api_data import get_record, get_plate
from .maimaidx_music import TotalList
JINGLEBELL_SONG_ID = 70
total_list = TotalList()
plate_to_version = {
'': 'maimai',
'': 'maimai PLUS',
'': 'maimai GreeN',
'': 'maimai GreeN PLUS',
'': 'maimai ORANGE',
'': 'maimai ORANGE PLUS',
'': 'maimai ORANGE PLUS',
'': 'maimai PiNK',
'': 'maimai PiNK PLUS',
'': 'maimai PiNK PLUS',
'': 'maimai MURASAKi',
'': 'maimai MURASAKi PLUS',
'': 'maimai MURASAKi PLUS',
'': 'maimai MiLK',
'': 'MiLK PLUS',
'': 'maimai FiNALE',
'': 'maimai FiNALE',
'': 'maimai でらっくす',
'': 'maimai でらっくす',
'': 'maimai でらっくす',
'': 'maimai でらっくす Splash',
'': 'maimai でらっくす Splash',
'': 'maimai でらっくす UNiVERSE',
'': 'maimai でらっくす UNiVERSE',
'': 'maimai でらっくす FESTiVAL',
'': 'maimai でらっくす FESTiVAL'
}
score_to_rank = {
(0.0, 50.0): "D",
(50.0, 60.0): "C",
(60.0, 70.0): "B",
(70.0, 75.0): "BB",
(75.0, 80.0): "BBB",
(80.0, 90.0): "A",
(90.0, 94.0): "AA",
(94.0, 97.0): "AAA",
(97.0, 98.0): "S",
(98.0, 99.0): "S+",
(99.0, 99.5): "SS",
(99.5, 100.0): "SS+",
(100.0, 100.5): "SSS",
(100.5, float('inf')): "SSS+",
}
combo_conversion = {
"fc": "FC",
"fcp": "FC+",
"ap": "AP",
"app": "AP+",
}
sync_conversion = {
"fs": "FS",
"fsp": "FS+",
"fsd": "FDX",
"fsdp": "FDX+",
}
diffs = {
0: "Basic",
1: "Advanced",
2: "Expert",
3: "Master",
4: "Re:MASTER",
}
achievementList = [50.0, 60.0, 70.0, 75.0, 80.0, 90.0, 94.0, 97.0, 98.0, 99.0, 99.5, 100.0, 100.5] # 各个成绩对应的评级分界线(向上取)
scoreRank = list(score_to_rank.values()) # Rank字典的值文本显示
comboRank = list(combo_conversion.values()) # Combo字典的值文本显示
syncRank = list(sync_conversion.values()) # Sync字典的值文本显示
combo_rank = list(combo_conversion.keys()) # Combo字典的键API内显示
sync_rank = list(sync_conversion.keys()) # Sync字典的键API内显示
async def get_rank(msg, payload):
player_data = await get_record(msg, payload)
username = player_data['username']
url = f"https://www.diving-fish.com/api/maimaidxprober/rating_ranking"
rank_data = await get_url(url, 200, fmt='json')
sorted_data = sorted(rank_data, key=lambda x: x['ra'], reverse=True) # 根据rating排名并倒序
rating = 0
rank = None
total_rating = 0
total_rank = len(sorted_data)
for i, scoreboard in enumerate(sorted_data):
if scoreboard['username'] == username:
rank = i + 1
rating = scoreboard['ra']
total_rating += scoreboard['ra']
if not rank:
rank = total_rank
average_rating = total_rating / total_rank
surpassing_rate = (total_rank - rank) / total_rank * 100
time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
formatted_average_rating = "{:.4f}".format(average_rating)
formatted_surpassing_rate = "{:.2f}".format(surpassing_rate)
await msg.finish(msg.locale.t('maimai.message.rank', time=time, total_rank=total_rank, user=username,
rating=rating, rank=rank, average_rating=formatted_average_rating,
surpassing_rate=formatted_surpassing_rate))
async def get_player_score(msg, payload, input_id):
payload['version'] = list(set(version for version in plate_to_version.values())) # 全版本
res = await get_plate(msg, payload) # 获取用户成绩信息
verlist = res["verlist"]
music = (await total_list.get()).by_id(input_id)
level_scores = {level: [] for level in range(len(music['level']))} # 获取歌曲难度列表
for entry in verlist:
sid = entry["id"]
achievements = entry["achievements"]
fc = entry["fc"]
fs = entry["fs"]
level_index = entry["level_index"]
if str(sid) == input_id:
score_rank = next(
rank for interval, rank in score_to_rank.items() if interval[0] <= achievements < interval[1] # 根据成绩获得等级
)
combo_rank = combo_conversion.get(fc, "") # Combo字典转换
sync_rank = sync_conversion.get(fs, "") # Sync字典转换
level_scores[level_index].append((diffs[level_index], achievements, score_rank, combo_rank, sync_rank))
output_lines = []
for level, scores in level_scores.items(): # 使用循环输出格式化文本
if scores:
output_lines.append(f"{diffs[level]} {music['level'][level]}") # 难度字典转换
for score in scores:
level, achievements, score_rank, combo_rank, sync_rank = score
entry_output = f"{achievements} {score_rank}"
if combo_rank and sync_rank:
entry_output += f" {combo_rank} {sync_rank}"
elif combo_rank or sync_rank:
entry_output += f" {combo_rank}{sync_rank}"
output_lines.append(entry_output)
else:
output_lines.append(
f"{diffs[level]} {music['level'][level]}\n{msg.locale.t('maimai.message.info.no_record')}")
return '\n'.join(output_lines)
async def get_level_process(msg, payload, process, goal):
song_played = []
song_remain = []
payload['version'] = list(set(version for version in plate_to_version.values())) # 全版本
res = await get_plate(msg, payload) # 获取用户成绩信息
verlist = res["verlist"]
goal = goal.upper() # 输入强制转换为大写以适配字典
if goal in scoreRank:
achievement = achievementList[scoreRank.index(goal) - 1] # 根据列表将输入评级转换为成绩分界线
for song in verlist:
if song['level'] == process and song['achievements'] < achievement: # 达成难度条件但未达成目标条件
song_remain.append([song['id'], song['level_index']]) # 将剩余歌曲ID和难度加入目标列表
song_played.append([song['id'], song['level_index']]) # 将已游玩歌曲ID和难度加入列表
elif goal in comboRank:
combo_index = comboRank.index(goal) # 根据API结果字典转换
for song in verlist:
if song['level'] == process and (
(song['fc'] and combo_rank.index(
song['fc']) < combo_index) or not song['fc']): # 达成难度条件但未达成目标条件
song_remain.append([song['id'], song['level_index']]) # 将剩余歌曲ID和难度加入目标列表
song_played.append([song['id'], song['level_index']]) # 将已游玩歌曲ID和难度加入列表
elif goal in syncRank:
sync_index = syncRank.index(goal) # 根据API结果字典转换
for song in verlist:
if song['level'] == process and (
(song['fs'] and sync_rank.index(
song['fs']) < sync_index) or not song['fs']): # 达成难度条件但未达成目标条件
song_remain.append([song['id'], song['level_index']]) # 将剩余歌曲ID和难度加入目标列表
song_played.append([song['id'], song['level_index']]) # 将已游玩歌曲ID和难度加入列表
for music in (await total_list.get()): # 遍历歌曲列表
for i, lv in enumerate(music.level[2:]):
if lv == process and [int(music.id), i + 2] not in song_played:
song_remain.append([int(music.id), i + 2]) # 将未游玩歌曲ID和难度加入目标列表
song_remain = sorted(song_remain, key=lambda i: int(i[1])) # 根据难度排序结果
song_remain = sorted(song_remain, key=lambda i: int(i[0])) # 根据ID排序结果
songs = []
for song in song_remain: # 循环查询歌曲信息
music = (await total_list.get()).by_id(str(song[0]))
songs.append([music.id, music.title, diffs[song[1]], music.ds[song[1]], song[1], music.type])
output = ''
get_img = False
if len(song_remain) > 0:
if len(song_remain) < 50: # 若剩余歌曲小于50个则显示
song_record = [[s['id'], s['level_index']] for s in verlist]
output += f"{msg.locale.t('maimai.message.process.last', process=process, goal=goal)}\n"
for i, s in enumerate(sorted(songs, key=lambda i: i[3])): # 显示剩余歌曲信息
self_record = ''
if [int(s[0]), s[-2]] in song_record:
record_index = song_record.index([int(s[0]), s[-2]])
if goal in scoreRank:
self_record = str(verlist[record_index]['achievements']) + '%'
elif goal in comboRank:
if verlist[record_index]['fc']:
self_record = comboRank[combo_rank.index(verlist[record_index]['fc'])]
elif goal in syncRank:
if verlist[record_index]['fs']:
self_record = syncRank[sync_rank.index(verlist[record_index]['fs'])]
output += f"{s[0]}\u200B. {s[1]}{' (DX)' if s[5] == 'DX' else ''} {s[2]} {s[3]} {self_record}\n"
if len(song_remain) > 10: # 若剩余歌曲大于10个则使用图片形式
get_img = True
else:
await msg.finish(msg.locale.t('maimai.message.process', song_remain=len(song_remain), process=process, goal=goal))
else:
await msg.finish(msg.locale.t('maimai.message.process.completed', process=process, goal=goal))
return output, get_img
async def get_score_list(msg, payload, level):
song_list = []
player_data = await get_record(msg, payload)
username = player_data['username']
payload['version'] = list(set(version for version in plate_to_version.values())) # 全版本
res = await get_plate(msg, payload) # 获取用户成绩信息
verlist = res["verlist"]
for song in verlist:
if song['level'] == level:
song_list.append(song) # 将符合难度的成绩加入列表
output_lines = []
for s in enumerate(sorted(song_list, key=lambda i: i['achievements'], reverse=True)): # 根据成绩排序
music = (await total_list.get()).by_id(str(s[1]['id']))
output = f"{music.id}\u200B. {music.title}{' (DX)' if music.type == 'DX' else ''} {diffs[s[1]['level_index']]} {music.ds[s[1]['level_index']]} {s[1]['achievements']}%"
if s[1]["fc"] and s[1]["fs"]:
output += f" {combo_conversion.get(s[1]['fc'], '')} {sync_conversion.get(s[1]['fs'], '')}"
elif s[1]["fc"] or s[1]["fs"]:
output += f" {combo_conversion.get(s[1]['fc'], '')}{sync_conversion.get(s[1]['fs'], '')}"
output_lines.append(output)
outputs = '\n'.join(output_lines)
res = f"{msg.locale.t('maimai.message.scorelist', user=username, level=level)}\n{outputs}"
get_img = False
if len(output_lines) == 0:
await msg.finish(msg.locale.t("maimai.message.chart_not_found"))
elif len(output_lines) > 10:
get_img = True
return res, get_img
async def get_plate_process(msg, payload, plate):
song_played = []
song_remain_basic = []
song_remain_advanced = []
song_remain_expert = []
song_remain_master = []
song_remain_remaster = []
song_remain_difficult = []
version = plate[0]
goal = plate[1:]
get_img = False
if version == '': # 真代为无印版本
payload['version'] = ['maimai', 'maimai PLUS']
elif version in ['', '']: # 霸者和舞牌需要全版本
payload['version'] = list(set(version for version in list(plate_to_version.values())[:-9]))
elif version in plate_to_version and version != '': # “初”不是版本名称
payload['version'] = [plate_to_version[version]]
else:
await msg.finish(msg.locale.t('maimai.message.plate.plate_not_found'))
res = await get_plate(msg, payload) # 获取用户成绩信息
verlist = res["verlist"]
if goal in ['', '']:
for song in verlist: # 将剩余歌曲ID和难度加入目标列表
if song['level_index'] == 0 and song['achievements'] < (100.0 if goal == '' else 80.0):
song_remain_basic.append([song['id'], song['level_index']])
if song['level_index'] == 1 and song['achievements'] < (100.0 if goal == '' else 80.0):
song_remain_advanced.append([song['id'], song['level_index']])
if song['level_index'] == 2 and song['achievements'] < (100.0 if goal == '' else 80.0):
song_remain_expert.append([song['id'], song['level_index']])
if song['level_index'] == 3 and song['achievements'] < (100.0 if goal == '' else 80.0):
song_remain_master.append([song['id'], song['level_index']])
if version in [
'', ''] and song['level_index'] == 4 and song['achievements'] < (
100.0 if goal == '' else 80.0):
song_remain_remaster.append([song['id'], song['level_index']]) # 霸者和舞牌需要Re:MASTER难度
song_played.append([song['id'], song['level_index']])
elif goal in ['', '']:
for song in verlist: # 将剩余歌曲ID和难度加入目标列表
if song['level_index'] == 0 and not song['fc']:
song_remain_basic.append([song['id'], song['level_index']])
if song['level_index'] == 1 and not song['fc']:
song_remain_advanced.append([song['id'], song['level_index']])
if song['level_index'] == 2 and not song['fc']:
song_remain_expert.append([song['id'], song['level_index']])
if song['level_index'] == 3 and not song['fc']:
song_remain_master.append([song['id'], song['level_index']])
if version == '' and song['level_index'] == 4 and not song['fc']:
song_remain_remaster.append([song['id'], song['level_index']])
song_played.append([song['id'], song['level_index']])
elif goal == '舞舞':
for song in verlist: # 将剩余歌曲ID和难度加入目标列表
if song['level_index'] == 0 and song['fs'] not in ['fsd', 'fsdp']:
song_remain_basic.append([song['id'], song['level_index']])
if song['level_index'] == 1 and song['fs'] not in ['fsd', 'fsdp']:
song_remain_advanced.append([song['id'], song['level_index']])
if song['level_index'] == 2 and song['fs'] not in ['fsd', 'fsdp']:
song_remain_expert.append([song['id'], song['level_index']])
if song['level_index'] == 3 and song['fs'] not in ['fsd', 'fsdp']:
song_remain_master.append([song['id'], song['level_index']])
if version == '' and song['level_index'] == 4 and song['fs'] not in ['fsd', 'fsdp']:
song_remain_remaster.append([song['id'], song['level_index']])
song_played.append([song['id'], song['level_index']])
elif goal == '':
for song in verlist: # 将剩余歌曲ID和难度加入目标列表
if song['level_index'] == 0 and song['fc'] not in ['ap', 'app']:
song_remain_basic.append([song['id'], song['level_index']])
if song['level_index'] == 1 and song['fc'] not in ['ap', 'app']:
song_remain_advanced.append([song['id'], song['level_index']])
if song['level_index'] == 2 and song['fc'] not in ['ap', 'app']:
song_remain_expert.append([song['id'], song['level_index']])
if song['level_index'] == 3 and song['fc'] not in ['ap', 'app']:
song_remain_master.append([song['id'], song['level_index']])
if version == '' and song['level_index'] == 4 and song['fc'] not in ['ap', 'app']:
song_remain_remaster.append([song['id'], song['level_index']])
song_played.append([song['id'], song['level_index']])
else:
await msg.finish(msg.locale.t('maimai.message.plate.plate_not_found'))
for music in (await total_list.get()): # 将未游玩歌曲ID加入目标列表
if music['basic_info']['from'] in payload['version']:
if [int(music.id), 0] not in song_played:
song_remain_basic.append([int(music.id), 0])
if [int(music.id), 1] not in song_played:
song_remain_advanced.append([int(music.id), 1])
if [int(music.id), 2] not in song_played:
song_remain_expert.append([int(music.id), 2])
if [int(music.id), 3] not in song_played:
song_remain_master.append([int(music.id), 3])
if version in ['', ''] and len(music.level) == 5 and [int(music.id), 4] not in song_played:
song_remain_remaster.append([int(music.id), 4])
song_remain_basic = sorted(song_remain_basic, key=lambda i: int(i[0])) # 根据ID排序结果
song_remain_advanced = sorted(song_remain_advanced, key=lambda i: int(i[0]))
song_remain_expert = sorted(song_remain_expert, key=lambda i: int(i[0]))
song_remain_master = sorted(song_remain_master, key=lambda i: int(i[0]))
song_remain_remaster = sorted(song_remain_remaster, key=lambda i: int(i[0]))
for song in song_remain_basic + song_remain_advanced + song_remain_expert + song_remain_master + song_remain_remaster: # 循环查询歌曲信息
music = (await total_list.get()).by_id(str(song[0]))
if music.ds[song[1]] > 13.6: # 将难度为13+以上的谱面加入列表
song_remain_difficult.append([music.id, music.title, diffs[song[1]],
music.ds[song[1]], song[1], music.type])
if version == '': # 真代歌曲不包含“​ジングルベル”
song_remain_basic = [music for music in song_remain_basic if music[0] != JINGLEBELL_SONG_ID]
song_remain_advanced = [music for music in song_remain_advanced if music[0] != JINGLEBELL_SONG_ID]
song_remain_expert = [music for music in song_remain_expert if music[0] != JINGLEBELL_SONG_ID]
song_remain_master = [music for music in song_remain_master if music[0] != JINGLEBELL_SONG_ID]
song_remain_remaster = [music for music in song_remain_remaster if music[0] != JINGLEBELL_SONG_ID]
song_remain_difficult = [music for music in song_remain_difficult if int(music[0]) != JINGLEBELL_SONG_ID]
song_remain: list[list] = song_remain_basic + song_remain_advanced + \
song_remain_expert + song_remain_master + song_remain_remaster
prompt = msg.locale.t('maimai.message.plate', plate=plate,
song_remain_basic=len(song_remain_basic),
song_remain_advanced=len(song_remain_advanced),
song_remain_expert=len(song_remain_expert),
song_remain_master=len(song_remain_master))
if version in ['', '']: # 霸者和舞牌需要Re:MASTER难度
prompt += msg.locale.t('maimai.message.plate.remaster', song_remain_remaster=len(song_remain_remaster))
prompt += msg.locale.t('message.end')
await msg.send_message(prompt.strip())
song_record = [[s['id'], s['level_index']] for s in verlist]
output = ''
if len(song_remain_difficult) > 0:
if len(song_remain_difficult) < 50: # 若剩余歌曲小于50个则显示
output += msg.locale.t('maimai.message.plate.difficult.last') + '\n'
for i, s in enumerate(sorted(song_remain_difficult, key=lambda i: i[3])): # 根据定数排序结果
self_record = ''
if [int(s[0]), s[-2]] in song_record: # 显示剩余13+以上歌曲信息
record_index = song_record.index([int(s[0]), s[-2]])
if goal in ['', '']:
self_record = str(verlist[record_index]['achievements']) + '%'
elif goal in ['', '', '']:
if verlist[record_index]['fc']:
self_record = comboRank[combo_rank.index(verlist[record_index]['fc'])]
elif goal == '舞舞':
if verlist[record_index]['fs']:
self_record = syncRank[sync_rank.index(verlist[record_index]['fs'])]
output += f"{s[0]}\u200B. {s[1]}{' (DX)' if s[5] == 'DX' else ''} {s[2]} {s[3]} {self_record}".strip() + '\n'
if len(song_remain_difficult) > 10: # 若剩余歌曲大于10个则使用图片形式
get_img = True
else:
output += msg.locale.t('maimai.message.plate.difficult', song_remain=len(song_remain_difficult))
elif len(song_remain) > 0:
for i, s in enumerate(song_remain):
m = (await total_list.get()).by_id(str(s[0]))
ds = m.ds[s[1]]
song_remain[i].append(ds)
if len(song_remain) < 50: # 若剩余歌曲小于50个则显示
output += msg.locale.t('maimai.message.plate.last') + '\n'
for i, s in enumerate(sorted(song_remain, key=lambda i: i[2])): # 根据难度排序结果
m = (await total_list.get()).by_id(str(s[0]))
self_record = ''
if [int(s[0]), s[-2]] in song_record: # 显示剩余歌曲信息
record_index = song_record.index([int(s[0]), s[-2]])
if goal in ['', '']:
self_record = str(verlist[record_index]['achievements']) + '%'
elif goal in ['', '', '']:
if verlist[record_index]['fc']:
self_record = comboRank[combo_rank.index(verlist[record_index]['fc'])]
elif goal == '舞舞':
if verlist[record_index]['fs']:
self_record = syncRank[sync_rank.index(verlist[record_index]['fs'])]
output += f"{m.id}\u200B. {m.title}{' (DX)' if m.type == 'DX' else ''} {diffs[s[1]]} {m.ds[s[1]]} {self_record}".strip(
) + '\n'
if len(song_remain) > 10: # 若剩余歌曲大于10个则使用图片形式
get_img = True
else:
output += msg.locale.t('maimai.message.plate.difficult.completed')
else:
output += msg.locale.t('maimai.message.plate.completed', plate=plate)
return output, get_img
def get_global_data(music, level_index):
stats = music.stats[level_index]
fc_data_pair = [(c.upper() if c else 'Not FC', count) for c, count in zip([''] + comboRank, stats.fc_dist)]
acc_data_pair = [(s.upper(), count) for s, count in zip(scoreRank, stats.dist)]
fig, axs = plt.subplots(1, 2, figsize=(10, 8))
# Combo Plot
labels, counts = zip(*fc_data_pair)
colors = plt.cm.viridis(np.linspace(0, 1, len(labels)))
axs[0].pie(counts, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors)
axs[0].set_title('Combo')
# Sync Plot
labels, counts = zip(*acc_data_pair)
colors = plt.cm.viridis(np.linspace(0, 1, len(labels)))
axs[1].pie(counts, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors)
axs[1].set_title('Sync')
plt.suptitle(f'{music.id} {music.title} {diffs[level_index]}', color='#2c343c')
plt.legend(labels, loc='upper left')
plt.show()
# Save the plot to an image file
data_path = random_cache_path()
path = f'{data_path}.png'
fig.savefig(path)
return path