diff --git a/README.md b/README.md
index 865ea74..be5a8c3 100644
--- a/README.md
+++ b/README.md
@@ -138,7 +138,7 @@ python main.py
Brauchst du Hilfe oder möchtest du das Projekt unterstützen?
-[](https://discord.gg/oppro)
+[](https://discord.gg/9T28DWup3g)
[](https://github.com/Oppro-net-Development/ManagerX/issues)
diff --git a/config/example.env b/config/example.env
index b2561c8..e404e7d 100644
--- a/config/example.env
+++ b/config/example.env
@@ -6,6 +6,12 @@ DISCORD_CLIENT_ID=12345678900
DISCORD_CLIENT_SECRET=abc123
DISCORD_REDIRECT_URI=https://
-URL=https://
+URL=https://my-super-discord-bot.com
-DASHBOARD_API_KEYS=abc123
\ No newline at end of file
+DASHBOARD_API_KEYS=abc123
+
+JWT_SECRET=abc123
+DASHBOARD_URL=https://my-super-discord-bot.com
+VITE_API_URL=https://api.my-super-discord-bot.com
+
+TOPGG_TOKEN=abc123
\ No newline at end of file
diff --git a/main.py b/main.py
index a77f79e..46df1c9 100644
--- a/main.py
+++ b/main.py
@@ -142,10 +142,6 @@ async def on_ready():
)
)
- # Commands sync
- await bot.sync_commands()
- logger.success("COMMANDS", "Application Commands synchronisiert")
-
# --- LIMIT CHECK START ---
all_cmds = bot.pending_application_commands
# Wir zählen nur die echten Top-Level Slash Commands (Slots)
@@ -155,6 +151,15 @@ async def on_ready():
logger.info("LIMITS", f"Discord-API Slots belegt: {len(root_slots)} / 100")
# --- LIMIT CHECK ENDE ---
+ @bot.event
+ async def on_application_command_completion(ctx: discord.ApplicationContext):
+ """Track command usage across all guilds."""
+ if ctx.guild and hasattr(bot, 'stats_db'):
+ try:
+ await bot.stats_db.log_command(ctx.guild.id, ctx.command.qualified_name)
+ except Exception as e:
+ logger.error("STATS", f"Fehler beim Loggen des Commands: {e}")
+
# Minimaler KeepAlive Cog
class KeepAlive(discord.ext.commands.Cog):
def __init__(self, bot):
diff --git a/src/bot/cogs/bot/admin.py b/src/bot/cogs/bot/admin.py
index 4c05b37..09b36cc 100644
--- a/src/bot/cogs/bot/admin.py
+++ b/src/bot/cogs/bot/admin.py
@@ -21,23 +21,7 @@
AUDIT_LOG_FILE = Path("data/admin_audit.json")
BLACKLIST_FILE = Path("data/blacklist.json")
-class ConfirmView(View):
- """Bestätigungsdialog für kritische Aktionen"""
- def __init__(self, timeout=30):
- super().__init__(timeout=timeout)
- self.value = None
- @discord.ui.button(label="✅ Bestätigen", style=discord.ButtonStyle.danger)
- async def confirm(self, button: discord.ui.Button, interaction: discord.Interaction):
- self.value = True
- self.stop()
- await interaction.response.defer()
-
- @discord.ui.button(label="❌ Abbrechen", style=discord.ButtonStyle.secondary)
- async def cancel(self, button: discord.ui.Button, interaction: discord.Interaction):
- self.value = False
- self.stop()
- await interaction.response.defer()
class ServerListView:
@@ -322,6 +306,34 @@ async def cog_check(self, ctx):
return True
+ async def request_confirmation(self, ctx: discord.ApplicationContext, container: Container, timeout: int = 30) -> bool:
+ """Helper to create a confirmation dialog with buttons and a designer container."""
+ view = discord.ui.DesignerView(container, timeout=timeout)
+ view.value = None
+
+ async def confirm_callback(interaction: discord.Interaction):
+ view.value = True
+ view.stop()
+ await interaction.response.defer()
+
+ async def cancel_callback(interaction: discord.Interaction):
+ view.value = False
+ view.stop()
+ await interaction.response.defer()
+
+ confirm_btn = Button(label="✅ Bestätigen", style=discord.ButtonStyle.danger)
+ confirm_btn.callback = confirm_callback
+
+ cancel_btn = Button(label="❌ Abbrechen", style=discord.ButtonStyle.secondary)
+ cancel_btn.callback = cancel_callback
+
+ view.add_item(confirm_btn)
+ view.add_item(cancel_btn)
+
+ await ctx.respond(view=view, ephemeral=True)
+ await view.wait()
+ return view.value if view.value is not None else False
+
def log_command(self, ctx):
"""Loggt Admin-Commands"""
AUDIT_LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
@@ -414,11 +426,7 @@ async def shutdown(self, ctx: discord.ApplicationContext):
container.add_text("# ⚠️ Shutdown bestätigen")
container.add_text("Bist du sicher, dass du den Bot herunterfahren möchtest?")
- view = ConfirmView()
- await ctx.respond(view=discord.ui.DesignerView(container, timeout=30), ephemeral=True)
- await view.wait()
-
- if view.value:
+ if await self.request_confirmation(ctx, container):
container = Container(color=discord.Color.red())
container.add_text("# ⚠️ ManagerX wird heruntergefahren...")
container.add_separator()
@@ -437,11 +445,7 @@ async def restart(self, ctx: discord.ApplicationContext):
container.add_text("# ⚠️ Restart bestätigen")
container.add_text("Bist du sicher, dass du den Bot neustarten möchtest?")
- view = ConfirmView()
- await ctx.respond(view=discord.ui.DesignerView(container, timeout=30), ephemeral=True)
- await view.wait()
-
- if view.value:
+ if await self.request_confirmation(ctx, container):
container = Container(color=discord.Color.orange())
container.add_text("# 🔄 ManagerX wird neugestartet...")
container.add_separator()
@@ -804,11 +808,7 @@ async def reload_all(self, ctx: discord.ApplicationContext):
container.add_text("# ⚠️ Reload All bestätigen")
container.add_text("Alle Cogs (außer Admin) werden neu geladen. Fortfahren?")
- view = ConfirmView()
- await ctx.respond(view=discord.ui.DesignerView(container, timeout=30), ephemeral=True)
- await view.wait()
-
- if not view.value:
+ if not await self.request_confirmation(ctx, container):
container = Container(color=discord.Color.green())
container.add_text("## ✅ Reload abgebrochen")
await ctx.edit(view=discord.ui.DesignerView(container, timeout=0))
@@ -924,11 +924,7 @@ async def leave_server(
container.add_text(f"**ID:** `{guild_id}`")
container.add_text(f"**Mitglieder:** {guild.member_count:,}")
- view = ConfirmView()
- await ctx.respond(view=discord.ui.DesignerView(container, timeout=30), ephemeral=True)
- await view.wait()
-
- if view.value:
+ if await self.request_confirmation(ctx, container):
await guild.leave()
container = Container(color=discord.Color.green())
@@ -1075,6 +1071,26 @@ async def server_info(
container.add_text("## ✨ Features")
container.add_text(features_text)
+ container.add_separator()
+
+ # Befehls-Statistiken (NEU)
+ if hasattr(self.bot, "stats_db"):
+ guild_top = await self.bot.stats_db.get_top_commands(guild.id, 3)
+ global_top = await self.bot.stats_db.get_global_top_commands(3)
+
+ container.add_text("## 📊 Top 3 Befehle")
+
+ if guild_top:
+ gt_text = "\n".join([f"• `/{name}` ({count}x)" for name, count in guild_top])
+ container.add_text(f"**Dieser Server:**\n{gt_text}")
+ else:
+ container.add_text("**Dieser Server:** Keine Daten")
+
+ if global_top:
+ glob_text = "\n".join([f"• `/{name}` ({count}x)" for name, count in global_top])
+ container.add_text(f"**Global:**\n{glob_text}")
+ else:
+ container.add_text("**Global:** Keine Daten")
await ctx.respond(view=discord.ui.DesignerView(container, timeout=0), ephemeral=True)
@@ -1374,11 +1390,7 @@ async def clear_logs(self, ctx: discord.ApplicationContext):
container.add_text("# ⚠️ Logs löschen bestätigen")
container.add_text("Alle Admin-Logs werden permanent gelöscht!")
- view = ConfirmView()
- await ctx.respond(view=discord.ui.DesignerView(container, timeout=30), ephemeral=True)
- await view.wait()
-
- if view.value:
+ if await self.request_confirmation(ctx, container):
if AUDIT_LOG_FILE.exists():
AUDIT_LOG_FILE.unlink()
diff --git a/src/bot/cogs/user/stats.py b/src/bot/cogs/user/stats.py
index 10f78a7..cef5649 100644
--- a/src/bot/cogs/user/stats.py
+++ b/src/bot/cogs/user/stats.py
@@ -21,8 +21,9 @@ class EnhancedStatsCog(ezcord.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
- self.db = StatsDB()
- self.bot.stats_db = self.db
+ self.db = getattr(bot, "stats_db", None) or StatsDB()
+ if not hasattr(bot, "stats_db"):
+ bot.stats_db = self.db
self.level_db = LevelDatabase()
self.cleanup_task.start()
self.monthly_reset_task.start()
diff --git a/src/bot/core/bot_setup.py b/src/bot/core/bot_setup.py
index 85df90a..bf96fff 100644
--- a/src/bot/core/bot_setup.py
+++ b/src/bot/core/bot_setup.py
@@ -65,7 +65,7 @@ def create_bot(self) -> ezcord.Bot:
name="🔗 **Important Links**",
value=(
"🌐 [**Website**](https://managerx-bot.de) • "
- "🚑 [**Support**](https://discord.gg/SrcE6zJZ) • "
+ "🚑 [**Support**](https://discord.gg/9T28DWup3g) • "
"💻 [**GitHub**](https://github.com/ManagerX-Development/ManagerX)"
),
inline=False
diff --git a/src/bot/core/database.py b/src/bot/core/database.py
index 6050f3c..eb86ef3 100644
--- a/src/bot/core/database.py
+++ b/src/bot/core/database.py
@@ -9,10 +9,11 @@
from logger import logger, Category
try:
- from mx_devtools import SettingsDB
+ from mx_devtools import SettingsDB, StatsDB
except ImportError as e:
- logger.critical(Category.DATABASE, f"SettingsDB Import fehlgeschlagen: {e}")
+ logger.critical(Category.DATABASE, f"Database Imports fehlgeschlagen: {e}")
SettingsDB = None
+ StatsDB = None
class DatabaseManager:
"""Verwaltet die Datenbank-Initialisierung"""
@@ -38,6 +39,11 @@ def initialize(self, bot) -> bool:
self.db = SettingsDB()
bot.settings_db = self.db
logger.success(Category.DATABASE, "Settings Database initialized ✓")
+
+ if StatsDB:
+ bot.stats_db = StatsDB()
+ logger.success(Category.DATABASE, "Stats Database initialized ✓")
+
return True
except Exception as e:
diff --git a/scripts/fix_xp.py b/src/scripts/fix_xp.py
similarity index 100%
rename from scripts/fix_xp.py
rename to src/scripts/fix_xp.py
diff --git a/src/web/components/CTA.tsx b/src/web/components/CTA.tsx
index d3880b7..364abd2 100644
--- a/src/web/components/CTA.tsx
+++ b/src/web/components/CTA.tsx
@@ -77,7 +77,7 @@ export const CTA = memo(function CTA() {