Files
Lacie/xp/database.py
Lilac-Rose 971ec5c849 feat: mod logging overhaul, bug fixes, minesweeper fix, emote credits pending view
Moderation & logging:
- cleanban now collects all messages before banning and attaches them as a
  .txt audit log file via the member_ban log channel
- purgememberall restricted to server owner only (nuke protection)
- unban, lock, unlock, spam ban-button, and manual infraction deletes all
  now write to the DB infractions table and post to the mod log
- new log types (channel_lock, infraction_modify, cleanban) added and seeded
  to route to the main mod log channel (982644273960873994)

Bug fixes:
- unban: mod log no longer fires on failure (missing return added)
- mute: fixed DB connection leaks and silent unmute failures in
  schedule_unmute and check_mutes (try/finally + proper error logging)
- suggest: fixed AttributeError crash when original_embed is None and
  IndexError risk on set_field_at calls without field count guards
- ban/cleanban: removed isinstance(user, discord.User) guard that silently
  prevented DMs from ever being sent to in-server members
- cleanban: wrapped collect_status.delete() in try/except
- commands/infractions: fixed DB connection leak with try/finally
- events/botban: silent ban failure now logs error instead of swallowing it

Minesweeper:
- track detonated_cell on mine hit; show 💥 on the clicked mine and 💣
  on all others when the board is revealed on loss

Emote credits:
- new /emote_credits_pending command: shows the user their own unreviewed
  submissions; admins can pass all:True to see the full pending queue

Code quality:
- comprehensive docstrings and inline comments added across all cogs,
  events, commands, utils, and supporting modules
2026-06-16 15:01:11 +02:00

75 lines
2.3 KiB
Python

import sqlite3
from pathlib import Path
DB_DIR = Path(__file__).parent.parent / "data"
DB_DIR.mkdir(parents=True, exist_ok=True)
def get_db(db_type="lifetime"):
"""Open (or create) an XP database for the given leaderboard type and return (conn, cursor).
Accepts a boolean for backwards-compatibility: True → "lifetime", False → "annual".
Each leaderboard type has its own SQLite file (e.g. lifetime.db, weekly.db).
The daily/weekly/monthly databases also include a reset_log table that
records the last time the leaderboard was wiped, so the UI can display
'last reset X ago'.
Parameters
----------
db_type:
One of "lifetime", "annual", "monthly", "weekly", "daily" (or a bool).
Defaults to "lifetime" for any unrecognised value.
"""
if isinstance(db_type, bool):
db_type = "lifetime" if db_type else "annual"
valid_types = ["lifetime", "annual", "monthly", "weekly", "daily"]
if db_type not in valid_types:
db_type = "lifetime"
db_name = f"{db_type}.db"
db_path = str(DB_DIR / db_name)
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS xp (
user_id TEXT PRIMARY KEY,
xp INTEGER DEFAULT 0,
level INTEGER DEFAULT 0,
last_message INTEGER DEFAULT 0
)
""")
if db_type in ["daily", "weekly", "monthly"]:
cur.execute("""
CREATE TABLE IF NOT EXISTS reset_log (
id INTEGER PRIMARY KEY,
last_reset INTEGER DEFAULT 0
)
""")
cur.execute("INSERT OR IGNORE INTO reset_log (id, last_reset) VALUES (1, 0)")
conn.commit()
return conn, cur
def reset_leaderboard(db_type):
"""Wipe all XP rows and record the reset timestamp in reset_log."""
import time
conn, cur = get_db(db_type)
cur.execute("DELETE FROM xp")
cur.execute("UPDATE reset_log SET last_reset = ? WHERE id = 1", (int(time.time()),))
conn.commit()
conn.close()
def get_last_reset(db_type):
"""Return the Unix timestamp of the last leaderboard reset, or 0 if never reset."""
conn, cur = get_db(db_type)
cur.execute("SELECT last_reset FROM reset_log WHERE id = 1")
row = cur.fetchone()
conn.close()
return row[0] if row else 0