server notice hot update

This commit is contained in:
UnitedAirforce
2025-12-03 10:06:08 +08:00
parent cf409d78a1
commit 1b2086b6d0
9 changed files with 214 additions and 40 deletions

View File

@@ -189,7 +189,7 @@ async def register(request: Request):
if not decrypted_fields: if not decrypted_fields:
return inform_page("FAILED:<br>Invalid request data.", 0) return inform_page("FAILED:<br>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: if user_info:
return inform_page("FAILED:<br>Another user already has this name.", 0) return inform_page("FAILED:<br>Another user already has this name.", 0)

View File

@@ -4,6 +4,8 @@ from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse
import sqlalchemy import sqlalchemy
import json import json
from datetime import datetime 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.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 from api.misc import crc32_decimal
@@ -11,11 +13,11 @@ from api.misc import crc32_decimal
TABLE_MAP = { 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"]), "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"]), "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"]), "whitelist": (whitelists, ["id", "device_id"]),
"blacklist": (blacklists, ["id", "ban_terms", "reason"]), "blacklist": (blacklists, ["id", "ban_terms", "reason"]),
"batch_tokens": (batch_tokens, ["id", "batch_token", "expire_at", "uses_left", "auth_id", "created_at", "updated_at"]), "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"]), "webs": (webs, ["id", "user_id", "permission", "web_token", "last_save_export", "created_at", "updated_at"]),
"logs": (logs, ["id", "user_id", "filename", "filesize", "timestamp"]), "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."}) 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"""<?xml version="1.0" encoding="UTF-8"?>
<response>
<code>{status}</code>
<message>
<en>{message_en}</en>
<ja>{message_ja}</ja>
<fr>{message_fr}</fr>
<it>{message_it}</it>
</message>
</response>
"""
# 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 = [ routes = [
Route("/admin", web_admin_page, methods=["GET"]), Route("/admin", web_admin_page, methods=["GET"]),
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/table/insert", web_admin_table_insert, methods=["POST"]),
Route("/admin/data", web_admin_data_get, methods=["GET"]), Route("/admin/data", web_admin_data_get, methods=["GET"]),
Route("/admin/data/save", web_admin_data_save, methods=["POST"]), Route("/admin/data/save", web_admin_data_save, methods=["POST"]),
Route("/admin/update_maintenance", web_admin_update_maintenance, methods=["POST"])
] ]

View File

