Discord stuff

This commit is contained in:
UnitedAirforce
2026-03-16 17:55:09 +08:00
parent c4ceefb282
commit ed7714efdb
4 changed files with 333 additions and 15 deletions
+9 -13
View File
@@ -4,7 +4,7 @@ from starlette.routing import Route
from datetime import datetime
import secrets
from api.misc import is_alphanumeric, inform_page, verify_password, hash_password, crc32_decimal, should_serve, generate_salt
from api.misc import is_alphanumeric, inform_page, verify_password, hash_password, crc32_decimal, should_serve, generate_salt, validate_password
from api.database import check_blacklist, user_name_to_user_info, decrypt_fields_to_user_info, set_user_data_using_decrypted_fields, get_user_from_save_id, create_user, logout_user, login_user, get_bind, read_user_save_file, write_user_save_file
from api.crypt import decrypt_fields
from config import AUTHORIZATION_MODE
@@ -69,11 +69,9 @@ async def password_reset(request: Request):
user_info, device_info = await decrypt_fields_to_user_info(decrypted_fields)
if user_info:
username = user_info['username']
if username == new_password:
return inform_page("FAILED:<br>Username cannot be the same as password.", 0)
if len(new_password) < 6:
return inform_page("FAILED:<br>Password must have 6 or more characters.", 0)
result, str = validate_password(user_info['username'], new_password)
if not result:
return inform_page("FAILED:<br>" + str, 0)
old_hash = user_info['password_hash']
if old_hash:
@@ -172,18 +170,16 @@ async def register(request: Request):
if not username or not password:
return inform_page("FAILED:<br>Missing username or password.", 0)
if username == password:
return inform_page("FAILED:<br>Username cannot be the same as password.", 0)
if len(username) < 6 or len(username) > 20:
return inform_page("FAILED:<br>Username must be between 6 and 20<br>characters long.", 0)
if len(password) < 6:
return inform_page("FAILED:<br>Password must have<br>6 or above characters.", 0)
if not is_alphanumeric(username):
return inform_page("FAILED:<br>Username must consist entirely of<br>alphanumeric characters.", 0)
result, str = validate_password(username, password)
if not result:
return inform_page("FAILED:<br>" + str, 0)
decrypted_fields, _ = await decrypt_fields(request)
if not decrypted_fields:
+34 -1
View File
@@ -4,7 +4,7 @@ from starlette.requests import Request
from starlette.routing import Route
from datetime import datetime
from api.misc import is_alphanumeric, inform_page, generate_salt, check_email, generate_otp
from api.misc import is_alphanumeric, inform_page, generate_salt, check_email, generate_otp, validate_password, hash_password
from api.database import player_database, accounts, binds, decrypt_fields_to_user_info, get_bind, verify_user_code, user_name_to_user_info
from api.crypt import decrypt_fields
from api.email_hook import send_email_to_user
@@ -139,6 +139,38 @@ async def discord_get_bind(request: Request, form):
return JSONResponse({"state": 1, "message": "Your account is binded to: " + user_record['username']})
@require_authorization(mode_required=[2])
@validate_form_fields(["discord_id", "password"])
@check_discord_api_key()
async def discord_reset_password(request: Request, form):
discord_id = form.get("discord_id")
password = form.get("password")
query = binds.select().where(binds.c.bind_account == discord_id).where(binds.c.is_verified == 1)
bind_record = await player_database.fetch_one(query)
bind_record = dict(bind_record) if bind_record else None
if not bind_record:
return JSONResponse({"state": 0, "message": "No verified bind found for this Discord ID."}, status_code=404)
user_query = accounts.select().where(accounts.c.id == bind_record['user_id'])
user_record = await player_database.fetch_one(user_query)
user_record = dict(user_record) if user_record else None
if not user_record:
return JSONResponse({"state": 0, "message": "User associated with this bind does not exist."}, status_code=405)
result, str = validate_password(user_record['username'], password)
if not result:
return JSONResponse({"state": 0, "message": str}, status_code=406)
hashed_new_password = hash_password(password)
query = accounts.update().where(accounts.c.id == user_record['id']).values(
password_hash=hashed_new_password
)
await player_database.execute(query)
return JSONResponse({"state": 1, "message": "Your password is reset."})
@require_authorization(mode_required=[2])
@validate_form_fields(["discord_id"])
@check_discord_api_key()
@@ -183,6 +215,7 @@ routes = [
Route('/send_email', send_email, methods=['POST']),
Route('/discord_get_token', discord_get_token, methods=['POST']),
Route('/discord_get_bind', discord_get_bind, methods=['POST']),
Route('/discord_reset_password', discord_reset_password, methods=['POST']),
Route('/discord_ban', discord_ban, methods=['POST']),
Route('/discord_unban', discord_unban, methods=['POST']),
Route('/verify', verify_user, methods=['POST'])
+10 -1
View File
@@ -318,4 +318,13 @@ async def get_start_xml():
for child in response_root:
root.append(child)
return root
return root
def validate_password(username, password):
if username == password:
return 0, "Username cannot be the same as password."
if len(password) < 6:
return 0, "Password must have<br>6 or above characters."
return 1, ""
+280
View File
@@ -0,0 +1,280 @@
import discord
from discord.ext import commands
from discord import Interaction, TextStyle, app_commands
from discord.ui import Modal, TextInput, View, Button
import httpx
from config import DISCORD_BOT_SECRET, DISCORD_BOT_API_KEY, OVERRIDE_HOST, HOST, PORT
intents = discord.Intents.default()
intents.members = True
intents.message_content = True
bot = commands.Bot(command_prefix="/", intents=intents)
BIND_CHANNEL_ID = 1439460843015372971
URL_HOST = OVERRIDE_HOST if OVERRIDE_HOST is not None else ("http://" + HOST + ":" + str(PORT) + "/")
def only_in_bind_channel():
def predicate(interaction: discord.Interaction) -> bool:
if isinstance(interaction.channel, discord.DMChannel):
return False
return interaction.channel_id == BIND_CHANNEL_ID
return app_commands.check(predicate)
@bot.event
async def on_ready():
print(f"[Discord] Bot connected as {bot.user}")
try:
await bot.tree.sync()
except Exception as e:
print("[Discord] Error syncing commands:", e)
# Register persistent view
bot.add_view(BindButtons())
channel = bot.get_channel(BIND_CHANNEL_ID)
if channel:
# Delete old messages from the bot
async for message in channel.history(limit=100):
if message.author == bot.user:
try:
await message.delete()
except:
pass
# Send the new message with buttons
await channel.send("Use the buttons below:", view=BindButtons())
async def get_account_info(discord_id: str):
request_url = URL_HOST + "discord_get_bind"
request_post = {"discord_id": discord_id}
request_headers = {"X-API-KEY": DISCORD_BOT_API_KEY}
async with httpx.AsyncClient() as client:
response = await client.post(request_url, data=request_post, headers=request_headers)
response_data = response.json()
print("get_account_info called by ", discord_id, " response payload:", response.text)
prefix = "✅ " if response_data.get("state") == 1 else "❌ "
return prefix + response_data.get("message", "No message provided.")
async def get_otp(username: str, bind_token: str, discord_id: str):
request_url = URL_HOST + "discord_get_token"
request_post = {
"username": username,
"bind_token": bind_token,
"discord_id": discord_id
}
request_headers = {"X-API-KEY": DISCORD_BOT_API_KEY}
async with httpx.AsyncClient() as client:
response = await client.post(request_url, data=request_post, headers=request_headers)
print("get_otp called by ", discord_id, " response payload:", response.text)
response_data = response.json()
prefix = "✅ " if response_data.get("state") == 1 else "❌ "
return prefix + response_data.get("message", "No message provided.")
async def reset_password(password: str, discord_id: str):
request_url = URL_HOST + "discord_reset_password"
request_post = {
"password": password,
"discord_id": discord_id
}
request_headers = {"X-API-KEY": DISCORD_BOT_API_KEY}
async with httpx.AsyncClient() as client:
response = await client.post(request_url, data=request_post, headers=request_headers)
print("get_otp called by ", discord_id, " response payload:", response.text)
response_data = response.json()
prefix = "✅ " if response_data.get("state") == 1 else "❌ "
return prefix + response_data.get("message", "No message provided.")
async def ban_user_from_game_service(discord_id: str):
try:
request_url = URL_HOST + "discord_ban"
request_post = {"discord_id": discord_id}
request_headers = {"X-API-KEY": DISCORD_BOT_API_KEY}
async with httpx.AsyncClient() as client:
response = await client.post(request_url, data=request_post, headers=request_headers)
data = response.json()
print("ban_user_from_game_service called by ", discord_id, " response payload:", response.text)
prefix = "✅ " if data.get("state") == 1 else "❌ "
return prefix + data.get("message", "No message provided.")
except Exception as e:
return f"❌ Error: {str(e)}"
async def try_unban_user_from_game_service(discord_id: str):
try:
request_url = URL_HOST + "discord_unban"
request_post = {"discord_id": discord_id}
request_headers = {"X-API-KEY": DISCORD_BOT_API_KEY}
async with httpx.AsyncClient() as client:
response = await client.post(request_url, data=request_post, headers=request_headers)
data = response.json()
print("try_unban_user_from_game_service called by ", discord_id, " response payload:", response.text)
prefix = "✅ " if data.get("state") == 1 else "❌ "
return prefix + data.get("message", "No message provided.")
except Exception as e:
return f"❌ Error: {str(e)}"
# -------------------------------
# Modal Definition
# -------------------------------
class BindModal(Modal, title="Bind Your Account"):
username = TextInput(
label="Username",
placeholder="Enter your in-game username",
style=TextStyle.short,
required=True,
)
bind_token = TextInput(
label="Bind Token",
placeholder="Enter your bind token",
style=TextStyle.short,
required=True,
)
async def on_submit(self, interaction: Interaction):
username = self.username.value
bind_token = self.bind_token.value
if not username or not username.strip():
return await interaction.response.send_message(
"❌ Username cannot be empty.", ephemeral=True
)
if len(username) < 6 or len(username) > 20:
return await interaction.response.send_message(
"❌ Username length not valid.", ephemeral=True
)
if not bind_token or not bind_token.strip():
return await interaction.response.send_message(
"❌ Bind token cannot be empty.", ephemeral=True
)
if len(bind_token) > 10:
return await interaction.response.send_message(
"❌ Bind token length not valid.", ephemeral=True
)
discord_id = str(interaction.user.id)
response = await get_otp(username, bind_token, discord_id)
await interaction.response.send_message(response, ephemeral=True)
class PasswordResetModal(Modal, title="Password Reset"):
password = TextInput(
label="New Password (Requirement: )",
placeholder="Your New Password",
style=TextStyle.short,
required=True,
)
repeat_password = TextInput(
label="Repeat New Password",
placeholder="Repeat The Password",
style=TextStyle.short,
required=True,
)
async def on_submit(self, interaction: Interaction):
password = self.password.value
repeat_password = self.repeat_password.value
if not password or not password.strip():
return await interaction.response.send_message(
"❌ Password cannot be empty.", ephemeral=True
)
if not repeat_password or not repeat_password.strip():
return await interaction.response.send_message(
"❌ Repeat Password cannot be empty.", ephemeral=True
)
if len(password) < 6:
return await interaction.response.send_message(
"❌ Password must have 6 or more characters.", ephemeral=True
)
if password != repeat_password:
return await interaction.response.send_message(
"❌ Repeat Password does not match.", ephemeral=True
)
discord_id = str(interaction.user.id)
response = await reset_password(password, discord_id)
await interaction.response.send_message(response, ephemeral=True)
class BindButtons(View):
def __init__(self):
super().__init__(timeout=None) # Persistent buttons
@discord.ui.button(label="Bind Account", style=discord.ButtonStyle.primary, custom_id="btn_bind")
async def bind_button(self, interaction: Interaction, button: Button):
await interaction.response.send_modal(BindModal())
@discord.ui.button(label="Who Am I?", style=discord.ButtonStyle.secondary, custom_id="btn_whoami")
async def whoami_button(self, interaction: Interaction, button: Button):
discord_id = str(interaction.user.id)
response = await get_account_info(discord_id)
await interaction.response.send_message(response, ephemeral=True)
@discord.ui.button(label="Reset Password", style=discord.ButtonStyle.danger, custom_id="btn_password_reset")
async def password_reset_button(self, interaction: Interaction, button: Button):
await interaction.response.send_modal(PasswordResetModal())
# -------------------------------
# Member Events
# -------------------------------
@bot.event
async def on_member_remove(member):
print(f"{member.name} left the server → banning in-game.")
await ban_user_from_game_service(str(member.id))
@bot.event
async def on_member_join(member):
print(f"{member.name} joined the server → unbanning in-game if needed.")
await try_unban_user_from_game_service(str(member.id))
# -------------------------------
# Prefix command error handler
# -------------------------------
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.MissingRequiredArgument):
await ctx.send(f"❌ Missing argument: {error.param.name}.", ephemeral=True)
else:
await ctx.send(f"❌ Error: {str(error)}", ephemeral=True)
@bot.event
async def on_message(message):
if message.author == bot.user:
return
if isinstance(message.channel, discord.DMChannel):
await message.channel.send("❌ This bot does not accept direct messages. Please use it in a server.")
return
if message.channel.id == BIND_CHANNEL_ID:
# Delete user messages
await message.delete()
return
bot.run(DISCORD_BOT_SECRET)