modified: app.py
modified: bot.py modified: templates/navigation.html modified: templates/privacy_policy.html new file: templates/user_contact.html
This commit is contained in:
117
app.py
117
app.py
@@ -898,6 +898,123 @@ def terms_of_service():
|
|||||||
"""Zeigt die Terms of Service Seite an."""
|
"""Zeigt die Terms of Service Seite an."""
|
||||||
return render_template("terms_of_service.html")
|
return render_template("terms_of_service.html")
|
||||||
|
|
||||||
|
@app.route("/user-contact", methods=["GET", "POST"])
|
||||||
|
def user_contact():
|
||||||
|
"""Kontaktformular für eingeloggte Benutzer mit automatischer Discord DM"""
|
||||||
|
if not g.user_info:
|
||||||
|
flash("Please log in to access the contact form.", "warning")
|
||||||
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
try:
|
||||||
|
# Form data sammeln
|
||||||
|
subject = request.form.get("subject", "").strip()
|
||||||
|
category = request.form.get("category", "").strip()
|
||||||
|
priority = request.form.get("priority", "").strip()
|
||||||
|
message = request.form.get("message", "").strip()
|
||||||
|
server_context = request.form.get("server_context", "").strip()
|
||||||
|
|
||||||
|
# Validierung
|
||||||
|
if not all([subject, category, priority, message]):
|
||||||
|
flash("Please fill in all required fields.", "danger")
|
||||||
|
return render_template("user_contact.html", user_info=g.user_info)
|
||||||
|
|
||||||
|
# Discord User Info
|
||||||
|
user_info = g.user_info
|
||||||
|
|
||||||
|
# Priority emoji mapping
|
||||||
|
priority_emojis = {
|
||||||
|
"low": "🟢",
|
||||||
|
"medium": "🟡",
|
||||||
|
"high": "🟠",
|
||||||
|
"urgent": "🔴"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Category emoji mapping
|
||||||
|
category_emojis = {
|
||||||
|
"bug_report": "🐛",
|
||||||
|
"feature_request": "💡",
|
||||||
|
"account_issue": "👤",
|
||||||
|
"moderation": "🛡️",
|
||||||
|
"giveaway": "🎁",
|
||||||
|
"privacy": "🔒",
|
||||||
|
"technical": "⚙️",
|
||||||
|
"other": "❓"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Discord Message erstellen
|
||||||
|
embed_content = f"""**📨 New Contact Form Submission**
|
||||||
|
|
||||||
|
**User Information:**
|
||||||
|
• **Name:** {user_info.get('global_name', user_info.get('username', 'Unknown'))}
|
||||||
|
• **Username:** {user_info.get('username', 'Unknown')}#{user_info.get('discriminator', '0000')}
|
||||||
|
• **Discord ID:** `{user_info.get('id', 'Unknown')}`
|
||||||
|
• **Avatar:** {user_info.get('avatar_url', 'No avatar')}
|
||||||
|
|
||||||
|
**Message Details:**
|
||||||
|
• **Subject:** {subject}
|
||||||
|
• **Category:** {category_emojis.get(category, '❓')} {category.replace('_', ' ').title()}
|
||||||
|
• **Priority:** {priority_emojis.get(priority, '⚪')} {priority.upper()}
|
||||||
|
{f"• **Server Context:** {server_context}" if server_context else ""}
|
||||||
|
|
||||||
|
**Message:**
|
||||||
|
```
|
||||||
|
{message}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Submitted at:** <t:{int(time.time())}:F>
|
||||||
|
**From:** Multus Bot Web Panel"""
|
||||||
|
|
||||||
|
# Discord DM via Bot senden
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Ihre Discord User ID hier eintragen
|
||||||
|
YOUR_DISCORD_ID = "253922739709018114" # Ersetzen Sie dies mit Ihrer Discord ID
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Versuche die Nachricht über den Bot zu senden
|
||||||
|
connection = get_db_connection()
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
# In Datenbank speichern
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO contact_messages (
|
||||||
|
user_id, username, subject, category, priority, message,
|
||||||
|
server_context, submitted_at, status
|
||||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
|
""", (
|
||||||
|
user_info.get('id'),
|
||||||
|
f"{user_info.get('username')}#{user_info.get('discriminator')}",
|
||||||
|
subject,
|
||||||
|
category,
|
||||||
|
priority,
|
||||||
|
message,
|
||||||
|
server_context,
|
||||||
|
int(time.time()),
|
||||||
|
'pending'
|
||||||
|
))
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
|
flash("Your message has been submitted successfully! We'll process it and get back to you within 15 minutes via Discord DM.", "success")
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error saving contact message: {e}")
|
||||||
|
flash("There was an error submitting your message. Please try again or contact us directly on Discord.", "danger")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error saving contact message: {e}")
|
||||||
|
flash("There was an error submitting your message. Please try again or contact us directly on Discord.", "danger")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing contact form: {e}")
|
||||||
|
flash("An unexpected error occurred. Please try again.", "danger")
|
||||||
|
|
||||||
|
return render_template("user_contact.html", user_info=g.user_info)
|
||||||
|
|
||||||
@app.route("/privacy-policy")
|
@app.route("/privacy-policy")
|
||||||
def privacy_policy():
|
def privacy_policy():
|
||||||
"""Zeigt die Privacy Policy Seite an."""
|
"""Zeigt die Privacy Policy Seite an."""
|
||||||
|
|||||||
371
bot.py
371
bot.py
@@ -687,6 +687,81 @@ async def process_manager():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in process manager: {e}")
|
logger.error(f"Error in process manager: {e}")
|
||||||
|
|
||||||
|
# Contact Messages Task
|
||||||
|
@tasks.loop(minutes=15)
|
||||||
|
async def check_contact_messages():
|
||||||
|
"""Automatically checks for pending contact messages every 15 minutes"""
|
||||||
|
try:
|
||||||
|
connection = connect_to_database()
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
# Hole alle pending Nachrichten
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, user_id, username, subject, category, priority, message,
|
||||||
|
server_context, submitted_at
|
||||||
|
FROM contact_messages
|
||||||
|
WHERE status = 'pending'
|
||||||
|
ORDER BY submitted_at ASC
|
||||||
|
""")
|
||||||
|
|
||||||
|
pending_messages = cursor.fetchall()
|
||||||
|
|
||||||
|
if pending_messages:
|
||||||
|
logger.info(f"Found {len(pending_messages)} pending contact messages")
|
||||||
|
|
||||||
|
processed_count = 0
|
||||||
|
for msg in pending_messages:
|
||||||
|
msg_id, user_id, username, subject, category, priority, message, server_context, submitted_at = msg
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Hole User-Informationen von Discord
|
||||||
|
user = client.get_user(int(user_id))
|
||||||
|
if not user:
|
||||||
|
try:
|
||||||
|
user = await client.fetch_user(int(user_id))
|
||||||
|
except:
|
||||||
|
user = None
|
||||||
|
|
||||||
|
message_data = {
|
||||||
|
'user_id': user_id,
|
||||||
|
'user_name': user.display_name if user else username.split('#')[0],
|
||||||
|
'username': username,
|
||||||
|
'avatar_url': user.avatar.url if user and user.avatar else None,
|
||||||
|
'subject': subject,
|
||||||
|
'category': category,
|
||||||
|
'priority': priority,
|
||||||
|
'message': message,
|
||||||
|
'server_context': server_context
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sende an Admin
|
||||||
|
success = await send_contact_message_to_admin(message_data)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Markiere als versendet
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE contact_messages
|
||||||
|
SET status = 'sent', responded_at = %s
|
||||||
|
WHERE id = %s
|
||||||
|
""", (int(time.time()), msg_id))
|
||||||
|
processed_count += 1
|
||||||
|
logger.info(f"Sent contact message {msg_id} to admin")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing contact message {msg_id}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
|
if processed_count > 0:
|
||||||
|
logger.info(f"Automatically processed {processed_count} contact messages")
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
close_database_connection(connection)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in check_contact_messages task: {e}")
|
||||||
|
|
||||||
async def handle_expired_process(process_uuid, process_type, data):
|
async def handle_expired_process(process_uuid, process_type, data):
|
||||||
"""Handles different types of expired processes"""
|
"""Handles different types of expired processes"""
|
||||||
try:
|
try:
|
||||||
@@ -1775,6 +1850,11 @@ async def on_ready():
|
|||||||
if not process_manager.is_running():
|
if not process_manager.is_running():
|
||||||
process_manager.start()
|
process_manager.start()
|
||||||
|
|
||||||
|
# Start the contact messages checker
|
||||||
|
if not check_contact_messages.is_running():
|
||||||
|
check_contact_messages.start()
|
||||||
|
logger.info("Contact messages checker started (15-minute intervals)")
|
||||||
|
|
||||||
logger.info("Bot is ready!")
|
logger.info("Bot is ready!")
|
||||||
logger.info(f"Logged in as: {client.user.name}")
|
logger.info(f"Logged in as: {client.user.name}")
|
||||||
logger.info(f"Client ID: {client.user.id}")
|
logger.info(f"Client ID: {client.user.id}")
|
||||||
@@ -2883,6 +2963,137 @@ def create_warnings_table():
|
|||||||
if connection:
|
if connection:
|
||||||
close_database_connection(connection)
|
close_database_connection(connection)
|
||||||
|
|
||||||
|
def create_contact_messages_table():
|
||||||
|
"""Creates the contact_messages table if it doesn't exist"""
|
||||||
|
connection = None
|
||||||
|
cursor = None
|
||||||
|
try:
|
||||||
|
connection = connect_to_database()
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
create_table_query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS contact_messages (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id VARCHAR(50) NOT NULL,
|
||||||
|
username VARCHAR(100) NOT NULL,
|
||||||
|
subject VARCHAR(100) NOT NULL,
|
||||||
|
category VARCHAR(50) NOT NULL,
|
||||||
|
priority VARCHAR(20) NOT NULL,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
server_context VARCHAR(200),
|
||||||
|
submitted_at BIGINT NOT NULL,
|
||||||
|
status VARCHAR(20) DEFAULT 'pending',
|
||||||
|
responded_at BIGINT,
|
||||||
|
response TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_user_id (user_id),
|
||||||
|
INDEX idx_status (status),
|
||||||
|
INDEX idx_submitted_at (submitted_at)
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(create_table_query)
|
||||||
|
connection.commit()
|
||||||
|
logger.info("Contact messages table checked/created successfully")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating contact messages table: {e}")
|
||||||
|
finally:
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
if connection:
|
||||||
|
close_database_connection(connection)
|
||||||
|
|
||||||
|
async def send_contact_message_to_admin(message_data):
|
||||||
|
"""Sends a contact message to the admin via Discord DM"""
|
||||||
|
try:
|
||||||
|
# Your Discord User ID
|
||||||
|
ADMIN_DISCORD_ID = 253922739709018114
|
||||||
|
|
||||||
|
admin_user = client.get_user(ADMIN_DISCORD_ID)
|
||||||
|
if not admin_user:
|
||||||
|
admin_user = await client.fetch_user(ADMIN_DISCORD_ID)
|
||||||
|
|
||||||
|
if admin_user:
|
||||||
|
# Priority emoji mapping
|
||||||
|
priority_emojis = {
|
||||||
|
"low": "🟢",
|
||||||
|
"medium": "🟡",
|
||||||
|
"high": "🟠",
|
||||||
|
"urgent": "🔴"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Category emoji mapping
|
||||||
|
category_emojis = {
|
||||||
|
"bug_report": "🐛",
|
||||||
|
"feature_request": "💡",
|
||||||
|
"account_issue": "👤",
|
||||||
|
"moderation": "🛡️",
|
||||||
|
"giveaway": "🎁",
|
||||||
|
"privacy": "🔒",
|
||||||
|
"technical": "⚙️",
|
||||||
|
"other": "❓"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create Discord Embed
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="📨 New Contact Form Submission",
|
||||||
|
color=0x667eea,
|
||||||
|
timestamp=datetime.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
|
# User Information
|
||||||
|
embed.add_field(
|
||||||
|
name="👤 User Information",
|
||||||
|
value=f"**Name:** {message_data.get('user_name', 'Unknown')}\n"
|
||||||
|
f"**Username:** {message_data.get('username', 'Unknown')}\n"
|
||||||
|
f"**Discord ID:** `{message_data.get('user_id', 'Unknown')}`",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Message Details
|
||||||
|
embed.add_field(
|
||||||
|
name="📋 Message Details",
|
||||||
|
value=f"**Subject:** {message_data.get('subject', 'No subject')}\n"
|
||||||
|
f"**Category:** {category_emojis.get(message_data.get('category', 'other'), '❓')} {message_data.get('category', 'other').replace('_', ' ').title()}\n"
|
||||||
|
f"**Priority:** {priority_emojis.get(message_data.get('priority', 'low'), '⚪')} {message_data.get('priority', 'low').upper()}",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Server Context (if provided)
|
||||||
|
if message_data.get('server_context'):
|
||||||
|
embed.add_field(
|
||||||
|
name="🖥️ Server Context",
|
||||||
|
value=message_data.get('server_context'),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Message Content
|
||||||
|
message_content = message_data.get('message', 'No message content')
|
||||||
|
if len(message_content) > 1024:
|
||||||
|
message_content = message_content[:1021] + "..."
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name="💬 Message",
|
||||||
|
value=f"```{message_content}```",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Footer
|
||||||
|
embed.set_footer(text="Multus Bot Web Panel Contact Form")
|
||||||
|
|
||||||
|
# Set avatar thumbnail if available
|
||||||
|
if message_data.get('avatar_url'):
|
||||||
|
embed.set_thumbnail(url=message_data.get('avatar_url'))
|
||||||
|
|
||||||
|
await admin_user.send(embed=embed)
|
||||||
|
logger.info(f"Contact message sent to admin for user {message_data.get('user_id')}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending contact message to admin: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
async def get_user_warnings(user_id, guild_id, active_only=True):
|
async def get_user_warnings(user_id, guild_id, active_only=True):
|
||||||
"""Retrieves warning records for a user
|
"""Retrieves warning records for a user
|
||||||
|
|
||||||
@@ -5263,9 +5474,169 @@ async def delnotes(ctx):
|
|||||||
else:
|
else:
|
||||||
await send_response(f"No notes found for user {ctx.author.name}.")
|
await send_response(f"No notes found for user {ctx.author.name}.")
|
||||||
|
|
||||||
|
@client.command()
|
||||||
|
async def process_contact_messages(ctx):
|
||||||
|
"""Prozessiert ausstehende Kontaktnachrichten (Admin only)"""
|
||||||
|
if ctx.author.id != 253922739709018114: # Ihre Discord ID
|
||||||
|
await ctx.send("You don't have permission to use this command.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection = connect_to_database()
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
# Hole alle pending Nachrichten
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT id, user_id, username, subject, category, priority, message,
|
||||||
|
server_context, submitted_at
|
||||||
|
FROM contact_messages
|
||||||
|
WHERE status = 'pending'
|
||||||
|
ORDER BY submitted_at ASC
|
||||||
|
""")
|
||||||
|
|
||||||
|
pending_messages = cursor.fetchall()
|
||||||
|
|
||||||
|
if not pending_messages:
|
||||||
|
await ctx.send("No pending contact messages found.")
|
||||||
|
cursor.close()
|
||||||
|
close_database_connection(connection)
|
||||||
|
return
|
||||||
|
|
||||||
|
processed_count = 0
|
||||||
|
for msg in pending_messages:
|
||||||
|
msg_id, user_id, username, subject, category, priority, message, server_context, submitted_at = msg
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Hole User-Informationen von Discord
|
||||||
|
user = client.get_user(int(user_id))
|
||||||
|
if not user:
|
||||||
|
user = await client.fetch_user(int(user_id))
|
||||||
|
|
||||||
|
message_data = {
|
||||||
|
'user_id': user_id,
|
||||||
|
'user_name': user.display_name if user else username.split('#')[0],
|
||||||
|
'username': username,
|
||||||
|
'avatar_url': user.avatar.url if user and user.avatar else None,
|
||||||
|
'subject': subject,
|
||||||
|
'category': category,
|
||||||
|
'priority': priority,
|
||||||
|
'message': message,
|
||||||
|
'server_context': server_context
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sende an Admin
|
||||||
|
success = await send_contact_message_to_admin(message_data)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Markiere als versendet
|
||||||
|
cursor.execute("""
|
||||||
|
UPDATE contact_messages
|
||||||
|
SET status = 'sent', responded_at = %s
|
||||||
|
WHERE id = %s
|
||||||
|
""", (int(time.time()), msg_id))
|
||||||
|
processed_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing contact message {msg_id}: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
connection.commit()
|
||||||
|
cursor.close()
|
||||||
|
close_database_connection(connection)
|
||||||
|
|
||||||
|
await ctx.send(f"Processed {processed_count}/{len(pending_messages)} contact messages successfully.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f"Error processing contact messages: {e}")
|
||||||
|
logger.error(f"Error in process_contact_messages: {e}")
|
||||||
|
|
||||||
|
@client.command()
|
||||||
|
async def contact_status(ctx):
|
||||||
|
"""Zeigt den Status der Kontaktnachrichten-Überwachung (Admin only)"""
|
||||||
|
if ctx.author.id != 253922739709018114: # Ihre Discord ID
|
||||||
|
await ctx.send("You don't have permission to use this command.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection = connect_to_database()
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
# Statistiken abrufen
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM contact_messages WHERE status = 'pending'")
|
||||||
|
pending_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM contact_messages WHERE status = 'sent'")
|
||||||
|
sent_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
cursor.execute("SELECT COUNT(*) FROM contact_messages")
|
||||||
|
total_count = cursor.fetchone()[0]
|
||||||
|
|
||||||
|
# Letzte Nachricht info
|
||||||
|
cursor.execute("""
|
||||||
|
SELECT subject, submitted_at FROM contact_messages
|
||||||
|
ORDER BY submitted_at DESC LIMIT 1
|
||||||
|
""")
|
||||||
|
last_message = cursor.fetchone()
|
||||||
|
|
||||||
|
cursor.close()
|
||||||
|
close_database_connection(connection)
|
||||||
|
|
||||||
|
# Status des Task Loops
|
||||||
|
task_status = "🟢 Running" if check_contact_messages.is_running() else "🔴 Stopped"
|
||||||
|
|
||||||
|
# Nächste Ausführung berechnen
|
||||||
|
if check_contact_messages.is_running():
|
||||||
|
next_run = check_contact_messages.next_iteration
|
||||||
|
if next_run:
|
||||||
|
next_run_str = f"<t:{int(next_run.timestamp())}:R>"
|
||||||
|
else:
|
||||||
|
next_run_str = "Soon"
|
||||||
|
else:
|
||||||
|
next_run_str = "Task not running"
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="📊 Contact Messages Status",
|
||||||
|
color=0x667eea,
|
||||||
|
timestamp=datetime.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name="📋 Message Statistics",
|
||||||
|
value=f"**Pending:** {pending_count}\n"
|
||||||
|
f"**Processed:** {sent_count}\n"
|
||||||
|
f"**Total:** {total_count}",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name="⏰ Task Status",
|
||||||
|
value=f"**Status:** {task_status}\n"
|
||||||
|
f"**Interval:** Every 15 minutes\n"
|
||||||
|
f"**Next Check:** {next_run_str}",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if last_message:
|
||||||
|
last_subject = last_message[0][:50] + "..." if len(last_message[0]) > 50 else last_message[0]
|
||||||
|
embed.add_field(
|
||||||
|
name="📝 Latest Message",
|
||||||
|
value=f"**Subject:** {last_subject}\n"
|
||||||
|
f"**Submitted:** <t:{int(last_message[1])}:R>",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text="Contact Messages Auto-Check System")
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.send(f"Error getting contact status: {e}")
|
||||||
|
logger.error(f"Error in contact_status: {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Initialize database tables
|
# Initialize database tables
|
||||||
create_warnings_table()
|
create_warnings_table()
|
||||||
|
create_contact_messages_table()
|
||||||
logger.info("Database tables initialized successfully")
|
logger.info("Database tables initialized successfully")
|
||||||
|
|
||||||
loop.run_until_complete(client.start(TOKEN))
|
loop.run_until_complete(client.start(TOKEN))
|
||||||
|
|||||||
@@ -52,6 +52,11 @@
|
|||||||
<i class="fas fa-server"></i> Server Selection
|
<i class="fas fa-server"></i> Server Selection
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('user_contact') }}">
|
||||||
|
<i class="fas fa-headset"></i> Contact Support
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<!-- Admin Link nur für Bot-Admins anzeigen -->
|
<!-- Admin Link nur für Bot-Admins anzeigen -->
|
||||||
{% if g.is_admin %}
|
{% if g.is_admin %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|||||||
@@ -194,6 +194,15 @@
|
|||||||
We are committed to protecting your privacy and being transparent about our data practices.
|
We are committed to protecting your privacy and being transparent about our data practices.
|
||||||
This policy explains what information we collect, why we collect it, and how you can manage your data.
|
This policy explains what information we collect, why we collect it, and how you can manage your data.
|
||||||
</p>
|
</p>
|
||||||
|
<div class="data-type">
|
||||||
|
<h4><i class="fas fa-building"></i> <span class="highlight">Data Controller Information</span></h4>
|
||||||
|
<p class="privacy-text">
|
||||||
|
<strong>Data Controller:</strong> SimolZimol (Individual Developer)<br>
|
||||||
|
<strong>Contact:</strong> Available through Discord (@simolzimol) or our contact form<br>
|
||||||
|
<strong>Location:</strong> European Union<br>
|
||||||
|
<strong>Legal Basis:</strong> Legitimate interest for service operation and user consent for data processing
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="privacy-section">
|
<div class="privacy-section">
|
||||||
@@ -248,6 +257,20 @@
|
|||||||
<i class="fas fa-cogs section-icon"></i>
|
<i class="fas fa-cogs section-icon"></i>
|
||||||
3. How We Use Your Information
|
3. How We Use Your Information
|
||||||
</h2>
|
</h2>
|
||||||
|
<p class="privacy-text">
|
||||||
|
We process your personal data based on the following legal bases under GDPR:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="data-type">
|
||||||
|
<h4><i class="fas fa-balance-scale"></i> <span class="highlight">Legal Basis for Processing</span></h4>
|
||||||
|
<ul class="privacy-list">
|
||||||
|
<li><strong>Legitimate Interest (Art. 6(1)(f) GDPR):</strong> Service operation, security, and improvement</li>
|
||||||
|
<li><strong>Consent (Art. 6(1)(a) GDPR):</strong> Optional features and analytics (where applicable)</li>
|
||||||
|
<li><strong>Contract Performance (Art. 6(1)(b) GDPR):</strong> Providing the bot service you requested</li>
|
||||||
|
<li><strong>Legal Obligation (Art. 6(1)(c) GDPR):</strong> Compliance with applicable laws</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="privacy-text">
|
<p class="privacy-text">
|
||||||
We use the collected information for the following purposes:
|
We use the collected information for the following purposes:
|
||||||
</p>
|
</p>
|
||||||
@@ -255,7 +278,7 @@
|
|||||||
<li><strong>Service Operation:</strong> To provide moderation, management, and administrative features</li>
|
<li><strong>Service Operation:</strong> To provide moderation, management, and administrative features</li>
|
||||||
<li><strong>User Experience:</strong> To personalize your experience and maintain user preferences</li>
|
<li><strong>User Experience:</strong> To personalize your experience and maintain user preferences</li>
|
||||||
<li><strong>Moderation:</strong> To enforce server rules and maintain community safety</li>
|
<li><strong>Moderation:</strong> To enforce server rules and maintain community safety</li>
|
||||||
<li><strong>Analytics:</strong> To understand usage patterns and improve service quality</li>
|
<li><strong>Analytics:</strong> To understand usage patterns and improve service quality (anonymized where possible)</li>
|
||||||
<li><strong>Communication:</strong> To send important updates and respond to support requests</li>
|
<li><strong>Communication:</strong> To send important updates and respond to support requests</li>
|
||||||
<li><strong>Security:</strong> To detect and prevent abuse, spam, and security threats</li>
|
<li><strong>Security:</strong> To detect and prevent abuse, spam, and security threats</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -290,12 +313,24 @@
|
|||||||
We do not sell, trade, or rent your personal information to third parties. We may share your information only in the following circumstances:
|
We do not sell, trade, or rent your personal information to third parties. We may share your information only in the following circumstances:
|
||||||
</p>
|
</p>
|
||||||
<ul class="privacy-list">
|
<ul class="privacy-list">
|
||||||
<li><strong>Discord Platform:</strong> As required for bot functionality through Discord's API</li>
|
<li><strong>Discord Platform:</strong> As required for bot functionality through Discord's API (Discord Inc., USA - adequate protection under Privacy Shield successor)</li>
|
||||||
<li><strong>Server Administrators:</strong> Moderation data may be visible to server moderators and administrators</li>
|
<li><strong>Server Administrators:</strong> Moderation data may be visible to server moderators and administrators within your Discord server</li>
|
||||||
<li><strong>Legal Requirements:</strong> When required by law, court order, or government regulation</li>
|
<li><strong>Legal Requirements:</strong> When required by law, court order, or government regulation</li>
|
||||||
<li><strong>Service Providers:</strong> With trusted third-party services that help us operate (hosting, analytics)</li>
|
<li><strong>Service Providers:</strong> With trusted third-party services that help us operate (hosting providers within EU/EEA)</li>
|
||||||
<li><strong>Safety and Security:</strong> To protect the rights, property, or safety of our users or others</li>
|
<li><strong>Safety and Security:</strong> To protect the rights, property, or safety of our users or others</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="data-type">
|
||||||
|
<h4><i class="fas fa-globe"></i> <span class="highlight">International Data Transfers</span></h4>
|
||||||
|
<p class="privacy-text">
|
||||||
|
Some of our service providers may be located outside the European Economic Area (EEA). In such cases:
|
||||||
|
</p>
|
||||||
|
<ul class="privacy-list">
|
||||||
|
<li>We ensure adequate protection through approved mechanisms (adequacy decisions, standard contractual clauses)</li>
|
||||||
|
<li>Discord Inc. (USA) is covered by appropriate safeguards for international transfers</li>
|
||||||
|
<li>We minimize data transfers outside the EEA where possible</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="privacy-section">
|
<div class="privacy-section">
|
||||||
@@ -325,18 +360,37 @@
|
|||||||
7. Your Rights and Choices
|
7. Your Rights and Choices
|
||||||
</h2>
|
</h2>
|
||||||
<p class="privacy-text">
|
<p class="privacy-text">
|
||||||
You have the following rights regarding your personal information:
|
Under the General Data Protection Regulation (GDPR), you have the following rights regarding your personal information:
|
||||||
</p>
|
</p>
|
||||||
<ul class="privacy-list">
|
|
||||||
<li><strong>Access:</strong> Request information about what personal data we have about you</li>
|
<div class="data-type">
|
||||||
<li><strong>Correction:</strong> Request correction of inaccurate or incomplete data</li>
|
<h4><i class="fas fa-user-shield"></i> <span class="highlight">Your GDPR Rights</span></h4>
|
||||||
<li><strong>Deletion:</strong> Request deletion of your personal data (subject to retention requirements)</li>
|
<ul class="privacy-list">
|
||||||
<li><strong>Portability:</strong> Request a copy of your data in a machine-readable format</li>
|
<li><strong>Right of Access (Art. 15):</strong> Request information about what personal data we have about you</li>
|
||||||
<li><strong>Objection:</strong> Object to processing of your data for certain purposes</li>
|
<li><strong>Right to Rectification (Art. 16):</strong> Request correction of inaccurate or incomplete data</li>
|
||||||
<li><strong>Restriction:</strong> Request restriction of processing under certain circumstances</li>
|
<li><strong>Right to Erasure (Art. 17):</strong> Request deletion of your personal data ("right to be forgotten")</li>
|
||||||
</ul>
|
<li><strong>Right to Restrict Processing (Art. 18):</strong> Request restriction of processing under certain circumstances</li>
|
||||||
|
<li><strong>Right to Data Portability (Art. 20):</strong> Request a copy of your data in a machine-readable format</li>
|
||||||
|
<li><strong>Right to Object (Art. 21):</strong> Object to processing of your data for certain purposes</li>
|
||||||
|
<li><strong>Right to Withdraw Consent (Art. 7):</strong> Withdraw consent at any time where processing is based on consent</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="retention-period">
|
||||||
|
<h4><i class="fas fa-clock"></i> How to Exercise Your Rights</h4>
|
||||||
|
<p class="privacy-text">
|
||||||
|
To exercise these rights, please contact us through our <a href="{{ url_for('contact') }}" style="color: #667eea;">contact form</a>
|
||||||
|
or Discord (@simolzimol). We will respond to your request within 30 days as required by GDPR.
|
||||||
|
</p>
|
||||||
|
<p class="privacy-text">
|
||||||
|
<strong>Right to Lodge a Complaint:</strong> You have the right to lodge a complaint with your local data protection authority
|
||||||
|
if you believe we have not adequately addressed your concerns.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="privacy-text">
|
<p class="privacy-text">
|
||||||
To exercise these rights, please contact us through our <a href="{{ url_for('contact') }}" style="color: #667eea;">contact form</a>.
|
<strong>Note:</strong> Some rights may be limited by applicable law or necessary for the legitimate operation of our service.
|
||||||
|
We will inform you of any such limitations when responding to your request.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -379,19 +433,45 @@
|
|||||||
10. Changes to This Privacy Policy
|
10. Changes to This Privacy Policy
|
||||||
</h2>
|
</h2>
|
||||||
<p class="privacy-text">
|
<p class="privacy-text">
|
||||||
We may update this Privacy Policy from time to time. We will notify you of any material changes by:
|
We may update this Privacy Policy from time to time to reflect changes in our practices or applicable law. We will notify you of any material changes by:
|
||||||
</p>
|
</p>
|
||||||
<ul class="privacy-list">
|
<ul class="privacy-list">
|
||||||
<li>Posting the new Privacy Policy on this page</li>
|
<li>Posting the new Privacy Policy on this page with an updated "Last Updated" date</li>
|
||||||
<li>Updating the "Last Updated" date at the top of this policy</li>
|
<li>Providing prominent notice through our service for significant changes</li>
|
||||||
<li>Providing notice through our service or other communication methods</li>
|
<li>Sending direct notification where we have your contact information (for material changes affecting your rights)</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="privacy-text">
|
<p class="privacy-text">
|
||||||
Your continued use of the Service after the effective date of the revised Privacy Policy
|
Your continued use of the Service after the effective date of the revised Privacy Policy
|
||||||
constitutes acceptance of the revised policy.
|
constitutes acceptance of the revised policy. If you do not agree to the changes, please stop using our service.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="privacy-section">
|
||||||
|
<h2 class="section-title">
|
||||||
|
<i class="fas fa-balance-scale section-icon"></i>
|
||||||
|
11. Legal Compliance and Supervisory Authority
|
||||||
|
</h2>
|
||||||
|
<div class="data-type">
|
||||||
|
<h4><i class="fas fa-gavel"></i> <span class="highlight">GDPR Compliance</span></h4>
|
||||||
|
<p class="privacy-text">
|
||||||
|
This service is operated in compliance with the General Data Protection Regulation (EU) 2016/679.
|
||||||
|
As a service primarily targeting EU users, we adhere to GDPR requirements regardless of your location.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="retention-period">
|
||||||
|
<h4><i class="fas fa-shield-alt"></i> Supervisory Authority Contact</h4>
|
||||||
|
<p class="privacy-text">
|
||||||
|
If you believe we have not adequately addressed your data protection concerns, you have the right to lodge a complaint with:
|
||||||
|
</p>
|
||||||
|
<ul class="privacy-list">
|
||||||
|
<li><strong>Your local data protection authority</strong> in your EU member state</li>
|
||||||
|
<li><strong>The lead supervisory authority</strong> where our main establishment is located</li>
|
||||||
|
<li>You can find your local DPA contact information at: <a href="https://edpb.europa.eu/about-edpb/about-edpb/members_en" target="_blank" style="color: #667eea;">European Data Protection Board</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="contact-info">
|
<div class="contact-info">
|
||||||
<h2 class="section-title">
|
<h2 class="section-title">
|
||||||
<i class="fas fa-envelope section-icon"></i>
|
<i class="fas fa-envelope section-icon"></i>
|
||||||
|
|||||||
407
templates/user_contact.html
Normal file
407
templates/user_contact.html
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Contact Support - Multus Bot</title>
|
||||||
|
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #0c1426 0%, #1a1f2e 25%, #2d3748 75%, #0c1426 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-container {
|
||||||
|
background: rgba(26, 31, 46, 0.9);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
border: 1px solid rgba(102, 126, 234, 0.2);
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 3rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
max-width: 900px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
border-bottom: 1px solid rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-title {
|
||||||
|
color: #f8fafc;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-icon {
|
||||||
|
color: #667eea;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-subtitle {
|
||||||
|
color: #a0aec0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-card {
|
||||||
|
background: rgba(45, 55, 72, 0.6);
|
||||||
|
border: 1px solid rgba(102, 126, 234, 0.2);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-details h5 {
|
||||||
|
color: #f8fafc;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-details p {
|
||||||
|
color: #a0aec0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
background: rgba(45, 55, 72, 0.7);
|
||||||
|
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
background: rgba(45, 55, 72, 0.9);
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.form-control {
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-option {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: rgba(45, 55, 72, 0.7);
|
||||||
|
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-option:hover {
|
||||||
|
border-color: #667eea;
|
||||||
|
background: rgba(45, 55, 72, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-option.selected {
|
||||||
|
background: rgba(102, 126, 234, 0.2);
|
||||||
|
border-color: #667eea;
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-low { border-left: 4px solid #48bb78; }
|
||||||
|
.priority-medium { border-left: 4px solid #ed8936; }
|
||||||
|
.priority-high { border-left: 4px solid #f56565; }
|
||||||
|
.priority-urgent { border-left: 4px solid #e53e3e; }
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section {
|
||||||
|
background: rgba(72, 187, 120, 0.1);
|
||||||
|
border: 1px solid rgba(72, 187, 120, 0.3);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section h6 {
|
||||||
|
color: #48bb78;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: rgba(72, 187, 120, 0.2);
|
||||||
|
border: 1px solid rgba(72, 187, 120, 0.4);
|
||||||
|
border-radius: 15px;
|
||||||
|
color: #48bb78;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
background: rgba(245, 101, 101, 0.2);
|
||||||
|
border: 1px solid rgba(245, 101, 101, 0.4);
|
||||||
|
border-radius: 15px;
|
||||||
|
color: #f56565;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.contact-container {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-selector {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% include 'navigation.html' %}
|
||||||
|
|
||||||
|
<div class="container-fluid" style="flex: 1;">
|
||||||
|
<div class="contact-container">
|
||||||
|
<div class="contact-header">
|
||||||
|
<h1 class="contact-title">
|
||||||
|
<i class="fas fa-headset contact-icon"></i>
|
||||||
|
Contact Support
|
||||||
|
</h1>
|
||||||
|
<p class="contact-subtitle">Need help? Send us a message and we'll get back to you soon!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if user_info %}
|
||||||
|
<div class="user-info-card">
|
||||||
|
<img src="{{ user_info.avatar_url }}" alt="Avatar" class="user-avatar">
|
||||||
|
<div class="user-details">
|
||||||
|
<h5>{{ user_info.global_name or user_info.username }}</h5>
|
||||||
|
<p><i class="fab fa-discord"></i> {{ user_info.username }}#{{ user_info.discriminator }} (ID: {{ user_info.id }})</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<div class="alert alert-{{ 'success' if category == 'success' else 'danger' }}">
|
||||||
|
<i class="fas fa-{{ 'check-circle' if category == 'success' else 'exclamation-triangle' }}"></i>
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div class="info-section">
|
||||||
|
<h6><i class="fas fa-info-circle"></i> How it works</h6>
|
||||||
|
<ul style="color: #a0aec0; margin: 0; padding-left: 1.5rem;">
|
||||||
|
<li>Your Discord information is automatically included with your message</li>
|
||||||
|
<li>Messages are sent directly to the bot administrator via Discord DM</li>
|
||||||
|
<li>We typically respond within 24 hours</li>
|
||||||
|
<li>For urgent issues, please join our Discord server for faster support</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" action="{{ url_for('user_contact') }}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="subject">
|
||||||
|
<i class="fas fa-tag"></i> Subject
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="subject" name="subject"
|
||||||
|
placeholder="Brief description of your issue" required maxlength="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="category">
|
||||||
|
<i class="fas fa-folder"></i> Category
|
||||||
|
</label>
|
||||||
|
<select class="form-control" id="category" name="category" required>
|
||||||
|
<option value="">Select a category</option>
|
||||||
|
<option value="bug_report">🐛 Bug Report</option>
|
||||||
|
<option value="feature_request">💡 Feature Request</option>
|
||||||
|
<option value="account_issue">👤 Account Issue</option>
|
||||||
|
<option value="moderation">🛡️ Moderation Help</option>
|
||||||
|
<option value="giveaway">🎁 Giveaway Support</option>
|
||||||
|
<option value="privacy">🔒 Privacy/Data Request</option>
|
||||||
|
<option value="technical">⚙️ Technical Support</option>
|
||||||
|
<option value="other">❓ Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="priority">
|
||||||
|
<i class="fas fa-exclamation-circle"></i> Priority Level
|
||||||
|
</label>
|
||||||
|
<div class="priority-selector">
|
||||||
|
<div class="priority-option priority-low" onclick="selectPriority('low', this)">
|
||||||
|
<i class="fas fa-circle" style="color: #48bb78;"></i><br>
|
||||||
|
<strong>Low</strong><br>
|
||||||
|
<small>General inquiry</small>
|
||||||
|
</div>
|
||||||
|
<div class="priority-option priority-medium" onclick="selectPriority('medium', this)">
|
||||||
|
<i class="fas fa-circle" style="color: #ed8936;"></i><br>
|
||||||
|
<strong>Medium</strong><br>
|
||||||
|
<small>Need assistance</small>
|
||||||
|
</div>
|
||||||
|
<div class="priority-option priority-high" onclick="selectPriority('high', this)">
|
||||||
|
<i class="fas fa-circle" style="color: #f56565;"></i><br>
|
||||||
|
<strong>High</strong><br>
|
||||||
|
<small>Important issue</small>
|
||||||
|
</div>
|
||||||
|
<div class="priority-option priority-urgent" onclick="selectPriority('urgent', this)">
|
||||||
|
<i class="fas fa-circle" style="color: #e53e3e;"></i><br>
|
||||||
|
<strong>Urgent</strong><br>
|
||||||
|
<small>Critical problem</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="priority" name="priority" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="message">
|
||||||
|
<i class="fas fa-comment-alt"></i> Message
|
||||||
|
</label>
|
||||||
|
<textarea class="form-control" id="message" name="message" rows="6"
|
||||||
|
placeholder="Please provide detailed information about your issue or request..."
|
||||||
|
required maxlength="2000"></textarea>
|
||||||
|
<small class="text-muted">Maximum 2000 characters</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="server_context">
|
||||||
|
<i class="fas fa-server"></i> Server Context (Optional)
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control" id="server_context" name="server_context"
|
||||||
|
placeholder="If your issue is related to a specific server, mention it here" maxlength="200">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<button type="submit" class="submit-btn">
|
||||||
|
<i class="fas fa-paper-plane"></i>
|
||||||
|
Send Message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include 'footer.html' %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function selectPriority(priority, element) {
|
||||||
|
// Remove selected class from all options
|
||||||
|
document.querySelectorAll('.priority-option').forEach(option => {
|
||||||
|
option.classList.remove('selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add selected class to clicked option
|
||||||
|
element.classList.add('selected');
|
||||||
|
|
||||||
|
// Set hidden input value
|
||||||
|
document.getElementById('priority').value = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Character counter for message
|
||||||
|
document.getElementById('message').addEventListener('input', function() {
|
||||||
|
const maxLength = 2000;
|
||||||
|
const currentLength = this.value.length;
|
||||||
|
const remaining = maxLength - currentLength;
|
||||||
|
|
||||||
|
// You can add a character counter here if desired
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
document.querySelector('form').addEventListener('submit', function(e) {
|
||||||
|
const priority = document.getElementById('priority').value;
|
||||||
|
if (!priority) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('Please select a priority level.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user