@@ -64,7 +64,8 @@ devices = Table(
Column("avatar", Integer, default=1), Column("avatar", Integer, default=1),
Column("created_at", DateTime, default=datetime.utcnow), Column("created_at", DateTime, default=datetime.utcnow),
Column("updated_at", DateTime, default=datetime.utcnow, onupdate=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( results = Table(
@@ -141,7 +142,6 @@ binds = Table(
Column("bind_account", String(128), unique=True, nullable=False), Column("bind_account", String(128), unique=True, nullable=False),
Column("bind_code", String(6), nullable=False), Column("bind_code", String(6), nullable=False),
Column("is_verified", Integer, default=0), Column("is_verified", Integer, default=0),
Column("auth_token", String(64), unique=True),
Column("bind_date", DateTime, default=datetime.utcnow) Column("bind_date", DateTime, default=datetime.utcnow)
) )
@@ -191,13 +191,13 @@ async def ensure_user_columns():
import aiosqlite import aiosqlite
async with aiosqlite.connect(DB_PATH) as db: 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] columns = [row[1] async for row in cursor]
alter_needed = False alter_needed = False
#if "save_id" not in columns: if "bind_token" not in columns:
# await db.execute("ALTER TABLE user ADD COLUMN save_id TEXT;") await db.execute("ALTER TABLE devices ADD COLUMN bind_token TEXT;")
# alter_needed = True alter_needed = True
#if "coin_mp" not in columns: #if "coin_mp" not in columns:
# await db.execute("ALTER TABLE user ADD COLUMN coin_mp INTEGER DEFAULT 1;") # await db.execute("ALTER TABLE user ADD COLUMN coin_mp INTEGER DEFAULT 1;")
# alter_needed = True # alter_needed = True
@@ -212,14 +212,16 @@ async def get_bind(user_id):
result = await player_database.fetch_one(query) result = await player_database.fetch_one(query)
return dict(result) if result else None 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) existing_bind = await get_bind(user_id)
if existing_bind and existing_bind['is_verified'] == 1: if existing_bind and existing_bind['is_verified'] == 1:
new_auth_token = base64.urlsafe_b64encode(os.urandom(64)).decode("utf-8") new_auth_token = base64.urlsafe_b64encode(os.urandom(64)).decode("utf-8")
update_query = update(binds).where(binds.c.id == existing_bind['id']).values( update_query = update(devices).where(devices.c.device_id == device_id).values(
auth_token=new_auth_token bind_token=new_auth_token
) )
await player_database.execute(update_query) await player_database.execute(update_query)
return new_auth_token
return ""
async def log_download(user_id, filename, filesize): async def log_download(user_id, filename, filesize):
query = logs.insert().values( 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( update_query = update(binds).where(binds.c.id == result['id']).values(
is_verified=1, is_verified=1,
auth_token=base64.urlsafe_b64encode(os.urandom(64)).decode("utf-8") bind_date=datetime.utcnow()
) )
await player_database.execute(update_query) await player_database.execute(update_query)
return "Verified and account successfully bound." return "Verified and account successfully bound."
@@ -276,6 +278,12 @@ async def decrypt_fields_to_user_info(decrypted_fields):
return None, None 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): async def user_id_to_user_info(user_id):
user_query = accounts.select().where(accounts.c.id == user_id) user_query = accounts.select().where(accounts.c.id == user_id)
user_record = await player_database.fetch_one(user_query) user_record = await player_database.fetch_one(user_query)
@@ -317,7 +325,7 @@ async def check_blacklist(decrypted_fields):
return result is None return result is None
async def get_user_entitlement_from_devices(user_id): 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 = await player_database.fetch_all(devices_query)
devices_list = [dict(dev) for dev in devices_list] if devices_list else [] devices_list = [dict(dev) for dev in devices_list] if devices_list else []

View File

@@ -34,14 +34,19 @@ async def serve_file(request: Request):
pass pass
else: else:
existing_bind = select(binds).where((binds.c.auth_token == auth_token) & (binds.c.is_verified == 1)) existing_device_query = select(devices).where((devices.c.bind_token == auth_token))
result = await player_database.fetch_one(existing_bind) existing_device = await player_database.fetch_one(existing_device_query)
if not result: if not existing_device:
return Response("Unauthorized", status_code=403) return Response("Unauthorized - device not found", status_code=403)
else:
daily_bytes = await get_downloaded_bytes(result['user_id'], 24) existing_bind_query = select(binds).where((binds.c.user_id == existing_device['user_id']) & (binds.c.is_verified == 1))
if daily_bytes >= DAILY_DOWNLOAD_LIMIT: bind_result = await player_database.fetch_one(existing_bind_query)
return Response("Daily download limit exceeded", status_code=403) 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)) 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)) 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 # get size of file
if AUTHORIZATION_MODE != 0: if AUTHORIZATION_MODE != 0:
file_size = os.path.getsize(file_path) 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) return FileResponse(file_path)
else: else:
return Response("File not found", status_code=404) return Response("File not found", status_code=404)

View File

