diff --git a/scripts/fix_xp.py b/scripts/fix_xp.py new file mode 100644 index 0000000..995591f --- /dev/null +++ b/scripts/fix_xp.py @@ -0,0 +1,66 @@ +import sqlite3 +import math +import os + +db_path = "data/stats.db" + +def calculate_reasonable_xp(messages, voice_mins): + # Durschnittlich 3 XP pro Nachricht + 0.5 XP pro Voice Minute + return int((messages * 3) + (voice_mins * 0.5)) + +def calculate_level(xp): + """Neue Formel: Level = (XP/50)^(2/3) + 1""" + if xp < 50: return 1 + return int((xp / 50) ** (2/3)) + 1 + +def main(): + if not os.path.exists(db_path): + print(f"Datenbank {db_path} wurde nicht gefunden.") + return + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + print("Überprüfe global_user_levels auf Anomalien (Neue Formel angewendet)...") + + # Finde User mit mehr als 100.000 XP (für monatlichen Zyklus sehr hoch) + cursor.execute(''' + SELECT user_id, global_level, global_xp, total_messages, total_voice_minutes + FROM global_user_levels + WHERE global_xp > 100000 + ''') + + anomalies = cursor.fetchall() + + if not anomalies: + print("Keine extremen XP-Anomalien (> 100k XP) gefunden.") + print("Möchtest du trotzdem ALLE User auf die neue Formel und realistische Werte korrigieren? (j/n)") + choice = input().lower() + if choice == 'j': + cursor.execute('SELECT user_id, global_level, global_xp, total_messages, total_voice_minutes FROM global_user_levels') + anomalies = cursor.fetchall() + print(f"Korrigiere {len(anomalies)} User...") + else: + conn.close() + return + else: + print(f"{len(anomalies)} extreme Anomalien gefunden.") + + for user_id, level, xp, msgs, voice in anomalies: + new_xp = calculate_reasonable_xp(msgs, voice) + new_level = calculate_level(new_xp) + + print(f"User {user_id}: {int(xp):,} XP (lvl {level}) -> {new_xp:,} XP (lvl {new_level}) [Nachrichten: {msgs}, Voice: {voice}m]") + + cursor.execute(''' + UPDATE global_user_levels + SET global_xp = ?, global_level = ? + WHERE user_id = ? + ''', (new_xp, new_level, user_id)) + + conn.commit() + conn.close() + print("\nFertig! Die Datenbank wurde bereinigt und an die neue Formel angepasst.") + +if __name__ == "__main__": + main() diff --git a/src/api/dashboard/routes.py b/src/api/dashboard/routes.py index ab5d40a..9c0c4ed 100644 --- a/src/api/dashboard/routes.py +++ b/src/api/dashboard/routes.py @@ -46,7 +46,7 @@ async def get_stats(request: Request): try: # Berechne Uptime (in Sekunden seit dem letzten Ready-Event) - uptime_seconds = (datetime.utcnow() - bot_instance.start_time).total_seconds() if hasattr(bot_instance, 'start_time') else 0 + uptime_seconds = (discord.utils.utcnow() - bot_instance.start_time).total_seconds() if hasattr(bot_instance, 'start_time') else 0 uptime_minutes, remainder = divmod(int(uptime_seconds), 60) uptime_hours, uptime_minutes = divmod(uptime_minutes, 60) uptime_days, uptime_hours = divmod(uptime_hours, 24) @@ -85,14 +85,15 @@ async def get_leaderboard(limit: int = 50): leaderboard = [] for row in rows: uid = row[0] - # Try to get user from cache first - user = bot_instance.get_user(uid) + is_private = row[5] if len(row) > 5 else 0 - # If not in cache, we don't fetch_user here to avoid hitting rate limits - # and slowing down the request significantly for 50 users. - - username = user.name if user else f"User {uid}" - avatar = user.display_avatar.url if user else None + if is_private: + username = "Anonymer Nutzer" + avatar = None + else: + user = bot_instance.get_user(uid) + username = user.name if user else f"User {uid}" + avatar = user.display_avatar.url if user else None leaderboard.append({ "user_id": str(uid), diff --git a/src/api/dashboard/user_routes.py b/src/api/dashboard/user_routes.py index bdcb50b..ca14f26 100644 --- a/src/api/dashboard/user_routes.py +++ b/src/api/dashboard/user_routes.py @@ -114,7 +114,8 @@ async def get_user_settings(user: dict = Depends(get_current_user)): "global_coins": EconomyDatabase().get_global_balance(user_id), "overrides": EconomyDatabase().get_equipped_overrides(user_id) }, - "top_servers": top_servers + "top_servers": top_servers, + "is_private": global_info.get('is_private', 0) if global_info else 0 } } except Exception as e: @@ -131,6 +132,16 @@ async def update_user_settings(request: Request, user: dict = Depends(get_curren # Update language in SettingsDB if provided if "language" in data: settings_db.set_user_language(user_id, data["language"]) + + # Update privacy in StatsDB if provided + if "is_private" in data: + stats_db = StatsDB() + async with stats_db.lock: + stats_db.cursor.execute( + "UPDATE global_user_levels SET is_private = ? WHERE user_id = ?", + (1 if data["is_private"] else 0, user_id) + ) + stats_db.conn.commit() return {"success": True} except Exception as e: diff --git a/src/web/dashboard/UserSettingsPage.tsx b/src/web/dashboard/UserSettingsPage.tsx index 5328456..9c3b276 100644 --- a/src/web/dashboard/UserSettingsPage.tsx +++ b/src/web/dashboard/UserSettingsPage.tsx @@ -49,7 +49,8 @@ export default function UserSettingsPage() { globalStats: null as any, moderation: null as any, globalChat: null as any, - topServers: [] as any[] + topServers: [] as any[], + is_private: false }); useEffect(() => { @@ -71,7 +72,8 @@ export default function UserSettingsPage() { globalStats: data.data.global_stats, moderation: data.data.moderation, globalChat: data.data.global_chat, - topServers: data.data.top_servers || [] + topServers: data.data.top_servers || [], + is_private: data.data.is_private === 1 }); } } @@ -96,7 +98,10 @@ export default function UserSettingsPage() { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, - body: JSON.stringify({ language: settings.language }) + body: JSON.stringify({ + language: settings.language, + is_private: settings.is_private + }) }); if (res.ok) { @@ -429,15 +434,32 @@ export default function UserSettingsPage() {
Wähle deine bevorzugte Sprache für das Dashboard.
- + + + ++ Wenn aktiviert, werden dein Name und dein Profilbild auf dem globalen Leaderboard anonymisiert. +
+