Initial commit: Add maimaiDX API web application with AimeDB scanning and logging features

This commit is contained in:
kejiz
2025-09-18 10:19:08 +08:00
commit 4e83f159f0
84 changed files with 14012 additions and 0 deletions

554
backend/main.py Normal file
View File

@@ -0,0 +1,554 @@
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)}