diff --git a/new_server_7003/api/account.py b/new_server_7003/api/account.py index c1915ee..d7f00b8 100644 --- a/new_server_7003/api/account.py +++ b/new_server_7003/api/account.py @@ -189,7 +189,7 @@ async def register(request: Request): if not decrypted_fields: return inform_page("FAILED:
Invalid request data.", 0) - user_info, _ = await decrypt_fields_to_user_info(decrypted_fields) + user_info = await user_name_to_user_info(username) if user_info: return inform_page("FAILED:
Another user already has this name.", 0) diff --git a/new_server_7003/api/admin.py b/new_server_7003/api/admin.py index c5eaeba..32290d5 100644 --- a/new_server_7003/api/admin.py +++ b/new_server_7003/api/admin.py @@ -4,6 +4,8 @@ from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse import sqlalchemy import json from datetime import datetime +import os +import xml.etree.ElementTree as ET from api.database import player_database, accounts, results, devices, whitelists, blacklists, batch_tokens, binds, webs, logs, is_admin, read_user_save_file, write_user_save_file from api.misc import crc32_decimal @@ -11,11 +13,11 @@ from api.misc import crc32_decimal TABLE_MAP = { "accounts": (accounts, ["id", "username", "password_hash", "save_crc", "save_timestamp", "save_id", "coin_mp", "title", "avatar", "mobile_delta", "arcade_delta", "total_delta", "created_at", "updated_at"]), "results": (results, ["id", "device_id", "stts", "song_id", "mode", "avatar", "score", "high_score", "play_rslt", "item", "os", "os_ver", "ver", "created_at"]), - "devices": (devices, ["device_id", "user_id", "my_stage", "my_avatar", "item", "daily_day", "coin", "lvl", "title", "avatar", "created_at", "updated_at", "last_login_at"]), + "devices": (devices, ["device_id", "user_id", "my_stage", "my_avatar", "item", "daily_day", "coin", "lvl", "title", "avatar", "created_at", "updated_at", "bind_token", "last_login_at"]), "whitelist": (whitelists, ["id", "device_id"]), "blacklist": (blacklists, ["id", "ban_terms", "reason"]), "batch_tokens": (batch_tokens, ["id", "batch_token", "expire_at", "uses_left", "auth_id", "created_at", "updated_at"]), - "binds": (binds, ["id", "user_id", "bind_account", "bind_code", "is_verified", "auth_token", "bind_date"]), + "binds": (binds, ["id", "user_id", "bind_account", "bind_code", "is_verified", "bind_date"]), "webs": (webs, ["id", "user_id", "permission", "web_token", "last_save_export", "created_at", "updated_at"]), "logs": (logs, ["id", "user_id", "filename", "filesize", "timestamp"]), } @@ -346,6 +348,40 @@ async def web_admin_data_save(request: Request): return JSONResponse({"status": "success", "message": "Data saved successfully."}) +async def web_admin_update_maintenance(request: Request): + adm = await is_admin(request.cookies.get("token")) + if not adm: + return JSONResponse({"status": "failed", "message": "Invalid token."}, status_code=400) + + params = await request.json() + status = params.get("status") + message_en = params.get("message_en") + message_ja = params.get("message_ja") + message_fr = params.get("message_fr") + message_it = params.get("message_it") + + # Create the XML structure directly + notice_xml = f""" + + {status} + + {message_en} + {message_ja} + {message_fr} + {message_it} + + + """ + + # Save the XML to the file + notice_file_path = os.path.join('files/notice.xml') + try: + with open(notice_file_path, 'w', encoding='utf-8') as f: + f.write(notice_xml) + return JSONResponse({"status": "success", "message": "Maintenance settings updated successfully."}) + except Exception as e: + return JSONResponse({"status": "failed", "message": f"An error occurred: {str(e)}"}, status_code=500) + routes = [ Route("/admin", web_admin_page, methods=["GET"]), Route("/admin/", web_admin_page, methods=["GET"]), @@ -355,4 +391,5 @@ routes = [ Route("/admin/table/insert", web_admin_table_insert, methods=["POST"]), Route("/admin/data", web_admin_data_get, methods=["GET"]), Route("/admin/data/save", web_admin_data_save, methods=["POST"]), + Route("/admin/update_maintenance", web_admin_update_maintenance, methods=["POST"]) ] \ No newline at end of file diff --git a/new_server_7003/api/database.py b/new_server_7003/api/database.py index 97accc5..765ab44 100644 --- a/new_server_7003/api/database.py +++ b/new_server_7003/api/database.py @@ -64,7 +64,8 @@ devices = Table( Column("avatar", Integer, default=1), Column("created_at", DateTime, default=datetime.utcnow), Column("updated_at", DateTime, default=datetime.utcnow, onupdate=datetime.utcnow), - Column("last_login_at", DateTime, default=None) + Column("last_login_at", DateTime, default=None), + Column("bind_token", String(64), unique=True, nullable=True) ) results = Table( @@ -141,7 +142,6 @@ binds = Table( Column("bind_account", String(128), unique=True, nullable=False), Column("bind_code", String(6), nullable=False), Column("is_verified", Integer, default=0), - Column("auth_token", String(64), unique=True), Column("bind_date", DateTime, default=datetime.utcnow) ) @@ -191,13 +191,13 @@ async def ensure_user_columns(): import aiosqlite async with aiosqlite.connect(DB_PATH) as db: - async with db.execute("PRAGMA table_info(user);") as cursor: + async with db.execute("PRAGMA table_info(devices);") as cursor: columns = [row[1] async for row in cursor] alter_needed = False - #if "save_id" not in columns: - # await db.execute("ALTER TABLE user ADD COLUMN save_id TEXT;") - # alter_needed = True + if "bind_token" not in columns: + await db.execute("ALTER TABLE devices ADD COLUMN bind_token TEXT;") + alter_needed = True #if "coin_mp" not in columns: # await db.execute("ALTER TABLE user ADD COLUMN coin_mp INTEGER DEFAULT 1;") # alter_needed = True @@ -212,14 +212,16 @@ async def get_bind(user_id): result = await player_database.fetch_one(query) return dict(result) if result else None -async def refresh_bind(user_id): +async def refresh_bind(user_id, device_id): existing_bind = await get_bind(user_id) if existing_bind and existing_bind['is_verified'] == 1: new_auth_token = base64.urlsafe_b64encode(os.urandom(64)).decode("utf-8") - update_query = update(binds).where(binds.c.id == existing_bind['id']).values( - auth_token=new_auth_token + update_query = update(devices).where(devices.c.device_id == device_id).values( + bind_token=new_auth_token ) await player_database.execute(update_query) + return new_auth_token + return "" async def log_download(user_id, filename, filesize): query = logs.insert().values( @@ -255,7 +257,7 @@ async def verify_user_code(code, user_id): update_query = update(binds).where(binds.c.id == result['id']).values( is_verified=1, - auth_token=base64.urlsafe_b64encode(os.urandom(64)).decode("utf-8") + bind_date=datetime.utcnow() ) await player_database.execute(update_query) return "Verified and account successfully bound." @@ -276,6 +278,12 @@ async def decrypt_fields_to_user_info(decrypted_fields): return None, None +async def get_device_info(device_id): + query = devices.select().where(devices.c.device_id == device_id) + device_record = await player_database.fetch_one(query) + device_record = dict(device_record) if device_record else None + return device_record + async def user_id_to_user_info(user_id): user_query = accounts.select().where(accounts.c.id == user_id) user_record = await player_database.fetch_one(user_query) @@ -317,7 +325,7 @@ async def check_blacklist(decrypted_fields): return result is None async def get_user_entitlement_from_devices(user_id): - devices_query = devices.select().where(devices.c.user_id == user_id) + devices_query = select(devices.c.my_stage, devices.c.my_avatar).where(devices.c.user_id == user_id) devices_list = await player_database.fetch_all(devices_query) devices_list = [dict(dev) for dev in devices_list] if devices_list else [] diff --git a/new_server_7003/api/file.py b/new_server_7003/api/file.py index 62fe89e..41b1874 100644 --- a/new_server_7003/api/file.py +++ b/new_server_7003/api/file.py @@ -34,14 +34,19 @@ async def serve_file(request: Request): pass else: - existing_bind = select(binds).where((binds.c.auth_token == auth_token) & (binds.c.is_verified == 1)) - result = await player_database.fetch_one(existing_bind) - if not result: - return Response("Unauthorized", status_code=403) - else: - daily_bytes = await get_downloaded_bytes(result['user_id'], 24) - if daily_bytes >= DAILY_DOWNLOAD_LIMIT: - return Response("Daily download limit exceeded", status_code=403) + existing_device_query = select(devices).where((devices.c.bind_token == auth_token)) + existing_device = await player_database.fetch_one(existing_device_query) + if not existing_device: + return Response("Unauthorized - device not found", status_code=403) + + existing_bind_query = select(binds).where((binds.c.user_id == existing_device['user_id']) & (binds.c.is_verified == 1)) + bind_result = await player_database.fetch_one(existing_bind_query) + if not bind_result: + return Response("Unauthorized - bind not verified", status_code=403) + + daily_bytes = await get_downloaded_bytes(bind_result['user_id'], 24) + if daily_bytes >= DAILY_DOWNLOAD_LIMIT: + return Response("Daily download limit exceeded", status_code=403) safe_filename = os.path.realpath(os.path.join(os.getcwd(), "files", "gc2", folder, filename)) base_directory = os.path.realpath(os.path.join(os.getcwd(), "files", "gc2", folder)) @@ -55,7 +60,7 @@ async def serve_file(request: Request): # get size of file if AUTHORIZATION_MODE != 0: file_size = os.path.getsize(file_path) - await log_download(result['user_id'], filename, file_size) + await log_download(bind_result['user_id'], filename, file_size) return FileResponse(file_path) else: return Response("File not found", status_code=404) diff --git a/new_server_7003/api/misc.py b/new_server_7003/api/misc.py index 832bc0c..4cda206 100644 --- a/new_server_7003/api/misc.py +++ b/new_server_7003/api/misc.py @@ -7,8 +7,11 @@ import bcrypt import hashlib import re import xml.etree.ElementTree as ET +import copy +import os from config import MODEL, TUNEFILE, SKIN, AUTHORIZATION_NEEDED, AUTHORIZATION_MODE, GRANDFATHERED_ACCOUNT_LIMIT, BIND_SALT -from api.database import get_bind, check_whitelist, check_blacklist, decrypt_fields_to_user_info, user_id_to_user_info_simple +from api.database import get_bind, check_whitelist, check_blacklist, decrypt_fields_to_user_info, user_id_to_user_info_simple, get_device_info, refresh_bind +from api.template import START_XML FMAX_VER = None FMAX_RES = None @@ -75,18 +78,22 @@ async def get_model_pak(decrypted_fields, user_id): mid = ET.Element("model_pak") rid = ET.Element("date") uid = ET.Element("url") + device_id = decrypted_fields[b'vid'][0].decode() host = await get_host_string() if AUTHORIZATION_MODE == 0: - auth_token = decrypted_fields[b'vid'][0].decode() + auth_token = device_id rid.text = MODEL uid.text = host + "files/gc2/" + auth_token + "/pak/model" + MODEL + ".pak" else: if user_id: + device_info = await get_device_info(device_id) bind_info = await get_bind(user_id) if bind_info and bind_info['is_verified'] == 1: - auth_token = bind_info['auth_token'] + auth_token = device_info['bind_token'] + if not auth_token: + auth_token = await refresh_bind(user_id, device_id) rid.text = MODEL uid.text = host + "files/gc2/" + auth_token + "/pak/model" + MODEL + ".pak" else: @@ -104,18 +111,20 @@ async def get_tune_pak(decrypted_fields, user_id): mid = ET.Element("tuneFile_pak") rid = ET.Element("date") uid = ET.Element("url") + device_id = decrypted_fields[b'vid'][0].decode() host = await get_host_string() if AUTHORIZATION_MODE == 0: - auth_token = decrypted_fields[b'vid'][0].decode() + auth_token = device_id rid.text = TUNEFILE uid.text = host + "files/gc2/" + auth_token + "/pak/tuneFile" + TUNEFILE + ".pak" else: if user_id: + device_info = await get_device_info(device_id) bind_info = await get_bind(user_id) if bind_info and bind_info['is_verified'] == 1: - auth_token = bind_info['auth_token'] + auth_token = device_info['bind_token'] rid.text = TUNEFILE uid.text = host + "files/gc2/" + auth_token + "/pak/tuneFile" + TUNEFILE + ".pak" else: @@ -133,18 +142,20 @@ async def get_skin_pak(decrypted_fields, user_id): mid = ET.Element("skin_pak") rid = ET.Element("date") uid = ET.Element("url") + device_id = decrypted_fields[b'vid'][0].decode() host = await get_host_string() if AUTHORIZATION_MODE == 0: - auth_token = decrypted_fields[b'vid'][0].decode() + auth_token = device_id rid.text = SKIN uid.text = host + "files/gc2/" + auth_token + "/pak/skin" + SKIN + ".pak" else: if user_id: + device_info = await get_device_info(device_id) bind_info = await get_bind(user_id) if bind_info and bind_info['is_verified'] == 1: - auth_token = bind_info['auth_token'] + auth_token = device_info['bind_token'] rid.text = SKIN uid.text = host + "files/gc2/" + auth_token + "/pak/skin" + SKIN + ".pak" else: @@ -160,16 +171,18 @@ async def get_skin_pak(decrypted_fields, user_id): async def get_m4a_path(decrypted_fields, user_id): host = await get_host_string() + device_id = decrypted_fields[b'vid'][0].decode() if AUTHORIZATION_MODE == 0: - auth_token = decrypted_fields[b'vid'][0].decode() + auth_token = device_id mid = ET.Element("m4a_path") mid.text = host + "files/gc2/" + auth_token + "/audio/" else: if user_id: + device_info = await get_device_info(device_id) bind_info = await get_bind(user_id) if bind_info and bind_info['is_verified'] == 1: mid = ET.Element("m4a_path") - mid.text = host + "files/gc2/" + bind_info['auth_token'] + "/audio/" + mid.text = host + "files/gc2/" + device_info['bind_token'] + "/audio/" else: mid = ET.Element("m4a_path") mid.text = host @@ -181,16 +194,18 @@ async def get_m4a_path(decrypted_fields, user_id): async def get_stage_path(decrypted_data, user_id): host = await get_host_string() + device_id = decrypted_data[b'vid'][0].decode() if AUTHORIZATION_MODE == 0: - auth_token = decrypted_data[b'vid'][0].decode() + auth_token = device_id mid = ET.Element("stage_path") mid.text = host + "files/gc2/" + auth_token + "/stage/" else: if user_id: + device_info = await get_device_info(device_id) bind_info = await get_bind(user_id) if bind_info and bind_info['is_verified'] == 1: mid = ET.Element("stage_path") - mid.text = host + "files/gc2/" + bind_info['auth_token'] + "/stage/" + mid.text = host + "files/gc2/" + device_info['bind_token'] + "/stage/" else: mid = ET.Element("stage_path") mid.text = host @@ -293,3 +308,14 @@ async def get_host_string(): from config import OVERRIDE_HOST, HOST, PORT host_string = OVERRIDE_HOST if OVERRIDE_HOST is not None else ("http://" + HOST + ":" + str(PORT) + "/") return host_string + +async def get_start_xml(): + root = copy.deepcopy(START_XML.getroot()) + with open(os.path.join('files/notice.xml'), 'r', encoding='utf-8') as f: + response_xml = ET.parse(f) + response_root = response_xml.getroot() + + for child in response_root: + root.append(child) + + return root \ No newline at end of file diff --git a/new_server_7003/api/user.py b/new_server_7003/api/user.py index 8523c73..552f825 100644 --- a/new_server_7003/api/user.py +++ b/new_server_7003/api/user.py @@ -1,14 +1,13 @@ from starlette.responses import Response, FileResponse, HTMLResponse from starlette.requests import Request from starlette.routing import Route -import os from datetime import datetime import xml.etree.ElementTree as ET import copy from config import START_COIN -from api.misc import get_model_pak, get_tune_pak, get_skin_pak, get_m4a_path, get_stage_path, get_stage_zero, should_serve_init, inform_page +from api.misc import get_model_pak, get_tune_pak, get_skin_pak, get_m4a_path, get_stage_path, get_stage_zero, should_serve_init, inform_page, get_start_xml from api.database import decrypt_fields_to_user_info, refresh_bind, get_user_entitlement_from_devices, set_device_data_using_decrypted_fields, create_device from api.crypt import decrypt_fields from api.template import START_AVATARS, START_STAGES, START_XML, SYNC_XML @@ -62,7 +61,7 @@ async def start(request: Request): return Response("""10Invalid request data.Invalid request data.""", media_type="application/xml" ) - root = copy.deepcopy(START_XML.getroot()) + root = await get_start_xml() user_info, device_info = await decrypt_fields_to_user_info(decrypted_fields) username = user_info['username'] if user_info else None @@ -74,7 +73,7 @@ async def start(request: Request): return Response("""403Access denied.""", media_type="application/xml") if user_id: - await refresh_bind(user_id) + _ = await refresh_bind(user_id, device_id) root.append(await get_model_pak(decrypted_fields, user_id)) root.append(await get_tune_pak(decrypted_fields, user_id)) @@ -265,7 +264,7 @@ async def bonus(request: Request): device_id = decrypted_fields[b'vid'][0].decode() user_info, device_info = await decrypt_fields_to_user_info(decrypted_fields) - root = copy.deepcopy(START_XML.getroot()) + root = await get_start_xml() daily_reward_elem = root.find(".//login_bonus") last_count_elem = daily_reward_elem.find("last_count") diff --git a/new_server_7003/files/notice.xml b/new_server_7003/files/notice.xml new file mode 100644 index 0000000..8a4123a --- /dev/null +++ b/new_server_7003/files/notice.xml @@ -0,0 +1,11 @@ + + + 1 + + testtest + Welcome to the private server! + Welcome to the private server! + Welcome to the private server! + + + \ No newline at end of file diff --git a/new_server_7003/files/start.xml b/new_server_7003/files/start.xml index 18599de..ff6c51f 100644 --- a/new_server_7003/files/start.xml +++ b/new_server_7003/files/start.xml @@ -1,5 +1,4 @@ -0 us 0 10 diff --git a/new_server_7003/web/admin.html b/new_server_7003/web/admin.html index ee732e2..8e0b3d1 100644 --- a/new_server_7003/web/admin.html +++ b/new_server_7003/web/admin.html @@ -82,6 +82,7 @@ +
+ +