@@ -7,8 +7,11 @@ import bcrypt
import hashlib import hashlib
import re import re
import xml.etree.ElementTree as ET 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 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_VER = None
FMAX_RES = None FMAX_RES = None
@@ -75,18 +78,22 @@ async def get_model_pak(decrypted_fields, user_id):
mid = ET.Element("model_pak") mid = ET.Element("model_pak")
rid = ET.Element("date") rid = ET.Element("date")
uid = ET.Element("url") uid = ET.Element("url")
device_id = decrypted_fields[b'vid'][0].decode()
host = await get_host_string() host = await get_host_string()
if AUTHORIZATION_MODE == 0: if AUTHORIZATION_MODE == 0:
auth_token = decrypted_fields[b'vid'][0].decode() auth_token = device_id
rid.text = MODEL rid.text = MODEL
uid.text = host + "files/gc2/" + auth_token + "/pak/model" + MODEL + ".pak" uid.text = host + "files/gc2/" + auth_token + "/pak/model" + MODEL + ".pak"
else: else:
if user_id: if user_id:
device_info = await get_device_info(device_id)
bind_info = await get_bind(user_id) bind_info = await get_bind(user_id)
if bind_info and bind_info['is_verified'] == 1: 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 rid.text = MODEL
uid.text = host + "files/gc2/" + auth_token + "/pak/model" + MODEL + ".pak" uid.text = host + "files/gc2/" + auth_token + "/pak/model" + MODEL + ".pak"
else: else:
@@ -104,18 +111,20 @@ async def get_tune_pak(decrypted_fields, user_id):
mid = ET.Element("tuneFile_pak") mid = ET.Element("tuneFile_pak")
rid = ET.Element("date") rid = ET.Element("date")
uid = ET.Element("url") uid = ET.Element("url")
device_id = decrypted_fields[b'vid'][0].decode()
host = await get_host_string() host = await get_host_string()
if AUTHORIZATION_MODE == 0: if AUTHORIZATION_MODE == 0:
auth_token = decrypted_fields[b'vid'][0].decode() auth_token = device_id
rid.text = TUNEFILE rid.text = TUNEFILE
uid.text = host + "files/gc2/" + auth_token + "/pak/tuneFile" + TUNEFILE + ".pak" uid.text = host + "files/gc2/" + auth_token + "/pak/tuneFile" + TUNEFILE + ".pak"
else: else:
if user_id: if user_id:
device_info = await get_device_info(device_id)
bind_info = await get_bind(user_id) bind_info = await get_bind(user_id)
if bind_info and bind_info['is_verified'] == 1: if bind_info and bind_info['is_verified'] == 1:
auth_token = bind_info['auth_token'] auth_token = device_info['bind_token']
rid.text = TUNEFILE rid.text = TUNEFILE
uid.text = host + "files/gc2/" + auth_token + "/pak/tuneFile" + TUNEFILE + ".pak" uid.text = host + "files/gc2/" + auth_token + "/pak/tuneFile" + TUNEFILE + ".pak"
else: else:
@@ -133,18 +142,20 @@ async def get_skin_pak(decrypted_fields, user_id):
mid = ET.Element("skin_pak") mid = ET.Element("skin_pak")
rid = ET.Element("date") rid = ET.Element("date")
uid = ET.Element("url") uid = ET.Element("url")
device_id = decrypted_fields[b'vid'][0].decode()
host = await get_host_string() host = await get_host_string()
if AUTHORIZATION_MODE == 0: if AUTHORIZATION_MODE == 0:
auth_token = decrypted_fields[b'vid'][0].decode() auth_token = device_id
rid.text = SKIN rid.text = SKIN
uid.text = host + "files/gc2/" + auth_token + "/pak/skin" + SKIN + ".pak" uid.text = host + "files/gc2/" + auth_token + "/pak/skin" + SKIN + ".pak"
else: else:
if user_id: if user_id:
device_info = await get_device_info(device_id)
bind_info = await get_bind(user_id) bind_info = await get_bind(user_id)
if bind_info and bind_info['is_verified'] == 1: if bind_info and bind_info['is_verified'] == 1:
auth_token = bind_info['auth_token'] auth_token = device_info['bind_token']
rid.text = SKIN rid.text = SKIN
uid.text = host + "files/gc2/" + auth_token + "/pak/skin" + SKIN + ".pak" uid.text = host + "files/gc2/" + auth_token + "/pak/skin" + SKIN + ".pak"
else: else:
@@ -160,16 +171,18 @@ async def get_skin_pak(decrypted_fields, user_id):
async def get_m4a_path(decrypted_fields, user_id): async def get_m4a_path(decrypted_fields, user_id):
host = await get_host_string() host = await get_host_string()
device_id = decrypted_fields[b'vid'][0].decode()
if AUTHORIZATION_MODE == 0: if AUTHORIZATION_MODE == 0:
auth_token = decrypted_fields[b'vid'][0].decode() auth_token = device_id
mid = ET.Element("m4a_path") mid = ET.Element("m4a_path")
mid.text = host + "files/gc2/" + auth_token + "/audio/" mid.text = host + "files/gc2/" + auth_token + "/audio/"
else: else:
if user_id: if user_id:
device_info = await get_device_info(device_id)
bind_info = await get_bind(user_id) bind_info = await get_bind(user_id)
if bind_info and bind_info['is_verified'] == 1: if bind_info and bind_info['is_verified'] == 1:
mid = ET.Element("m4a_path") 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: else:
mid = ET.Element("m4a_path") mid = ET.Element("m4a_path")
mid.text = host 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): async def get_stage_path(decrypted_data, user_id):
host = await get_host_string() host = await get_host_string()
device_id = decrypted_data[b'vid'][0].decode()
if AUTHORIZATION_MODE == 0: if AUTHORIZATION_MODE == 0:
auth_token = decrypted_data[b'vid'][0].decode() auth_token = device_id
mid = ET.Element("stage_path") mid = ET.Element("stage_path")
mid.text = host + "files/gc2/" + auth_token + "/stage/" mid.text = host + "files/gc2/" + auth_token + "/stage/"
else: else:
if user_id: if user_id:
device_info = await get_device_info(device_id)
bind_info = await get_bind(user_id) bind_info = await get_bind(user_id)
if bind_info and bind_info['is_verified'] == 1: if bind_info and bind_info['is_verified'] == 1:
mid = ET.Element("stage_path") 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: else:
mid = ET.Element("stage_path") mid = ET.Element("stage_path")
mid.text = host mid.text = host
@@ -293,3 +308,14 @@ async def get_host_string():
from config import OVERRIDE_HOST, HOST, PORT from config import OVERRIDE_HOST, HOST, PORT
host_string = OVERRIDE_HOST if OVERRIDE_HOST is not None else ("http://" + HOST + ":" + str(PORT) + "/") host_string = OVERRIDE_HOST if OVERRIDE_HOST is not None else ("http://" + HOST + ":" + str(PORT) + "/")
return host_string 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

