Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions scripts/fix_xp.py
Original file line number Diff line number Diff line change
@@ -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()
17 changes: 9 additions & 8 deletions src/api/dashboard/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand Down
13 changes: 12 additions & 1 deletion src/api/dashboard/user_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
42 changes: 32 additions & 10 deletions src/web/dashboard/UserSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -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
});
}
}
Expand All @@ -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) {
Expand Down Expand Up @@ -429,15 +434,32 @@ export default function UserSettingsPage() {
<Label className="text-lg font-bold">System Sprache</Label>
<p className="text-sm text-muted-foreground group-hover:text-muted-foreground/80 transition-colors">Wähle deine bevorzugte Sprache für das Dashboard.</p>
</div>
<select
className="bg-white/5 border border-white/10 rounded-xl px-6 h-12 text-white outline-none cursor-pointer hover:bg-white/10 transition-all font-bold"
value={settings.language}
onChange={(e) => setSettings({ ...settings, language: e.target.value })}
>
<option value="de" className="bg-[#1a1c1e]">Deutsch 🇩🇪</option>
<option value="en" className="bg-[#1a1c1e]">English 🇬🇧</option>
</select>
</div>

<div className="p-px bg-gradient-to-r from-transparent via-white/10 to-transparent" />

<div className="flex items-center justify-between group">
<div className="space-y-1">
<div className="flex items-center gap-2">
<Shield className="w-4 h-4 text-primary" />
<Label className="text-lg font-bold">Privatsphäre bei Leaderboard</Label>
</div>
<p className="text-sm text-muted-foreground group-hover:text-muted-foreground/80 transition-colors">
Wenn aktiviert, werden dein Name und dein Profilbild auf dem globalen Leaderboard anonymisiert.
</p>
</div>
<div className="flex items-center gap-3">
<span className={cn("text-xs font-bold uppercase tracking-widest transition-colors", settings.is_private ? "text-primary" : "text-slate-500")}>
{settings.is_private ? "Anonym" : "Öffentlich"}
</span>
<Switch
checked={settings.is_private}
onCheckedChange={(val) => setSettings({ ...settings, is_private: val })}
className="data-[state=checked]:bg-primary"
/>
</div>
</div>
</CardContent>
</Card>
</div>
Expand Down
Loading