feat: Add song difficulty leaderboard feature

This commit is contained in:
2026-01-18 07:42:58 +08:00
parent 8d58bf683f
commit 6686f5ae15
8 changed files with 1831 additions and 1086 deletions

135
app.py
View File

@@ -13,6 +13,7 @@ import requests
import schema
import os
import time
from datetime import datetime
# -- カスタム --
import traceback
@@ -105,6 +106,13 @@ db.users.create_index('username', unique=True)
db.songs.create_index('id', unique=True)
db.songs.create_index('song_type')
db.scores.create_index('username')
db.leaderboard.create_index([
('song_id', 1),
('difficulty', 1),
('month', 1),
('score', -1)
])
db.leaderboard.create_index('username')
class HashException(Exception):
@@ -746,6 +754,133 @@ def route_api_scores_get():
return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name'], 'don': don})
@app.route(basedir + 'api/leaderboard/submit', methods=['POST'])
@login_required
def route_api_leaderboard_submit():
data = request.get_json()
if not schema.validate(data, schema.leaderboard_submit):
return abort(400)
username = session.get('username')
user = db.users.find_one({'username': username})
current_month = datetime.now().strftime('%Y-%m')
# Handle song_id type
song_id = data['song_id']
query = {
'song_id': song_id,
'difficulty': data['difficulty'],
'month': current_month,
'username': username
}
existing = db.leaderboard.find_one(query)
new_record = False
rank_info = None
if not existing or data['score'] > existing['score']:
entry = {
'song_id': song_id,
'difficulty': data['difficulty'],
'month': current_month,
'username': username,
'display_name': user['display_name'],
'don': get_db_don(user),
'score': data['score'],
'good': data.get('good', 0),
'ok': data.get('ok', 0),
'bad': data.get('bad', 0),
'maxCombo': data.get('maxCombo', 0),
'timestamp': datetime.now()
}
db.leaderboard.update_one(query, {'$set': entry}, upsert=True)
new_record = True
# Get rank
count = db.leaderboard.count_documents({
'song_id': song_id,
'difficulty': data['difficulty'],
'month': current_month,
'score': {'$gt': data['score']}
})
rank = count + 1
rank_info = rank
return jsonify({
'status': 'ok',
'new_record': new_record,
'rank': rank_info
})
@app.route(basedir + 'api/leaderboard/get')
def route_api_leaderboard_get():
song_id = request.args.get('song_id')
difficulty = request.args.get('difficulty')
if not song_id or not difficulty:
return abort(400)
# Try convert song_id to int if it looks like one
if re.match('^[0-9]+$', str(song_id)):
song_id = int(song_id)
current_month = datetime.now().strftime('%Y-%m')
query = {
'song_id': song_id,
'difficulty': difficulty,
'month': current_month
}
# Get top 50
leaderboard = list(db.leaderboard.find(query, {'_id': False}).sort('score', -1).limit(50))
user_rank = None
if session.get('username'):
username = session.get('username')
user_entry = db.leaderboard.find_one({
'song_id': song_id,
'difficulty': difficulty,
'month': current_month,
'username': username
}, {'_id': False})
if user_entry:
# Calculate rank
count = db.leaderboard.count_documents({
'song_id': song_id,
'difficulty': difficulty,
'month': current_month,
'score': {'$gt': user_entry['score']}
})
user_rank = {
'rank': count + 1,
'entry': user_entry
}
return jsonify({
'status': 'ok',
'leaderboard': leaderboard,
'user_rank': user_rank
})
@app.route(basedir + 'api/leaderboard/reset', methods=['POST'])
@admin_required(level=100)
def route_api_leaderboard_reset():
current_month = datetime.now().strftime('%Y-%m')
result = db.leaderboard.delete_many({'month': {'$ne': current_month}})
return jsonify({
'status': 'ok',
'deleted_count': result.deleted_count,
'current_month': current_month
})
@app.route(basedir + 'privacy')
def route_api_privacy():
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))