From abde18b3b6c0ae4417ed2f26fa2127d9a17c02b4 Mon Sep 17 00:00:00 2001 From: Lilac-Rose Date: Mon, 29 Sep 2025 20:02:39 +0200 Subject: [PATCH] Ported sparkles over from Nova, updated normal sparkle emoji to not be a star --- bot.py | 51 ++++++++++++++++++----------- sparkle/database.py | 20 ++++++++++++ sparkle/leaderboard.py | 73 ++++++++++++++++++++++++++++++++++++++++++ sparkle/sparkle.py | 51 +++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 sparkle/database.py create mode 100644 sparkle/leaderboard.py create mode 100644 sparkle/sparkle.py diff --git a/bot.py b/bot.py index a8edc89..9e41b88 100644 --- a/bot.py +++ b/bot.py @@ -5,7 +5,10 @@ from discord.ext import commands import asyncio import glob import traceback -from xp.database import get_db +from xp.database import get_db as get_xp_db + +# Import sparkle DB to ensure it exists +from sparkle.database import get_db as get_sparkle_db load_dotenv() @@ -15,15 +18,16 @@ ADMIN_ROLE_ID = int(os.getenv("ADMIN_ROLE_ID")) bot = commands.Bot(command_prefix="!", intents=discord.Intents.all()) async def load_cogs(folder: str): - # List of files that are NOT cogs (utility files) - non_cog_files = {"add_xp.py", "database.py", "utils.py", "__init__.py","import_old_data.py", "repair_db.py", "reset_db.py"} - + """Load all cogs in the folder except utility files""" + non_cog_files = {"add_xp.py", "database.py", "utils.py", "__init__.py", + "import_old_data.py", "repair_db.py", "reset_db.py"} + for file in glob.glob(f"{folder}/*.py"): filename = os.path.basename(file) if filename in non_cog_files: print(f"Skipping {filename} (utility file)") continue - + module_name = f"{folder}.{os.path.splitext(filename)[0]}" try: await bot.load_extension(module_name) @@ -36,26 +40,34 @@ async def load_cogs(folder: str): async def on_ready(): print(f"Logged in as {bot.user}!") - # Test database connections + # Ensure XP database connection works for lifetime in (True, False): try: - conn, cur = get_db(lifetime) + conn, cur = get_xp_db(lifetime) conn.close() - print(f"Database connection successful (lifetime={lifetime})") + print(f"XP database connection successful (lifetime={lifetime})") except Exception as e: - print(f"Database connection failed (lifetime={lifetime}): {e}") - - # Load cogs FIRST + print(f"XP database connection failed (lifetime={lifetime}): {e}") + + # Ensure Sparkle DB exists + try: + conn = get_sparkle_db() + conn.close() + print("Sparkle database initialized successfully.") + except Exception as e: + print(f"Failed to initialize sparkle database: {e}") + + # Load all cogs await load_cogs("commands") await load_cogs("wordbomb") await load_cogs("moderation") await load_cogs("xp") - - # Sync slash commands AFTER cogs are loaded + await load_cogs("sparkle") # <-- Load sparkle cogs + + # Sync slash commands after loading cogs try: synced = await bot.tree.sync() print(f"Synced {len(synced)} slash commands") - # Print the names of synced commands for debugging for cmd in synced: print(f" - {cmd.name}") except Exception as e: @@ -72,20 +84,21 @@ async def on_command_error(ctx, error): @bot.command(name="reload") @commands.has_role(ADMIN_ROLE_ID) async def reload(ctx): - # Reload cogs + """Reload commands cogs and sync slash commands""" await load_cogs("commands") await load_cogs("moderation") await load_cogs("xp") - - # Re-sync slash commands after reload + await load_cogs("sparkle") # Reload sparkle cogs + try: synced = await bot.tree.sync() await ctx.send(f"Cogs reloaded successfully! Synced {len(synced)} slash commands.") except Exception as e: await ctx.send(f"Cogs reloaded but failed to sync slash commands: {e}") -# hook XP into messages +# Hook XP into messages from xp.add_xp import add_xp + @bot.event async def on_message(message): if message.author.bot: @@ -106,4 +119,4 @@ async def main(): traceback.print_exc() if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/sparkle/database.py b/sparkle/database.py new file mode 100644 index 0000000..aa18ae7 --- /dev/null +++ b/sparkle/database.py @@ -0,0 +1,20 @@ +import sqlite3 +import os + +DB_PATH = os.path.join(os.path.dirname(__file__), "sparkle.db") + +def get_db(): + """Return a SQLite3 connection.""" + conn = sqlite3.connect(DB_PATH) + conn.execute(""" + CREATE TABLE IF NOT EXISTS sparkles ( + server_id TEXT, + user_id TEXT, + epic INTEGER DEFAULT 0, + rare INTEGER DEFAULT 0, + regular INTEGER DEFAULT 0, + PRIMARY KEY (server_id, user_id) + ) + """) + conn.commit() + return conn \ No newline at end of file diff --git a/sparkle/leaderboard.py b/sparkle/leaderboard.py new file mode 100644 index 0000000..d23f95c --- /dev/null +++ b/sparkle/leaderboard.py @@ -0,0 +1,73 @@ +import discord +from discord.ext import commands +from discord import app_commands +from discord.utils import escape_markdown +from .database import get_db +import asyncio + +class SparkleLeaderboard(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.sparkle_emojis = { + "epic": "✨", + "rare": "🌟", + "regular": "💫" + } + + + @commands.hybrid_command(name="sparkleleaderboard", aliases=["sparklelb"], description="Show server Sparkle leaderboard") + @app_commands.describe(limit="Number of users to show (max 20)") + async def sparkle_leaderboard(self, ctx: commands.Context, limit: int = 10): + limit = max(1, min(20, limit)) + guild_member_ids = {str(member.id) for member in ctx.guild.members} + + if not guild_member_ids: + await ctx.send("This server has no members to display.", ephemeral=True) + return + + def db_task(): + conn = get_db() + placeholders = ",".join(["?"] * len(guild_member_ids)) + query = f""" + SELECT user_id, epic, rare, regular, + (epic + rare + regular) as total + FROM sparkles + WHERE server_id = ? AND user_id IN ({placeholders}) + ORDER BY total DESC + LIMIT ? + """ + params = [str(ctx.guild.id), *guild_member_ids, limit] + cursor = conn.execute(query, params) + results = cursor.fetchall() + conn.close() + return results + + results = await asyncio.to_thread(db_task) + + if not results: + await ctx.send("No sparkle data available for members of this server.", ephemeral=True) + return + + embed = discord.Embed( + title=f"{escape_markdown(ctx.guild.name)} Sparkle Leaderboard", + color=discord.Color.gold() + ) + + for rank, (user_id, epic, rare, regular, total) in enumerate(results, 1): + user = ctx.guild.get_member(int(user_id)) + display_name = escape_markdown(user.display_name) if user else f"Unknown User ({user_id})" + sparkles = ( + f"{self.sparkle_emojis['epic']} {epic} (Epic) | " + f"{self.sparkle_emojis['rare']} {rare} (Rare) | " + f"{self.sparkle_emojis['regular']} {regular} (Regular) | " + f"**Total:** {total}" + ) + embed.add_field(name=f"{rank}. {display_name}", value=sparkles, inline=False) + if rank == 1 and user: + embed.set_thumbnail(url=user.display_avatar.url) + + embed.set_footer(text="✨ Epic | 🌟 Rare | 💫 Regular") + await ctx.send(embed=embed) + +async def setup(bot): + await bot.add_cog(SparkleLeaderboard(bot)) diff --git a/sparkle/sparkle.py b/sparkle/sparkle.py new file mode 100644 index 0000000..c5d9940 --- /dev/null +++ b/sparkle/sparkle.py @@ -0,0 +1,51 @@ +import random +import discord +from discord.ext import commands +from .database import get_db +import asyncio + +class Sparkle(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.chances = { + "epic": (1, "✨", "an **epic sparkle**"), + "rare": (10, "🌟", "a **rare sparkle**"), + "regular": (1000, "💫", "a **sparkle**") + } + + async def _add_sparkle(self, message, sparkle_type): + emoji, description = self.chances[sparkle_type][1:] + await message.add_reaction(emoji) + await message.reply( + f"**{message.author.name}** got {description}! {emoji}", + mention_author=False + ) + + def db_task(): + conn = get_db() + conn.execute( + f"""INSERT INTO sparkles (server_id, user_id, {sparkle_type}) + VALUES (?, ?, 1) + ON CONFLICT(server_id, user_id) DO UPDATE SET + {sparkle_type} = {sparkle_type} + 1""", + (str(message.guild.id), str(message.author.id)) + ) + conn.commit() + conn.close() + + await asyncio.to_thread(db_task) + + @commands.Cog.listener() + async def on_message(self, message): + if message.author.bot or not message.guild: + return + chance = random.randint(1, 1000000) + if chance == 1: + await self._add_sparkle(message, "epic") + elif chance <= 10: + await self._add_sparkle(message, "rare") + elif chance <= 1000: + await self._add_sparkle(message, "regular") + +async def setup(bot): + await bot.add_cog(Sparkle(bot))