From ae5767302994c504641f611455e3236bcc70f76d Mon Sep 17 00:00:00 2001
From: SimolZimol <70102430+SimolZimol@users.noreply.github.com>
Date: Sun, 24 Aug 2025 22:42:16 +0200
Subject: [PATCH] modified: app.py modified: bot.py
modified: templates/navigation.html modified:
templates/privacy_policy.html new file: templates/user_contact.html
---
app.py | 117 ++++++++++
bot.py | 371 +++++++++++++++++++++++++++++++
templates/navigation.html | 5 +
templates/privacy_policy.html | 118 ++++++++--
templates/user_contact.html | 407 ++++++++++++++++++++++++++++++++++
5 files changed, 999 insertions(+), 19 deletions(-)
create mode 100644 templates/user_contact.html
diff --git a/app.py b/app.py
index f160afa..bc83c44 100644
--- a/app.py
+++ b/app.py
@@ -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:**
+**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."""
diff --git a/bot.py b/bot.py
index 81fe745..2c1c458 100644
--- a/bot.py
+++ b/bot.py
@@ -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""
+ 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:** ",
+ 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))
diff --git a/templates/navigation.html b/templates/navigation.html
index 751c6df..2bc83dc 100644
--- a/templates/navigation.html
+++ b/templates/navigation.html
@@ -52,6 +52,11 @@
Server Selection
+
+
+ Contact Support
+
+
{% if g.is_admin %}
diff --git a/templates/privacy_policy.html b/templates/privacy_policy.html
index c56ffa6..99e4ae8 100644
--- a/templates/privacy_policy.html
+++ b/templates/privacy_policy.html
@@ -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.
+
+
Data Controller Information
+
+ Data Controller: SimolZimol (Individual Developer)
+ Contact: Available through Discord (@simolzimol) or our contact form
+ Location: European Union
+ Legal Basis: Legitimate interest for service operation and user consent for data processing
+
+
@@ -248,6 +257,20 @@
3. How We Use Your Information
+
+ We process your personal data based on the following legal bases under GDPR:
+
+
+
+
Legal Basis for Processing
+
+ - Legitimate Interest (Art. 6(1)(f) GDPR): Service operation, security, and improvement
+ - Consent (Art. 6(1)(a) GDPR): Optional features and analytics (where applicable)
+ - Contract Performance (Art. 6(1)(b) GDPR): Providing the bot service you requested
+ - Legal Obligation (Art. 6(1)(c) GDPR): Compliance with applicable laws
+
+
+
We use the collected information for the following purposes:
@@ -255,7 +278,7 @@
Service Operation: To provide moderation, management, and administrative features
User Experience: To personalize your experience and maintain user preferences
Moderation: To enforce server rules and maintain community safety
-
Analytics: To understand usage patterns and improve service quality
+
Analytics: To understand usage patterns and improve service quality (anonymized where possible)
Communication: To send important updates and respond to support requests
Security: To detect and prevent abuse, spam, and security threats
@@ -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:
- - Discord Platform: As required for bot functionality through Discord's API
- - Server Administrators: Moderation data may be visible to server moderators and administrators
+ - Discord Platform: As required for bot functionality through Discord's API (Discord Inc., USA - adequate protection under Privacy Shield successor)
+ - Server Administrators: Moderation data may be visible to server moderators and administrators within your Discord server
- Legal Requirements: When required by law, court order, or government regulation
- - Service Providers: With trusted third-party services that help us operate (hosting, analytics)
+ - Service Providers: With trusted third-party services that help us operate (hosting providers within EU/EEA)
- Safety and Security: To protect the rights, property, or safety of our users or others
+
+
+
International Data Transfers
+
+ Some of our service providers may be located outside the European Economic Area (EEA). In such cases:
+
+
+ - We ensure adequate protection through approved mechanisms (adequacy decisions, standard contractual clauses)
+ - Discord Inc. (USA) is covered by appropriate safeguards for international transfers
+ - We minimize data transfers outside the EEA where possible
+
+
@@ -325,18 +360,37 @@
7. Your Rights and Choices
- 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:
-
- - Access: Request information about what personal data we have about you
- - Correction: Request correction of inaccurate or incomplete data
- - Deletion: Request deletion of your personal data (subject to retention requirements)
- - Portability: Request a copy of your data in a machine-readable format
- - Objection: Object to processing of your data for certain purposes
- - Restriction: Request restriction of processing under certain circumstances
-
+
+
+
Your GDPR Rights
+
+ - Right of Access (Art. 15): Request information about what personal data we have about you
+ - Right to Rectification (Art. 16): Request correction of inaccurate or incomplete data
+ - Right to Erasure (Art. 17): Request deletion of your personal data ("right to be forgotten")
+ - Right to Restrict Processing (Art. 18): Request restriction of processing under certain circumstances
+ - Right to Data Portability (Art. 20): Request a copy of your data in a machine-readable format
+ - Right to Object (Art. 21): Object to processing of your data for certain purposes
+ - Right to Withdraw Consent (Art. 7): Withdraw consent at any time where processing is based on consent
+
+
+
+
+
How to Exercise Your Rights
+
+ To exercise these rights, please contact us through our contact form
+ or Discord (@simolzimol). We will respond to your request within 30 days as required by GDPR.
+
+
+ Right to Lodge a Complaint: You have the right to lodge a complaint with your local data protection authority
+ if you believe we have not adequately addressed your concerns.
+
+
+
- To exercise these rights, please contact us through our contact form.
+ Note: 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.
@@ -379,19 +433,45 @@
10. Changes to This Privacy Policy
- 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:
- - Posting the new Privacy Policy on this page
- - Updating the "Last Updated" date at the top of this policy
- - Providing notice through our service or other communication methods
+ - Posting the new Privacy Policy on this page with an updated "Last Updated" date
+ - Providing prominent notice through our service for significant changes
+ - Sending direct notification where we have your contact information (for material changes affecting your rights)
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.
+
+
+
+ 11. Legal Compliance and Supervisory Authority
+
+
+
GDPR Compliance
+
+ 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.
+
+
+
+
+
Supervisory Authority Contact
+
+ If you believe we have not adequately addressed your data protection concerns, you have the right to lodge a complaint with:
+
+
+ - Your local data protection authority in your EU member state
+ - The lead supervisory authority where our main establishment is located
+ - You can find your local DPA contact information at: European Data Protection Board
+
+
+
+