Files
Groove_Coaster_2_Server/new_server_7003/api/misc.py
UnitedAirforce 10bbd03bb6 v3 push
2025-11-26 13:49:27 +08:00

322 lines
10 KiB
Python

from starlette.responses import HTMLResponse
import requests
import json
import binascii
import secrets
import bcrypt
import hashlib
import re
import aiofiles
import xml.etree.ElementTree as ET
from config import MODEL, TUNEFILE, SKIN, AUTHORIZATION_NEEDED, AUTHORIZATION_MODE, GRANDFATHERED_ACCOUNT_LIMIT
from api.database import get_bind, check_whitelist, check_blacklist, decrypt_fields_to_user_info, user_id_to_user_info_simple
FMAX_VER = None
FMAX_RES = None
def get_4max_version_string():
url = "https://studio.code.org/v3/sources/3-aKHy16Y5XaAPXQHI95RnFOKlyYT2O95ia2HN2jKIs/main.json"
global FMAX_VER
try:
with open("./files/4max_ver.txt", 'r') as file:
FMAX_VER = file.read().strip()
except Exception as e:
print(f"An unexpected error occurred when loading files/4max_ver.txt: {e}")
def fetch():
global FMAX_RES
try:
response = requests.get(url)
if 200 <= response.status_code <= 207:
try:
response_json = response.json()
FMAX_RES = json.loads(response_json['source'])
except (json.JSONDecodeError, KeyError):
FMAX_RES = 500
else:
FMAX_RES = response.status_code
except requests.RequestException:
FMAX_RES = 400
fetch()
def parse_res(res):
parsed_data = []
if isinstance(res, int) or res == None:
return "Failed to fetch version info: Error " + str(res)
for item in res:
if item.get("isOpen"):
version = item.get("version", 0)
changelog = "<br>".join(item.get("changeLog", {}).get("en", []))
parsed_data.append(f"<strong>Version: {version}</strong><p><strong>Changelog:</strong><br>{changelog}</p>")
return "".join(parsed_data)
def crc32_decimal(data):
crc32_hex = binascii.crc32(data.encode())
return int(crc32_hex & 0xFFFFFFFF)
def hash_password(password):
salt = bcrypt.gensalt()
hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)
return hashed_password.decode('utf-8')
def verify_password(password, hashed_password):
if type(hashed_password) == str:
hashed_password = hashed_password.encode('utf-8')
return bcrypt.checkpw(password.encode('utf-8'), hashed_password)
def is_alphanumeric(username):
pattern = r"^[a-zA-Z0-9]+$"
return bool(re.match(pattern, username))
async def get_model_pak(decrypted_fields, user_id):
mid = ET.Element("model_pak")
rid = ET.Element("date")
uid = ET.Element("url")
host = await get_host_string()
if AUTHORIZATION_MODE == 0:
auth_token = decrypted_fields[b'vid'][0].decode()
rid.text = MODEL
uid.text = host + "files/gc2/" + auth_token + "/pak/model" + MODEL + ".pak"
else:
if user_id:
bind_info = await get_bind(user_id)
if bind_info and bind_info['is_verified'] == 1:
auth_token = bind_info['auth_token']
rid.text = MODEL
uid.text = host + "files/gc2/" + auth_token + "/pak/model" + MODEL + ".pak"
else:
rid.text = "1"
uid.text = host + "files/gc/model1.pak"
else:
rid.text = "1"
uid.text = host + "files/gc/model1.pak"
mid.append(rid)
mid.append(uid)
return mid
async def get_tune_pak(decrypted_fields, user_id):
mid = ET.Element("tuneFile_pak")
rid = ET.Element("date")
uid = ET.Element("url")
host = await get_host_string()
if AUTHORIZATION_MODE == 0:
auth_token = decrypted_fields[b'vid'][0].decode()
rid.text = TUNEFILE
uid.text = host + "files/gc2/" + auth_token + "/pak/tuneFile" + TUNEFILE + ".pak"
else:
if user_id:
bind_info = await get_bind(user_id)
if bind_info and bind_info['is_verified'] == 1:
auth_token = bind_info['auth_token']
rid.text = TUNEFILE
uid.text = host + "files/gc2/" + auth_token + "/pak/tuneFile" + TUNEFILE + ".pak"
else:
rid.text = "1"
uid.text = host + "files/gc/tuneFile1.pak"
else:
rid.text = "1"
uid.text = host + "files/gc/tuneFile1.pak"
mid.append(rid)
mid.append(uid)
return mid
async def get_skin_pak(decrypted_fields, user_id):
mid = ET.Element("skin_pak")
rid = ET.Element("date")
uid = ET.Element("url")
host = await get_host_string()
if AUTHORIZATION_MODE == 0:
auth_token = decrypted_fields[b'vid'][0].decode()
rid.text = SKIN
uid.text = host + "files/gc2/" + auth_token + "/pak/skin" + SKIN + ".pak"
else:
if user_id:
bind_info = await get_bind(user_id)
if bind_info and bind_info['is_verified'] == 1:
auth_token = bind_info['auth_token']
rid.text = SKIN
uid.text = host + "files/gc2/" + auth_token + "/pak/skin" + SKIN + ".pak"
else:
rid.text = "1"
uid.text = host + "files/gc/skin1.pak"
else:
rid.text = "1"
uid.text = host + "files/gc/skin1.pak"
mid.append(rid)
mid.append(uid)
return mid
async def get_m4a_path(decrypted_fields, user_id):
host = await get_host_string()
if AUTHORIZATION_MODE == 0:
auth_token = decrypted_fields[b'vid'][0].decode()
mid = ET.Element("m4a_path")
mid.text = host + "files/gc2/" + auth_token + "/audio/"
else:
if user_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/"
else:
mid = ET.Element("m4a_path")
mid.text = host
else:
mid = ET.Element("m4a_path")
mid.text = host
return mid
async def get_stage_path(decrypted_data, user_id):
host = await get_host_string()
if AUTHORIZATION_MODE == 0:
auth_token = decrypted_data[b'vid'][0].decode()
mid = ET.Element("stage_path")
mid.text = host + "files/gc2/" + auth_token + "/stage/"
else:
if user_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/"
else:
mid = ET.Element("stage_path")
mid.text = host
else:
mid = ET.Element("stage_path")
mid.text = host
return mid
def get_stage_zero():
sid = ET.Element("my_stage")
did = ET.Element("stage_id")
cid = ET.Element("ac_mode")
did.text = "0"
cid.text = "0"
sid.append(did)
sid.append(cid)
return sid
def inform_page(text, mode):
if mode == 0:
mode = "/files/web/ttl_taitoid.png"
elif mode == 1:
mode = "/files/web/ttl_information.png"
elif mode == 2:
mode = "/files/web/ttl_buy.png"
elif mode == 3:
mode = "/files/web/ttl_title.png"
elif mode == 4:
mode = "/files/web/ttl_rank.png"
elif mode == 5:
mode = "/files/web/ttl_mission.png"
elif mode == 6:
mode = "/files/web/ttl_shop.png"
with open("web/inform.html", "r") as file:
return HTMLResponse(file.read().format(text=text, img=mode))
def safe_int(val):
try:
return int(val)
except (TypeError, ValueError):
return None
def generate_otp():
otp = ''.join(secrets.choice('0123456789') for _ in range(6))
hashed_otp = hash_otp(otp)
return otp, hashed_otp
def hash_otp(otp):
return hashlib.sha256(otp.encode()).hexdigest()
def check_email(email):
STRICT_EMAIL_REGEX = r"^[A-Za-z0-9]+(?:[._-][A-Za-z0-9]+)*@[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*(?:\.[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)*\.[A-Za-z]{2,}$"
return re.match(STRICT_EMAIL_REGEX, email) is not None
async def read_user_save_file(user_id):
if user_id is None:
return ""
elif type(user_id) != int:
return ""
else:
try:
async with aiofiles.open(f"./save/{user_id}.dat", "rb") as file:
result = await file.read()
result = result.decode("utf-8")
return result
except FileNotFoundError:
return ""
async def write_user_save_file(user_id, data):
if user_id is None:
return
elif type(user_id) != int:
return
else:
try:
async with aiofiles.open(f"./save/{user_id}.dat", "wb") as file:
await file.write(data.encode("utf-8"))
except Exception as e:
print(f"An error occurred while writing the file: {e}")
async def should_serve(decrypted_fields):
should_serve = True
if AUTHORIZATION_NEEDED:
should_serve = await check_whitelist(decrypted_fields) and not await check_blacklist(decrypted_fields)
if AUTHORIZATION_MODE and should_serve:
user_info, device_info = await decrypt_fields_to_user_info(decrypted_fields, "id")
bind_info = await get_bind(user_info["id"])
if not bind_info or bind_info['is_verified'] != 1:
should_serve = False
return should_serve
async def should_serve_init(decrypted_fields):
should_serve = True
if AUTHORIZATION_NEEDED:
should_serve = await check_whitelist(decrypted_fields) and not await check_blacklist(decrypted_fields)
return should_serve
async def should_serve_web(user_id):
user_id = safe_int(user_id)
should_serve = True
if AUTHORIZATION_MODE:
bind_info = await get_bind(user_id)
if not bind_info or bind_info['is_verified'] != 1:
should_serve = False
if user_id < GRANDFATHERED_ACCOUNT_LIMIT:
should_serve = True
return should_serve
async def generate_salt(user_id):
SALT = "jHENR3wq$zX9@LpO"
user_info = await user_id_to_user_info_simple(user_id)
user_pw_hash = user_info['password_hash']
username = user_info['username']
combined = f"{username}{user_id}{user_pw_hash}{SALT}".encode('utf-8')
crc32_hash = binascii.crc32(combined) & 0xFFFFFFFF
return str(crc32_hash)
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