import uvicorn import json import os from datetime import datetime from fastapi import FastAPI, Body from fastapi.middleware.cors import CORSMiddleware from loguru import logger from typing import Annotated # 创建logs目录(如果不存在) if not os.path.exists("logs"): os.makedirs("logs") # 配置日志 logger.add("logs/app_{time}.log", rotation="100 MB", retention="30 days") logger.add("logs/error_{time}.log", level="ERROR", rotation="100 MB", retention="90 days") # API Imports from HelperLogInOut import apiLogin, apiLogout, generateTimestamp from GetUserData import apiGetUserData from HelperUserAll import generateFullUserAll from HelperGetUserMusicDetail import getUserFullMusicDetail from Best50_To_Diving_Fish import implUserMusicToDivingFish from GetPreview import apiGetUserPreview from ActionLoginBonus import implLoginBonus from ActionUnlockItem import implUnlockMultiItem from ActionScoreRecord import implUploadMusicRecord, implDeleteMusicRecord from ChargeTicket import implBuyTicket, implWipeTickets from GetAny import apiGetAny from API_AuthLiteDelivery import getRawDelivery, parseRawDelivery, getUpdateIniFromURL, parseUpdateIni from API_AimeDB import implGetUID from APILogger import api_logger import uvicorn import json import os from datetime import datetime from fastapi import FastAPI, Body from fastapi.middleware.cors import CORSMiddleware from loguru import logger from typing import Annotated # 创建logs目录(如果不存在) if not os.path.exists("logs"): os.makedirs("logs") # 配置日志 logger.add("logs/app_{time}.log", rotation="100 MB", retention="30 days") logger.add("logs/error_{time}.log", level="ERROR", rotation="100 MB", retention="90 days") # API Imports from HelperLogInOut import apiLogin, apiLogout, generateTimestamp from GetUserData import apiGetUserData from HelperUserAll import generateFullUserAll from HelperGetUserMusicDetail import getUserFullMusicDetail from Best50_To_Diving_Fish import implUserMusicToDivingFish from GetPreview import apiGetUserPreview from ActionLoginBonus import implLoginBonus from ActionUnlockItem import implUnlockMultiItem from ActionScoreRecord import implUploadMusicRecord, implDeleteMusicRecord from ChargeTicket import implBuyTicket, implWipeTickets from GetAny import apiGetAny from API_AuthLiteDelivery import getRawDelivery, parseRawDelivery, getUpdateIniFromURL, parseUpdateIni from API_AimeDB import implGetUID from APILogger import api_logger from MyConfig import testUid8 app = FastAPI() # 日志中间件 @app.middleware("http") async def log_requests(request, call_next): # 记录请求开始时间 start_time = datetime.now() # 记录请求信息 logger.info(f"Request: {request.method} {request.url} - Headers: {dict(request.headers)}") # 处理请求 response = await call_next(request) # 记录响应信息和处理时间 process_time = (datetime.now() - start_time).total_seconds() logger.info(f"Response: {response.status_code} - Process Time: {process_time}s") return response # Set up CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.get("/api") async def root(): return {"message": "Welcome to the maimaiDX API"} @app.get("/api/music") async def get_music(): try: with open("Data/musicDB.json", "r", encoding="utf-8") as f: music_data_obj = json.load(f) # Convert the object to a list of objects music_list = [ { "id": int(music_id), "name": details.get("name"), "version": details.get("version") } for music_id, details in music_data_obj.items() ] return music_list except FileNotFoundError: return {"error": "Music database not found."} except Exception as e: return {"error": str(e)} @app.get("/api/user/all") async def get_user_all(userId: int = None): if userId is None: userId = testUid8 timestamp = None try: # 1. Login timestamp = generateTimestamp() login_result = apiLogin(timestamp, userId) if login_result.get("returnCode") != 1: return {"error": "Login failed", "details": login_result} # 2. Get basic user data user_data_str = apiGetUserData(userId, noLog=True) user_data = json.loads(user_data_str) if "userData" not in user_data: return {"error": "GetUserData failed", "details": user_data} current_user_data2 = user_data["userData"] # 3. Generate the full UserAll object full_user_all = generateFullUserAll( userId, login_result, timestamp, current_user_data2, {} ) # 4. Logout apiLogout(timestamp, userId) return full_user_all except Exception as e: logger.error(f"An error occurred in get_user_all: {e}") if timestamp and userId: try: apiLogout(timestamp, userId) except Exception as logout_e: logger.error(f"Error during cleanup logout: {logout_e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.get("/api/user/music_results") async def get_user_music_results(userId: int): if not userId: return {"error": "userId is required"} try: # This might be slow as it fetches all pages full_music_details = getUserFullMusicDetail(userId) return {"userMusicResults": full_music_details} except Exception as e: logger.error(f"An error occurred in get_user_music_results: {e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/user/upload_to_diving_fish") async def upload_to_diving_fish( data: dict ): userId = data.get("userId") fishImportToken = data.get("fishImportToken") if not userId or not fishImportToken: return {"error": "userId and fishImportToken are required"} try: result_code = implUserMusicToDivingFish(userId, fishImportToken) error_map = { 0: "Success", 1: "Failed to get user music from game server.", 2: "Diving Fish authentication failed. Check your token.", 3: "Communication error with Diving Fish server." } return { "resultCode": result_code, "message": error_map.get(result_code, "Unknown error") } except Exception as e: logger.error(f"An error occurred in upload_to_diving_fish: {e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.get("/api/user/preview") async def get_user_preview(userId: int): if not userId: return {"error": "userId is required"} try: preview_data_str = apiGetUserPreview(userId, noLog=True) return json.loads(preview_data_str) except Exception as e: logger.error(f"An error occurred in get_user_preview: {e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/action/claim_login_bonus") async def claim_login_bonus(data: dict): userId = data.get("userId") if not userId: return {"error": "userId is required"} timestamp = None try: # 1. Login timestamp = generateTimestamp() login_result = apiLogin(timestamp, userId) if login_result.get("returnCode") != 1: return {"error": "Login failed", "details": login_result} # 2. Claim bonus # Using mode 2 to claim all available bonuses bonus_result = implLoginBonus(userId, timestamp, login_result, bonusGenerateMode=2) # 3. Logout apiLogout(timestamp, userId) if bonus_result: return {"message": "Login bonus action completed successfully.", "details": bonus_result} else: return {"message": "No applicable login bonus found or action failed."} except Exception as e: logger.error(f"An error occurred in claim_login_bonus: {e}") if timestamp and userId: try: apiLogout(timestamp, userId) except Exception as logout_e: logger.error(f"Error during cleanup logout: {logout_e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/action/unlock_item") async def unlock_item(data: dict): userId = data.get("userId") itemKind = data.get("itemKind") itemIds_str = data.get("itemIds") # Expecting a comma-separated string of integers if not all([userId, itemKind is not None, itemIds_str]): return {"error": "userId, itemKind, and a string of itemIds are required"} try: itemIds = [int(i.strip()) for i in itemIds_str.split(",")] except ValueError: return {"error": "itemIds must be a comma-separated string of numbers"} timestamp = None try: # 1. Login timestamp = generateTimestamp() login_result = apiLogin(timestamp, userId) if login_result.get("returnCode") != 1: return {"error": "Login failed", "details": login_result} # 2. Unlock items unlock_result = implUnlockMultiItem( itemKind, userId, timestamp, login_result, *itemIds ) # 3. Logout apiLogout(timestamp, userId) return {"message": "Unlock action completed.", "details": unlock_result} except Exception as e: logger.error(f"An error occurred in unlock_item: {e}") if timestamp and userId: try: apiLogout(timestamp, userId) except Exception as logout_e: logger.error(f"Error during cleanup logout: {logout_e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/action/upload_score") async def upload_score(data: dict): userId = data.get("userId") musicId = data.get("musicId") levelId = data.get("levelId") achievement = data.get("achievement") dxScore = data.get("dxScore") if not all([userId, musicId, levelId is not None, achievement is not None, dxScore is not None]): return {"error": "userId, musicId, levelId, achievement, and dxScore are required"} timestamp = None try: # 1. Login timestamp = generateTimestamp() login_result = apiLogin(timestamp, userId) if login_result.get("returnCode") != 1: return {"error": "Login failed", "details": login_result} # 2. Upload score upload_result = implUploadMusicRecord( userId, timestamp, login_result, musicId, levelId, achievement, dxScore ) # 3. Logout apiLogout(timestamp, userId) return {"message": "Score upload action completed.", "details": upload_result} except Exception as e: logger.error(f"An error occurred in upload_score: {e}") if timestamp and userId: try: apiLogout(timestamp, userId) except Exception as logout_e: logger.error(f"Error during cleanup logout: {logout_e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/action/delete_score") async def delete_score(data: dict): userId = data.get("userId") musicId = data.get("musicId") levelId = data.get("levelId") if not all([userId, musicId, levelId is not None]): return {"error": "userId, musicId, and levelId are required"} timestamp = None try: # 1. Login timestamp = generateTimestamp() login_result = apiLogin(timestamp, userId) if login_result.get("returnCode") != 1: return {"error": "Login failed", "details": login_result} # 2. Delete score delete_result = implDeleteMusicRecord( userId, timestamp, login_result, musicId, levelId ) # 3. Logout apiLogout(timestamp, userId) return {"message": "Score delete action completed.", "details": delete_result} except Exception as e: logger.error(f"An error occurred in delete_score: {e}") if timestamp and userId: try: apiLogout(timestamp, userId) except Exception as logout_e: logger.error(f"Error during cleanup logout: {logout_e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/action/buy_ticket") async def buy_ticket(data: dict): userId = data.get("userId") ticketType = data.get("ticketType") if not all([userId, ticketType is not None]): return {"error": "userId and ticketType are required"} try: ticketType = int(ticketType) except ValueError: return {"error": "ticketType must be a number"} timestamp = None try: # 1. Login timestamp = generateTimestamp() login_result = apiLogin(timestamp, userId) if login_result.get("returnCode") != 1: return {"error": "Login failed", "details": login_result} # 2. Buy ticket buy_result = implBuyTicket(userId, ticketType) # 3. Logout apiLogout(timestamp, userId) return {"message": "Buy ticket action completed.", "details": json.loads(buy_result)} except Exception as e: logger.error(f"An error occurred in buy_ticket: {e}") if timestamp and userId: try: apiLogout(timestamp, userId) except Exception as logout_e: logger.error(f"Error during cleanup logout: {logout_e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/action/force_logout") async def force_logout(data: dict): userId = data.get("userId") if not userId: return {"error": "userId is required"} try: # Attempt to logout with the current timestamp timestamp = generateTimestamp() logout_result = apiLogout(timestamp, userId, noLog=True) return {"message": "Force logout attempt sent.", "details": logout_result} except Exception as e: logger.error(f"An error occurred in force_logout: {e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/action/wipe_tickets") async def wipe_tickets(data: dict): userId = data.get("userId") if not userId: return {"error": "userId is required"} timestamp = None try: # 1. Login timestamp = generateTimestamp() login_result = apiLogin(timestamp, userId) if login_result.get("returnCode") != 1: return {"error": "Login failed", "details": login_result} # 2. Wipe tickets wipe_result = implWipeTickets(userId, timestamp, login_result) # 3. Logout apiLogout(timestamp, userId) return {"message": "Wipe tickets action completed.", "details": wipe_result} except Exception as e: logger.error(f"An error occurred in wipe_tickets: {e}") if timestamp and userId: try: apiLogout(timestamp, userId) except Exception as logout_e: logger.error(f"Error during cleanup logout: {logout_e}") return {"error": "An unexpected error occurred.", "details": str(e)} if __name__ == "__main__": uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) @app.get("/api/get_auth_lite_delivery") async def get_auth_lite_delivery(): try: raw_delivery_str = getRawDelivery() update_links = parseRawDelivery(raw_delivery_str) return {"updateLinks": update_links} except Exception as e: logger.error(f"An error occurred in get_auth_lite_delivery: {e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.get("/api/get_any") async def get_any(userId: int, apiName: str): if not all([userId, apiName]): return {"error": "userId and apiName are required"} try: result_str = apiGetAny(userId, apiName, noLog=True) return json.loads(result_str) except Exception as e: logger.error(f"An error occurred in get_any for {apiName}: {e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/parse_update_ini") async def parse_update_ini(data: dict): url = data.get("url") if not url: return {"error": "URL is required"} try: ini_text = getUpdateIniFromURL(url) parsed_data = parseUpdateIni(ini_text) return {"parsedData": parsed_data} except Exception as e: logger.error(f"An error occurred parsing INI from {url}: {e}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.post("/api/aime_scan") async def aime_scan(data: dict): qr_content = data.get("qrContent") if not qr_content: api_logger.log_warning("Aime scan failed: qrContent is required") return {"error": "qrContent is required"} try: api_logger.log_info(f"Aime scan request received for QR content: {qr_content[:20]}...") result = implGetUID(qr_content) api_logger.log_info(f"Aime scan completed successfully for QR content: {qr_content[:20]}...") if "errorID" in result: api_logger.log_warning(f"Aime scan returned error: {result['errorID']}") elif "userID" in result: api_logger.log_info(f"Aime scan returned user ID: {result['userID']}") return result except Exception as e: api_logger.log_error(f"An error occurred in aime_scan: {str(e)}") return {"error": "An unexpected error occurred.", "details": str(e)} @app.get("/api/logs") async def get_logs(): """获取日志文件内容""" try: import os import glob from datetime import datetime # 获取最新的日志文件 log_files = glob.glob("logs/app_*.log") if not log_files: return {"logs": []} # 按修改时间排序,获取最新的日志文件 latest_log_file = max(log_files, key=os.path.getmtime) # 读取日志文件内容 with open(latest_log_file, "r", encoding="utf-8") as f: lines = f.readlines() # 解析日志行 logs = [] for line in lines[-100:]: # 只返回最后100行 # 尝试解析loguru格式的日志 try: # 格式: 2023-05-15 10:30:45.123 | INFO | message parts = line.strip().split(" | ") if len(parts) >= 3: timestamp_str = parts[0] level = parts[1].strip() message = " | ".join(parts[2:]) # 解析时间戳 timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S.%f") logs.append({ "timestamp": timestamp.isoformat(), "level": level, "message": message }) except Exception: # 如果解析失败,将整行作为消息 logs.append({ "timestamp": datetime.now().isoformat(), "level": "INFO", "message": line.strip() }) return {"logs": logs} except Exception as e: api_logger.log_error(f"Failed to read logs: {str(e)}") return {"error": "Failed to read logs", "details": str(e)}