from starlette.responses import Response
from starlette.requests import Request
from starlette.routing import Route
import json
import copy
import xml.etree.ElementTree as ET
from datetime import datetime
from config import COIN_REWARD
from api.database import player_database, results, decrypt_fields_to_user_info, set_device_data_using_decrypted_fields, results_query, set_user_data_using_decrypted_fields, clear_rank_cache
from api.crypt import decrypt_fields
from api.template import START_STAGES, EXP_UNLOCKED_SONGS, RESULT_XML
from api.misc import should_serve
async def score_delta(mode, old_score, new_score):
mobile_modes = [1, 2, 3]
arcade_modes = [11, 12, 13]
if mode in mobile_modes:
return new_score - old_score, 0, new_score - old_score
elif mode in arcade_modes:
return 0, new_score - old_score, new_score - old_score
else:
return 0, 0, 0
async def result_request(request: Request):
decrypted_fields, _ = await decrypt_fields(request)
if not decrypted_fields:
return Response("""10Invalid request data.""", media_type="application/xml")
should_serve_result = await should_serve(decrypted_fields)
if not should_serve_result:
return Response("""403Access denied.""", media_type="application/xml")
device_id = decrypted_fields[b'vid'][0].decode()
user_info, device_info = await decrypt_fields_to_user_info(decrypted_fields)
tree = copy.deepcopy(RESULT_XML)
root = tree.getroot()
stts = decrypted_fields[b'stts'][0].decode()
song_id = int(decrypted_fields[b'id'][0].decode())
mode = int(decrypted_fields[b'mode'][0].decode())
avatar = int(decrypted_fields[b'avatar'][0].decode())
score = int(decrypted_fields[b'score'][0].decode())
high_score = decrypted_fields[b'high_score'][0].decode()
play_rslt = decrypted_fields[b'play_rslt'][0].decode()
item = int(decrypted_fields[b'item'][0].decode())
device_os = decrypted_fields[b'os'][0].decode()
os_ver = decrypted_fields[b'os_ver'][0].decode()
ver = decrypted_fields[b'ver'][0].decode()
stts = "[" + stts + "]"
high_score = "[" + high_score + "]"
play_rslt = "[" + play_rslt + "]"
try:
stts = json.loads(stts)
high_score = json.loads(high_score)
play_rslt = json.loads(play_rslt)
except:
return Response("""10Invalid request data.""", media_type="application/xml")
cache_key = f"{song_id}-{mode}"
# delete cache with key
await clear_rank_cache(cache_key)
# Start results processing
target_row_id = 0
rank = None
user_id = user_info['id'] if user_info else None
if user_id:
query_param = {
"song_id": song_id,
"mode": mode,
"user_id": user_id
}
records = await results_query(query_param)
mobile_delta, arcade_delta, total_delta = 0, 0, 0
if len(records) != 0:
# user row exists
target_row_id = records[0]['id']
if score > records[0]['score']:
mobile_delta, arcade_delta, total_delta = await score_delta(mode, records[0]['score'], score)
update_query = results.update().where(results.c.id == records[0]['id']).values(
device_id=device_id,
stts=stts,
avatar=avatar,
score=score,
high_score=high_score,
play_rslt=play_rslt,
item=item,
os=device_os,
os_ver=os_ver,
ver=ver
)
await player_database.execute(update_query)
else:
# insert new row
mobile_delta, arcade_delta, total_delta = await score_delta(mode, 0, score)
insert_query = results.insert().values(
device_id=device_id,
user_id=user_id,
stts=stts,
song_id=song_id,
mode=mode,
avatar=avatar,
score=score,
high_score=high_score,
play_rslt=play_rslt,
item=item,
os=device_os,
os_ver=os_ver,
ver=ver,
created_at=datetime.utcnow(),
updated_at=datetime.utcnow()
)
result = await player_database.execute(insert_query)
target_row_id = result
# Calculate final rank for client display
query_param = {
"song_id": song_id,
"mode": mode
}
records = await results_query(query_param)
rank = None
for idx, record in enumerate(records, start=1):
if record["id"] == target_row_id:
rank = idx
break
# Update user score delta
if total_delta:
update_data = {
"mobile_delta": user_info['mobile_delta'] + mobile_delta,
"arcade_delta": user_info['arcade_delta'] + arcade_delta,
"total_delta": user_info['total_delta'] + total_delta
}
await set_user_data_using_decrypted_fields(decrypted_fields, update_data)
# Unlocking mission stages and updating avatars
my_stage = set(device_info["my_stage"]) if device_info and device_info["my_stage"] else set(START_STAGES)
current_exp = stts[0]
for song in EXP_UNLOCKED_SONGS:
if song["lvl"] <= current_exp:
my_stage.add(song["id"])
my_stage = sorted(my_stage)
update_data = {
"lvl": current_exp,
"avatar": int(avatar),
"my_stage": my_stage
}
# add coins, skip 4max placeholder songs
if int(song_id) not in range(616, 1024) or int(mode) not in range(0, 4):
coin_mp = user_info['coin_mp'] if user_info else 1
current_coin = device_info["coin"] if device_info and device_info["coin"] else 0
updated_coin = current_coin + COIN_REWARD * coin_mp
update_data["coin"] = updated_coin
await set_device_data_using_decrypted_fields(decrypted_fields, update_data)
after_element = root.find('.//after')
after_element.text = str(rank)
xml_response = ET.tostring(tree.getroot(), encoding='unicode')
return Response(xml_response, media_type="application/xml")
routes = [
Route('/result.php', result_request, methods=['GET'])
]