diff --git a/server/5002.py b/server/5002.py new file mode 100644 index 0000000..1110836 --- /dev/null +++ b/server/5002.py @@ -0,0 +1,410 @@ +from starlette.applications import Starlette +from starlette.responses import FileResponse, Response, JSONResponse +from starlette.routing import Route +from starlette.requests import Request +import os +import databases +import sqlalchemy +import json +import secrets +from sqlalchemy import Table, Column, Integer, String, JSON, Boolean, Float, ForeignKey, update, insert +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.future import select +import time + +from config import HOST, PORT, DEBUG, SSL_CERT, SSL_KEY, REGISTRATION + +#------------- DB ------------- + +DB_NAME = "player.db" +DB_PATH = os.path.join(os.getcwd(), DB_NAME) +DATABASE_URL = f"sqlite+aiosqlite:///{DB_PATH}" + +database = databases.Database(DATABASE_URL) +metadata = sqlalchemy.MetaData() + +player = Table( + "player", + metadata, + Column("id", String(64), primary_key=True), + Column("name", String(64), unique=True, nullable=False), + Column("banned", Boolean, nullable=False), + Column("rating", Float, nullable=False), + Column("data", JSON, nullable=False), +) + +rank = Table( + "rank", + metadata, + Column("id", Integer, primary_key=True, autoincrement=True), + Column("player_id", String(64), ForeignKey("player.id"), nullable=False), + Column("key", String(64), nullable=False), + Column("score", Integer, nullable=False), + Column("isFC", Boolean, nullable=False), +) + +redeem = Table( + "redeem", + metadata, + Column("id", Integer, primary_key=True, autoincrement=True), + Column("code", String(64), nullable=False), + Column("item", JSON, nullable=False), + Column("expire", Integer, nullable=False), + Column("limit", Integer, nullable=False), + Column("roster", JSON, nullable=False), +) + +async def init_db(): + + if not os.path.exists(DB_PATH): + print("[DB] Creating new database:", DB_PATH) + + engine = create_async_engine(DATABASE_URL, echo=False) + + async with engine.begin() as conn: + await conn.run_sync(metadata.create_all) + + await engine.dispose() + print("[DB] Database initialized successfully.") + +async def get_player_data(user_id): + query = player.select().where(player.c.id == user_id) + player_info = await database.fetch_one(query=query) + if player_info: + return dict(player_info) + else: + return None + +async def set_player_data(user_id, data_field, new_data): + if data_field not in player.c: + raise ValueError(f"Invalid column name: {data_field}") + + query = player.update().where(player.c.id == user_id).values({data_field: new_data}) + await database.execute(query) + +# ------------ CDN ------------- + +root_folder = os.path.dirname(os.path.abspath(__file__)) +allowed_folders = ["OverRapid", "Resources"] + +async def serve_file(request): + path = request.path_params['path'] + first_level_folder = path.split('/')[0] + if first_level_folder in allowed_folders: + file_path = os.path.realpath(os.path.join(os.getcwd(), path)) + if os.path.isfile(file_path): + return FileResponse(file_path) + + return Response("File not found", status_code=404) + +#------------ API ------------- + +async def api_function(request: Request): + data = await request.json() + content = data.get("Content", "") + name = data.get("Name", "") + user_id = data.get("UserId", "") + player_info = await get_player_data(user_id) + content_dict = json.loads(content) + + if player_info is None: + print("WARNING: User not found in database.") + + if name == "dataDownload": + user_id = content_dict.get("key", "") + if player_info: + return JSONResponse(player_info['data']) + else: + return JSONResponse({}) + + elif name == "UIDCheck": + result = await get_player_data(content_dict.get("key", "")) + return JSONResponse({"exists": result is not None}) + + elif name == "dataUpload": + content_json = content_dict["jsonString"] + if player_info: + await set_player_data(user_id, "data", content_json) + elif REGISTRATION: + query = insert(player).values(id=user_id, name=content_json["name"], banned=False, rating=0.0, data=content_json) + await database.execute(query) + + return JSONResponse({"dummy": True}) + + elif name == "nameCheck": + player_name = content_dict.get("name", "") + query = player.select(player.c.id).where(player.c.name == player_name) + result = await database.fetch_one(query) + if not REGISTRATION: + return JSONResponse({"regClosed": True}) + + return JSONResponse({"exists": result is not None}) + + elif name == "getName": + if player_info: + if player_info['name']: + if isinstance(player_info['banned'], str) or player_info['banned'] is None: + return JSONResponse({"banned": player_info['banned']}) + else: + return JSONResponse({"name": player_info['name']}) + + return JSONResponse({}) + + elif name == "ratingUpload": + try: + score = float(content_dict.get("score", "")) + except ValueError: + print("parse failed") + return JSONResponse({"dummy": False}, status_code=400) + print(player_info['rating']) + if player_info['rating'] is not None: + await set_player_data(user_id, "rating", score) + return JSONResponse({"dummy": True}) + else: + print("else failed") + return JSONResponse({"dummy": False}, status_code=400) + + elif name == "rename": + new_name = content_dict.get("name", "") + + if player_info['banned'] > 0 or player_info['banned'] is None: + return JSONResponse({"banned": True}) + else: + await set_player_data(user_id, "name", new_name) + return JSONResponse({"banned": False}) + + elif name == "ratingPosition": + if player_info: + query = select(sqlalchemy.func.count()).where(player.c.rating > player_info['rating']) + higher_count = (await database.fetch_one(query))[0] + + query = select(sqlalchemy.func.count()) + total_count = (await database.fetch_one(query))[0] + + response_data = { + "userExists": True, + "total": total_count, + "count": higher_count + } + else: + response_data = {"userExists": False} + + return JSONResponse(response_data) + + elif name == "rankUpload": + key = content_dict.get("key", "") + score = content_dict.get("score", 0) + isFC = content_dict.get("isFC", False) + query = select(rank.c.score).where(rank.c.player_id == user_id, rank.c.key == key) + result = await database.fetch_one(query) + + if result is not None: + if result[0] < score: + query = update(rank).where(rank.c.player_id == user_id, rank.c.key == key).values(score=score, isFC=isFC) + await database.execute(query) + else: + query = insert(rank).values(player_id=user_id, key=key, score=score, isFC=isFC) + await database.execute(query) + + return JSONResponse({"dummy": True}) + + elif name == "rankLeaderboard": + key_to_match = content_dict.get("key", "") + max_key = content_dict.get("maxKey", 99) + + query = select(rank.c.player_id, rank.c.score, rank.c.isFC).where(rank.c.key == key_to_match) + qualified_ranks = await database.fetch_all(query) + + qualified_ranks = sorted(qualified_ranks, key=lambda x: x["score"], reverse=True) + + top_scores = qualified_ranks[:max_key] + + result_list = [] + for rank_entry in top_scores: + player_id = rank_entry["player_id"] + score = rank_entry["score"] + isFC = rank_entry["isFC"] + + query = select(player.c.name).where(player.c.id == player_id) + player_name = (await database.fetch_one(query))["name"] + + result_list.append({"name": player_name, "score": score, "isFC": bool(isFC)}) + + return JSONResponse(result_list) + + elif name == "ratingLeaderboard": + query = select(player.c.name, player.c.rating).order_by(player.c.rating.desc()) + result = await database.fetch_all(query) + + leaderboard = [{"name": row["name"], "score": row["rating"]} for row in result] + return JSONResponse(leaderboard) + + elif name == "codeCheck": + code = content_dict.get("code", "") + + query = select(redeem).where(redeem.c.code == code) + redeem_info = await database.fetch_one(query) + + if redeem_info: + item = redeem_info["item"] + expire = redeem_info["expire"] + limit = redeem_info["limit"] + roster = redeem_info["roster"] + current_time = int(time.time()) + if current_time > expire or len(roster) >= limit or user_id in roster: + return JSONResponse({"success": False}) + + roster.append(user_id) + query = update(redeem).where(redeem.c.code == code).values(roster=roster) + await database.execute(query) + return JSONResponse({"success": True, "item": item}) + else: + return JSONResponse({"success": False}) + + elif name == "withdraw": + return JSONResponse({}) + +#------------ PVP ------------- + +queue = [] + +def find_lobby_by_id(lobby_id): + for lobby in queue: + if lobby.get("id") == lobby_id: + return lobby + return None + +async def pvp_function(request: Request): + data = await request.json() + type = data.get("type", "") + request_code = data.get("request", "") + lobby_id = data.get("id", "") + + if type == "match": + if request_code == "list": + filtered_lobbies = [lobby for lobby in queue if not lobby.get("selected", False)] + return JSONResponse(filtered_lobbies) + + elif request_code == "create": + player_id = data.get("pid", "") + session_id = secrets.token_urlsafe(24) + lobby_data = { + "data": { + "mp3": data.get("mp3", ""), + "diff": data.get("diff", "") + }, + "timestamp": int(time.time()), + "players": { + player_id: { + "icon": data.get("icon", ""), + "level": data.get("level", 1), + "is4Key": data.get("is4Key", False), + "diff": data.get("diff", ""), + "options": data.get("options", []), + "username": data.get("username", ""), + "rating": data.get("rating", 0.0), + "achivement": data.get("achivement", "") + } + }, + "waiting": True, + "id": session_id, + "match": {} + } + queue.append(lobby_data) + return JSONResponse({"id": session_id}) + + elif request_code == "purge": + for index, lobby in enumerate(queue): + if lobby.get("id") == lobby_id: + del queue[index] + return JSONResponse(queue) + return JSONResponse(queue) + + elif request_code == "players": + lobby = find_lobby_by_id(lobby_id) + if lobby: + return JSONResponse(lobby.get("players")) + return JSONResponse({}) + + elif request_code == "join": + lobby = find_lobby_by_id(lobby_id) + if lobby: + player_id = data.get("pid", "") + lobby["players"][player_id] = { + "icon": data.get("icon", ""), + "level": data.get("level", 1), + "is4Key": data.get("is4Key", False), + "diff": data.get("diff", ""), + "options": data.get("options", []), + "username": data.get("username", ""), + "rating": data.get("rating", 0.0), + "achivement": data.get("achivement", "") + } + lobby["waiting"] = False + lobby["timestamp"] = int(time.time()) + return JSONResponse(lobby) + else: + return JSONResponse({}) + + elif request_code == "select": + lobby = find_lobby_by_id(lobby_id) + if lobby: + is_already_selected = lobby.get("selected", False) + if not is_already_selected: + lobby["selected"] = True + return JSONResponse({"isAlreadySelected": False}) + else: + return JSONResponse({"isAlreadySelected": True}) + else: + return JSONResponse({"isAlreadySelected": True}) + + elif request_code == "unselect": + lobby = find_lobby_by_id(lobby_id) + if lobby: + lobby["selected"] = False + return JSONResponse({"message": ""}) + else: + return JSONResponse({"message": ""}) + + elif request_code == "match": + lobby = find_lobby_by_id(lobby_id) + if lobby: + player_id = data.get("pid", "") + match_value = data.get("match", "") + current_timestamp = int(time.time()) + if player_id in lobby["match"]: + lobby["match"][player_id]["timestamp"] = current_timestamp + lobby["match"][player_id]["match"] = match_value + else: + lobby["match"][player_id] = { + "timestamp": current_timestamp, + "match": match_value + } + return JSONResponse(lobby["match"]) + else: + return JSONResponse({}) + +routes = [ + Route("/Api", api_function, methods=["POST"]), + Route("/Pvp", pvp_function, methods=["POST"]) +] + +routes.append(Route("/{path:path}", serve_file)) + +app = Starlette(debug=DEBUG, routes=routes) + +@app.on_event("startup") +async def startup(): + await database.connect() + await init_db() + +@app.on_event("shutdown") +async def shutdown(): + await database.disconnect() + +if __name__ == "__main__": + import uvicorn + ssl_context = (SSL_CERT, SSL_KEY) if SSL_CERT and SSL_KEY else None + uvicorn.run(app, host=HOST, port=PORT, ssl_certfile=SSL_CERT, ssl_keyfile=SSL_KEY) + +# Made By Tony 2025.5.4 \ No newline at end of file diff --git a/server/config.py b/server/config.py new file mode 100644 index 0000000..2b404bb --- /dev/null +++ b/server/config.py @@ -0,0 +1,6 @@ +HOST = "127.0.0.1" +PORT = 9066 +REGISTRATION = True +DEBUG = True +SSL_CERT = None +SSL_KEY = None