View File

@@ -1,14 +1,13 @@
from starlette.responses import Response, FileResponse, HTMLResponse from starlette.responses import Response, FileResponse, HTMLResponse
from starlette.requests import Request from starlette.requests import Request
from starlette.routing import Route from starlette.routing import Route
import os
from datetime import datetime from datetime import datetime
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import copy import copy
from config import START_COIN 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.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.crypt import decrypt_fields
from api.template import START_AVATARS, START_STAGES, START_XML, SYNC_XML from api.template import START_AVATARS, START_STAGES, START_XML, SYNC_XML
@@ -62,7 +61,7 @@ async def start(request: Request):
return Response("""<response><code>10</code><message><ja>Invalid request data.</ja><en>Invalid request data.</en></message></response>""", media_type="application/xml" return Response("""<response><code>10</code><message><ja>Invalid request data.</ja><en>Invalid request data.</en></message></response>""", 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) user_info, device_info = await decrypt_fields_to_user_info(decrypted_fields)
username = user_info['username'] if user_info else None username = user_info['username'] if user_info else None
@@ -74,7 +73,7 @@ async def start(request: Request):
return Response("""<response><code>403</code><message>Access denied.</message></response>""", media_type="application/xml") return Response("""<response><code>403</code><message>Access denied.</message></response>""", media_type="application/xml")
if user_id: 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_model_pak(decrypted_fields, user_id))
root.append(await get_tune_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() device_id = decrypted_fields[b'vid'][0].decode()
user_info, device_info = await decrypt_fields_to_user_info(decrypted_fields) 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") daily_reward_elem = root.find(".//login_bonus")
last_count_elem = daily_reward_elem.find("last_count") last_count_elem = daily_reward_elem.find("last_count")

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<response>
<code>1</code>
<message>
<en>testtest</en>
<ja>Welcome to the private server!</ja>
<fr>Welcome to the private server!</fr>
<it>Welcome to the private server!</it>
</message>
</response>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?><response> <?xml version="1.0" encoding="UTF-8"?><response>
<code>0</code>
<area>us</area> <area>us</area>
<is_tutorial>0</is_tutorial> <is_tutorial>0</is_tutorial>
<cm_button_level>10</cm_button_level> <cm_button_level>10</cm_button_level>

View File

@@ -82,6 +82,7 @@
<li class="nav-item"><a class="nav-link" href="#" id="bindTab">Bind</a></li> <li class="nav-item"><a class="nav-link" href="#" id="bindTab">Bind</a></li>
<li class="nav-item"><a class="nav-link" href="#" id="websTab">Webs</a></li> <li class="nav-item"><a class="nav-link" href="#" id="websTab">Webs</a></li>
<li class="nav-item"><a class="nav-link" href="#" id="logsTab">Logs</a></li> <li class="nav-item"><a class="nav-link" href="#" id="logsTab">Logs</a></li>
<li class="nav-item"><a class="nav-link" href="#" id="maintenanceTab">Maintenance</a></li>
</ul> </ul>
<div class="d-flex ms-auto"> <div class="d-flex ms-auto">
<button class="btn btn-primary me-2" id="searchBtn"> <button class="btn btn-primary me-2" id="searchBtn">
@@ -173,6 +174,38 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Maintenance Settings Modal -->
<div class="modal fade" id="maintenanceModal" tabindex="-1" aria-labelledby="maintenanceModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<form id="maintenanceForm">
<div class="modal-header">
<h5 class="modal-title" id="maintenanceModalLabel">Maintenance Settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="max-height: 60vh; overflow-y: auto;">
<div class="row mb-3">
<div class="col-md-2">
<label class="form-label">Status</label>
<input type="number" class="form-control" id="ServerMaintenanceStatus" placeholder="Enter status code">
</div>
</div>
<div class="mb-3">
<label class="form-label">Message</label>
<input type="text" class="form-control mb-1" id="ServerTipsEn" placeholder="English">
<input type="text" class="form-control mb-1" id="ServerTipsJa" placeholder="Japanese">
<input type="text" class="form-control mb-1" id="ServerTipsFr" placeholder="French">
<input type="text" class="form-control mb-1" id="ServerTipsIt" placeholder="Italian">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary" onclick="updateMaintenance()">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
<script> <script>
window.currentTableName = null; window.currentTableName = null;
let dataUserId = -1; let dataUserId = -1;
@@ -483,6 +516,62 @@ document.getElementById('batchTokensTab').onclick = () => showTable("batch_token
document.getElementById('bindTab').onclick = () => showTable("binds"); document.getElementById('bindTab').onclick = () => showTable("binds");
document.getElementById('websTab').onclick = () => showTable("webs"); document.getElementById('websTab').onclick = () => showTable("webs");
document.getElementById('logsTab').onclick = () => showTable("logs"); document.getElementById('logsTab').onclick = () => showTable("logs");
document.getElementById('maintenanceTab').onclick = () => editMaintenance();
function load_maintenance() {
fetch('/files/notice.xml?timestamp=' + new Date().getTime())
.then(response => response.text())
.then(xmlData => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlData, "application/xml");
const statusCode = xmlDoc.querySelector("response > code").textContent;
const messageEn = xmlDoc.querySelector("response > message > en").textContent;
const messageJa = xmlDoc.querySelector("response > message > ja").textContent;
const messageFr = xmlDoc.querySelector("response > message > fr").textContent;
const messageIt = xmlDoc.querySelector("response > message > it").textContent;
document.getElementById("ServerMaintenanceStatus").value = parseInt(statusCode, 10);
document.getElementById("ServerTipsEn").value = messageEn;
document.getElementById("ServerTipsJa").value = messageJa;
document.getElementById("ServerTipsFr").value = messageFr;
document.getElementById("ServerTipsIt").value = messageIt;
console.log('Maintenance data loaded successfully.');
})
.catch(error => console.error('Error loading notice.xml:', error));
}
load_maintenance();
function editMaintenance() {
const maintenanceModal = new bootstrap.Modal(document.getElementById('maintenanceModal'));
maintenanceModal.show();
}
async function updateMaintenance() {
const status = parseInt(document.getElementById('ServerMaintenanceStatus').value, 10) || 0;
const message_en = document.getElementById('ServerTipsEn').value || "";
const message_ja = document.getElementById('ServerTipsJa').value || "";
const message_fr = document.getElementById('ServerTipsFr').value || "";
const message_it = document.getElementById('ServerTipsIt').value || "";
const response = await fetch('/admin/update_maintenance', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ status: status, message_en: message_en, message_ja: message_ja, message_fr: message_fr, message_it: message_it })
});
const result = await response.json();
if (result.status === "success") {
const modal = bootstrap.Modal.getOrCreateInstance(document.getElementById('maintenanceModal'));
modal.hide();
load_maintenance();
} else {
alert(result.message || "Update failed.");
}
}
document.getElementById('logoutBtn').addEventListener('click', function() { document.getElementById('logoutBtn').addEventListener('click', function() {
document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";