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:
SimolZimol
2025-08-24 22:42:16 +02:00
parent 52739bf189
commit ae57673029
5 changed files with 999 additions and 19 deletions

117
app.py
View File

@@ -898,6 +898,123 @@ def terms_of_service():
"""Zeigt die Terms of Service Seite an."""
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")
def privacy_policy():
"""Zeigt die Privacy Policy Seite an."""

371
bot.py
View File

@@ -687,6 +687,81 @@ async def process_manager():
except Exception as 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):
"""Handles different types of expired processes"""
try:
@@ -1775,6 +1850,11 @@ async def on_ready():
if not process_manager.is_running():
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(f"Logged in as: {client.user.name}")
logger.info(f"Client ID: {client.user.id}")
@@ -2883,6 +2963,137 @@ def create_warnings_table():
if 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):
"""Retrieves warning records for a user
@@ -5263,9 +5474,169 @@ async def delnotes(ctx):
else:
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:
# Initialize database tables
create_warnings_table()
create_contact_messages_table()
logger.info("Database tables initialized successfully")
loop.run_until_complete(client.start(TOKEN))

View File

@@ -52,6 +52,11 @@
<i class="fas fa-server"></i> Server Selection
</a>
</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 -->
{% if g.is_admin %}
<li class="nav-item">

View File

@@ -194,6 +194,15 @@
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.
</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 class="privacy-section">
@@ -248,6 +257,20 @@
<i class="fas fa-cogs section-icon"></i>
3. How We Use Your Information
</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">
We use the collected information for the following purposes:
</p>
@@ -255,7 +278,7 @@
<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>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>Security:</strong> To detect and prevent abuse, spam, and security threats</li>
</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:
</p>
<ul class="privacy-list">
<li><strong>Discord Platform:</strong> As required for bot functionality through Discord's API</li>
<li><strong>Server Administrators:</strong> Moderation data may be visible to server moderators and administrators</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 within your Discord server</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>
</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 class="privacy-section">
@@ -325,18 +360,37 @@
7. Your Rights and Choices
</h2>
<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>
<ul class="privacy-list">
<li><strong>Access:</strong> Request information about what personal data we have about you</li>
<li><strong>Correction:</strong> Request correction of inaccurate or incomplete data</li>
<li><strong>Deletion:</strong> Request deletion of your personal data (subject to retention requirements)</li>
<li><strong>Portability:</strong> Request a copy of your data in a machine-readable format</li>
<li><strong>Objection:</strong> Object to processing of your data for certain purposes</li>
<li><strong>Restriction:</strong> Request restriction of processing under certain circumstances</li>
</ul>
<div class="data-type">
<h4><i class="fas fa-user-shield"></i> <span class="highlight">Your GDPR Rights</span></h4>
<ul class="privacy-list">
<li><strong>Right of Access (Art. 15):</strong> Request information about what personal data we have about you</li>
<li><strong>Right to Rectification (Art. 16):</strong> Request correction of inaccurate or incomplete data</li>
<li><strong>Right to Erasure (Art. 17):</strong> Request deletion of your personal data ("right to be forgotten")</li>
<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">
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>
</div>
@@ -379,19 +433,45 @@
10. Changes to This Privacy Policy
</h2>
<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>
<ul class="privacy-list">
<li>Posting the new Privacy Policy on this page</li>
<li>Updating the "Last Updated" date at the top of this policy</li>
<li>Providing notice through our service or other communication methods</li>
<li>Posting the new Privacy Policy on this page with an updated "Last Updated" date</li>
<li>Providing prominent notice through our service for significant changes</li>
<li>Sending direct notification where we have your contact information (for material changes affecting your rights)</li>
</ul>
<p class="privacy-text">
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>
</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">
<h2 class="section-title">
<i class="fas fa-envelope section-icon"></i>

407
templates/user_contact.html Normal file
View 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>