From 30bab810072df2ea390e475d9a15ce1e1400a31a Mon Sep 17 00:00:00 2001 From: DefsNotQuack Date: Sun, 30 Mar 2025 17:34:47 +1000 Subject: [PATCH] Reinforced reaction_message management and interaction, better split responsibilty of tasks across files and functions. UUID is now used as primary key for reaction_messages. --- cogs/channel.py | 30 --- cogs/hello.py | 15 -- cogs/ping.py | 13 -- cogs/react_roles.py | 158 ++++++---------- cogs/setup.py | 30 +++ data/react_roles.py | 371 ++++++++++++++++++++++++++----------- main.py | 20 +- react_roles/react_roles.py | 38 ++-- 8 files changed, 389 insertions(+), 286 deletions(-) delete mode 100644 cogs/channel.py delete mode 100644 cogs/hello.py delete mode 100644 cogs/ping.py create mode 100644 cogs/setup.py diff --git a/cogs/channel.py b/cogs/channel.py deleted file mode 100644 index 36e1a1a..0000000 --- a/cogs/channel.py +++ /dev/null @@ -1,30 +0,0 @@ -import discord -from discord.ext import commands - -class Channel(commands.Cog): - def __init__(self, bot): - self.bot = bot - - @commands.slash_command(name="clear_channel", - description="Permanently delete all messages in a channel", - guild_ids=[681414775468589180]) - @commands.has_permissions(manage_messages=True) - async def clear_channel(self, ctx: discord.ApplicationContext): - progress = await ctx.respond("Deleting all messages in this channel...", ephemeral=True) - channel = ctx.channel - - # Fetch all messages in the channel - async for message in channel.history(limit=None): - try: - await message.delete() - except discord.Forbidden: - await ctx.send("I do not have permission to delete messages.") - return - except discord.HTTPException: - await ctx.send("Failed to delete a message.") - return - - await progress.edit(content="✔ All messages deleted successfully! ✔") - -def setup(bot): - bot.add_cog(Channel(bot)) \ No newline at end of file diff --git a/cogs/hello.py b/cogs/hello.py deleted file mode 100644 index 800c45e..0000000 --- a/cogs/hello.py +++ /dev/null @@ -1,15 +0,0 @@ -import discord -from discord.ext import commands - -class Hello(commands.Cog): - def __init__(self, bot): - self.bot = bot - - @commands.slash_command(name="hello", - description="Say hi to the bot!", - guild_ids=[681414775468589180]) - async def hello(self, ctx: discord.ApplicationContext): - await ctx.respond("Hello there!") - -def setup(bot): - bot.add_cog(Hello(bot)) \ No newline at end of file diff --git a/cogs/ping.py b/cogs/ping.py deleted file mode 100644 index 002d62a..0000000 --- a/cogs/ping.py +++ /dev/null @@ -1,13 +0,0 @@ -import discord -from discord.ext import commands - -class Ping(commands.Cog): - def __init__(self, bot): - self.bot = bot - - @commands.slash_command(name="ping", description="Ping the bot") - async def ping(self, ctx: discord.ApplicationContext): - await ctx.respond("🏓 Pong!") - -def setup(bot): - bot.add_cog(Ping(bot)) \ No newline at end of file diff --git a/cogs/react_roles.py b/cogs/react_roles.py index 4484c90..e6eb6e6 100644 --- a/cogs/react_roles.py +++ b/cogs/react_roles.py @@ -1,132 +1,88 @@ import os +import uuid import discord from discord.ext import commands -from data import react_roles +from data import react_roles as db_react_roles +from react_roles import react_roles as rr from util.console import console, panel, track_iterable as track GUILD_IDS = [int(os.getenv("DEV_GUILD_ID"))] -REACTION_ROLE_CHANNEL_ID = int(os.getenv("REACT_ROLE_CHANNEL_ID")) +# Description of the reaction message cache +reaction_message_descriptions_cache = {} -def update_react_role_embed(category_name): - # Generate role list - roles = react_roles.get_react_roles_by_category(category_name) - role_list = [] - for role in roles: - emoji = role[2] - description = role[3] - role_list.append(f"{emoji} - {description}") +async def check_server_instantiated(ctx: discord.ApplicationContext): + """ + Check if the server has been instantiated. + """ + if not db_react_roles.guild_exists(ctx.guild.id): + await ctx.send("Server not instantiated. Please run the `/instantiate_server` command first.") + return False + return True - # Create an embed for the react-role category - embed = discord.Embed(title=f"**{category_name.capitalize()} Roles**", - description="React to this message to gain access to the relevant text and voice channels.\n\n" + "\n".join(role_list), - color=discord.Color.dark_orange()) - return embed class ReactRoles(commands.Cog): def __init__(self, bot): self.bot = bot - # Slash command to add a new reaction role Category / Message - @commands.slash_command(name="new_react_role_category", - description="Create a new category of react roles", - guild_ids=GUILD_IDS) + ### COMMANDS ### + # Command to create a new reaction role message + @commands.slash_command(name="new_reaction_message", description="Create a new reaction message", guild_ids=GUILD_IDS) @commands.has_permissions(manage_roles=True) - async def new_react_role_category(self, ctx: discord.ApplicationContext, category_name: str): - # Verify the category is unique - existing_categories = react_roles.get_react_roles_categories() - if category_name.lower() in [cat[1] for cat in existing_categories]: - await ctx.respond(f"Category '{category_name}' already exists.", ephemeral=True) + async def new_reaction_message(self, ctx: discord.ApplicationContext, description: str, thumbnail_url: str = None): + # Check if server has been instantiated + await check_server_instantiated(ctx) + + # Since we can't get the message ID until we send the message, but need to display in the message itself + # some kind of unique identifier, we will create a UUID to display in the message, send the message, and then + # add it all to the database. + + # Send Message to Channel - Must happen first so that we can get the message ID for the database + unique_id = str(uuid.uuid4()) + embed = rr.generate_reaction_message_embed(description, thumbnail_url, unique_id) + message = await ctx.channel.send(embed=embed) + + # Add the reaction_message to the database + db_react_roles.check_and_add_reaction_message(unique_id, ctx.guild.id, ctx.channel.id, message.id, description, thumbnail_url) + + # Ensure the message was put in the database correctly, otherwise delete the message + if not db_react_roles.reaction_message_exists(ctx.guild.id, ctx.channel.id, message.id): + await message.delete() + await ctx.respond("Failed to create the reaction message. Please try again.", ephemeral=True) return + await ctx.respond(f"Reaction message created successfully!", ephemeral=True) - # Send a new Embed message - embed = discord.Embed(title=f"**{category_name.capitalize()} Roles**", - description="React to this message to gain access to the relevant text and voice channels.", - color=discord.Color.dark_orange()) - message = await ctx.guild.get_channel(REACTION_ROLE_CHANNEL_ID).send(embed=embed) - - # Create the category in the database - react_roles.add_react_role_category_to_db(message.id, category_name) - - # Send a confirmation message - await ctx.respond(f"React role category '{category_name}' created successfully!", ephemeral=True) - - #Slash command to add a new reaction role to an existing category - @commands.slash_command(name="new_react_role", - description="Create a new react role", - guild_ids=GUILD_IDS) + @commands.slash_command(name="delete_reaction_message", description="Delete a reaction message", guild_ids=GUILD_IDS) @commands.has_permissions(manage_roles=True) - async def new_react_role(self, ctx: discord.ApplicationContext, category_name: str, role: discord.Role, emoji: str, description: str): - # Verify the category exists - existing_categories = react_roles.get_react_roles_categories() - if category_name.lower() not in [cat[1] for cat in existing_categories]: - await ctx.respond(f"Category '{category_name}' does not exist.", ephemeral=True) + async def delete_reaction_message(self, ctx: discord.ApplicationContext, + reaction_role_id: str): + # Check if server has been instantiated + await check_server_instantiated(ctx) + + # Check if the message ID is in the database + message = db_react_roles.get_reaction_message_by_uuid(reaction_role_id) + if not message: + await ctx.respond("That Reaction Role ID does not exist", ephemeral=True) return - # Get the message ID of the category - message_id = next(cat[0] for cat in existing_categories if cat[1] == category_name.lower()) - - # Add the react-role to the database - react_roles.add_react_role_to_db(message_id, role.id, emoji, description) - message = await self.bot.get_channel(REACTION_ROLE_CHANNEL_ID).fetch_message(message_id) - await message.edit(embed=update_react_role_embed(category_name)) - await message.add_reaction(emoji) - - # Send a confirmation message - await ctx.respond(f"React role '{emoji}' - '{role.name}' added to category '{category_name}'.", ephemeral=True) - - - # Event listener for reaction add - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - # Check if the reaction is in the correct channel and not from the bot itself - if payload.channel_id != REACTION_ROLE_CHANNEL_ID or payload.user_id == self.bot.user.id: + if message[2] != ctx.guild.id: + await ctx.respond("That Reaction Role ID does not exist in this server", ephemeral=True) return - # Ge all react roles for the message_id that was reacted to - message_id = payload.message_id - emoji = str(payload.emoji) - react_roles_list = react_roles.get_react_roles_by_message_id(message_id) + # Delete the message from the channel + channel = message[3] + message = await self.bot.get_channel(channel).fetch_message(int(message_id)) + await message.delete() - if react_roles_list: - for role in react_roles_list: - if role[2] == emoji: - guild = self.bot.get_guild(payload.guild_id) - member = payload.member - role_obj = guild.get_role(role[1]) + # Delete the message from the database + db_react_roles.delete_reaction_message_from_db(int(message_id)) - # Add the role to the member - if role_obj not in member.roles: - await member.add_roles(role_obj) - console.log (f"[green]✔ Added role:[/] {emoji} {role_obj.name} to {member.name}") + # Send confirmation message + await ctx.respond(f"Reaction message deleted successfully!", ephemeral=True) - # Event Listener for reaction remove - @commands.Cog.listener() - async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): - # Check if the reaction is in the correct channel and not from the bot itself - if payload.channel_id != REACTION_ROLE_CHANNEL_ID or payload.user_id == self.bot.user.id: - return - # Ge all react roles for the message_id that was reacted to - message_id = payload.message_id - emoji = str(payload.emoji) - react_roles_list = react_roles.get_react_roles_by_message_id(message_id) - - if react_roles_list: - for role in react_roles_list: - if role[2] == emoji: - guild = self.bot.get_guild(payload.guild_id) - - # Member object is not available in the payload, so we need to fetch it... - member = await guild.fetch_member(payload.user_id) - role_obj = guild.get_role(role[1]) - - # Add the role to the member - if role_obj in member.roles: - await member.remove_roles(role_obj) - console.log (f"[red]✖ Removed role:[/] {emoji} {role_obj.name} from {member.name}") def setup(bot): bot.add_cog(ReactRoles(bot)) \ No newline at end of file diff --git a/cogs/setup.py b/cogs/setup.py new file mode 100644 index 0000000..2a82ea6 --- /dev/null +++ b/cogs/setup.py @@ -0,0 +1,30 @@ +import os + +import discord +from discord.ext import commands +from data import react_roles + +from util.console import console, panel, track_iterable as track + +GUILD_IDS = [int(os.getenv("DEV_GUILD_ID"))] + +class Setup(commands.Cog): + def __init__(self, bot): + self.bot = bot + + ### COMMANDS ### + @commands.slash_command(name="instantiate_server", description="Instantiate the server.", guild_ids=GUILD_IDS) + @commands.has_permissions(manage_guild=True) + async def instantiate_server(self, ctx: discord.ApplicationContext): + """ + Command to instantiate the server. + This command creates the necessary database tables and initializes the server. + """ + # Add guild to the database + react_roles.check_and_add_guild(ctx.guild_id, ctx.guild.name) + console.log(f"[green]Guild - {ctx.guild.name} - added to the database.[/green]") + await ctx.respond(f"Server '{ctx.guild.name}' instantiated successfully!", ephemeral=True) + + +def setup(bot): + bot.add_cog(Setup(bot)) \ No newline at end of file diff --git a/data/react_roles.py b/data/react_roles.py index fafbdbd..3a1f89d 100644 --- a/data/react_roles.py +++ b/data/react_roles.py @@ -1,7 +1,11 @@ import sqlite3 +from util.console import console, panel, track_iterable as track + DB_PATH = 'data.db' +### INITIALIZATION ### + def init_db(): """ Initialize the database and create necessary tables. @@ -9,136 +13,295 @@ def init_db(): conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() - # Create the react_roles table + # Create guilds table + # This table stores the guild IDs and their respective names. cursor.execute(''' - CREATE TABLE IF NOT EXISTS react_roles ( - message_id INTEGER, - role_id INTEGER, - emoji TEXT, - description TEXT + CREATE TABLE IF NOT EXISTS guilds ( + id BIGINT PRIMARY KEY, + name TEXT + ) + ''') + + # Create the reaction_messages table + # This table stores the messages that will be used for reaction roles. + cursor.execute(''' + CREATE TABLE IF NOT EXISTS reaction_messages ( + uid TEXT PRIMARY KEY, + message_id BIGINT, + guild_id BIGINT REFERENCES guilds(id), + channel_id BIGINT, + description TEXT, + thumbnail TEXT DEFAULT NULL, + UNIQUE(guild_id, channel_id, id), + UNIQUE(description) ) ''') # Create the react_role_categories table + # This table stores the emoji-role mappings for each message. cursor.execute(''' - CREATE TABLE IF NOT EXISTS react_role_categories ( - message_id INTEGER, - category_name TEXT + CREATE TABLE IF NOT EXISTS reaction_roles ( + id SERIAL PRIMARY KEY, + message_id BIGINT REFERENCES reaction_messages(id) ON DELETE CASCADE, + emoji TEXT, + role_id [BIGINT], + description TEXT, + forbidden_roles [BIGINT] DEFAULT NULL, + required_roles [BIGINT] DEFAULT NULL, + UNIQUE(message_id, emoji) ) ''') conn.commit() conn.close() -def add_react_role_category_to_db(message_id: int, category_name: str): +### DATABASE FUNCTIONS ### +### GUILD TABLE FUNCTIONS ### + +### CHECK EXISTANCE ### +def guild_exists(guild_id: int) -> bool: """ - Add a new react role category to the database. - :param message_id: The ID of the message to which the category is associated. - :param category_name: The name of the category (e.g., "Category1"). + Check if a guild exists in the database. + :param guild_id: The ID of the guild. + :return: True if the guild exists, False otherwise. """ conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() - # Create the table if it doesn't exist + # Check if the guild exists in the database cursor.execute(''' - CREATE TABLE IF NOT EXISTS react_role_categories ( - message_id INTEGER, - category_name TEXT - ) - ''') + SELECT EXISTS(SELECT 1 FROM guilds WHERE id=?) + ''', (guild_id,)) + exists = cursor.fetchone()[0] - # Insert the new react-role category into the database + conn.close() + return exists + +### ADD ROW ### +def add_guild_to_db(guild_id: int, guild_name: str): + """ + Add a new guild to the database. + :param guild_id: The ID of the guild. + :param guild_name: The name of the guild. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Insert the new guild into the database cursor.execute(''' - INSERT INTO react_role_categories (message_id, category_name) + INSERT INTO guilds (id, name) VALUES (?, ?) - ''', (message_id, category_name.lower())) + ''', (guild_id, guild_name)) conn.commit() conn.close() -def add_react_role_to_db(message_id: int, role_id: int, emoji: str, description: str): +### CHECK THEN ADD ROW ### +def check_and_add_guild(guild_id: int, guild_name: str): """ - Add a new react role to the database. - :param message_id: The message ID of the react-role message. - :param role_id: The ID of the role to assign. - :param emoji: The emoji to react with. + Check if a guild exists in the database, and if not, add it. + :param guild_id: The ID of the guild. + :param guild_name: The name of the guild. + """ + if not guild_exists(guild_id): + add_guild_to_db(guild_id, guild_name) + console.log(f"[green]✔ DB Added Row to table guilds:[/] {guild_name} ({guild_id})") + + +### REACTION MESSAGES TABLE FUNCTIONS ### + +### CHECK EXISTENCE ### +def reaction_message_exists(guild_id: int, channel_id: int, message_id: int) -> bool: + """ + Check if a reaction message exists in the database. + :param guild_id: The ID of the guild. + :param channel_id: The ID of the channel. + :param message_id: The ID of the message. + :return: True if the reaction message exists, False otherwise. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Check if the reaction message exists in the database + cursor.execute(''' + SELECT EXISTS(SELECT 1 FROM reaction_messages WHERE guild_id=? AND channel_id=? AND id=?) + ''', (guild_id, channel_id, message_id)) + exists = cursor.fetchone()[0] + + conn.close() + return exists + +### ADD ROW ### +def add_reaction_message_to_db(uuid: str, guild_id: int, channel_id: int, message_id: int, description: str, thumbnail: str = None): + """ + Add a new reaction message to the database. + :param uuid: Unique identifier for the message. + :param description: The name of the reaction message. IE "Gaming Roles" + :param guild_id: The ID of the guild. + :param channel_id: The ID of the channel. + :param message_id: The ID of the message. + :param thumbnail: The URL of the thumbnail image. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Insert the new reaction message into the database + cursor.execute(''' + INSERT INTO reaction_messages (uuid, id, guild_id, channel_id, description, thumbnail) + VALUES (?, ?, ?, ?, ?, ?) + ''', (uuid, message_id, guild_id, channel_id, description, thumbnail)) + + conn.commit() + conn.close() + +### DELETE ROW ### +def delete_reaction_message_from_db(message_id: int): + """ + Delete a reaction message from the database. + :param message_id: The ID of the message to delete. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Delete the reaction message from the database + cursor.execute(''' + DELETE FROM reaction_messages WHERE id=? + ''', (message_id,)) + + conn.commit() + conn.close() + +### FETCH MESSAGE BY ID ### +def get_reaction_message_by_id(message_id: int): + """ + Fetch a reaction message by its ID. + :param message_id: The ID of the message. + :return: The reaction message if found, None otherwise. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Fetch the reaction message from the database + cursor.execute(''' + SELECT * FROM reaction_messages WHERE id=? + ''', (message_id,)) + message = cursor.fetchone() + + conn.close() + return message + +### FETCH MESSAGE BY UUID ### +def get_reaction_message_by_uuid(uuid: str): + """ + Fetch a reaction message by its UUID. + :param uuid: The UUID of the message. + :return: The reaction message if found, None otherwise. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Fetch the reaction message from the database + cursor.execute(''' + SELECT * FROM reaction_messages WHERE uuid=? + ''', (uuid,)) + message = cursor.fetchone() + + conn.close() + return message + +### FETCH ALL REACTION MESSAGES ID:DESCRIPTION PAIRS ### +def get_all_reaction_messages_id_pairs() -> dict: + """ + Fetch all reaction messages from the database. + :return: A dictionary of message IDs and their descriptions. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Fetch all reaction messages from the database + cursor.execute(''' + SELECT id, description FROM reaction_messages + ''') + messages = cursor.fetchall() + + conn.close() + return {message[1]: message[0] for message in messages} + +### CHECK THEN ADD ROW ### +def check_and_add_reaction_message(uuid: str, guild_id: int, channel_id: int, message_id: int, description: str, thumbnail: str = None): + """ + Check if a reaction message exists in the database, and if not, add it. + :param uuid: + :param thumbnail: + :param description: + :param guild_id: The ID of the guild. + :param channel_id: The ID of the channel. + :param message_id: The ID of the message. + """ + if not reaction_message_exists(guild_id, channel_id, message_id): + console.log(f"[green]✔ DB Added Row to table reaction_messages:[/] {description}") + add_reaction_message_to_db(uuid, guild_id, channel_id, message_id, description, thumbnail) + else: + console.log(f"[yellow]⚠️ DB Adding Row to table reaction_messages failed:[/] {description} already exists.") + + +### REACTION ROLES TABLE FUNCTIONS ### + +### CHECK EXISTENCE ### +def reaction_role_exists(message_id: int, emoji: str) -> bool: + """ + Check if a reaction role exists in the database. + :param message_id: The ID of the message. + :param emoji: The emoji associated with the role. + :return: True if the reaction role exists, False otherwise. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Check if the reaction role exists in the database + cursor.execute(''' + SELECT EXISTS(SELECT 1 FROM reaction_roles WHERE message_id=? AND emoji=?) + ''', (message_id, emoji)) + exists = cursor.fetchone()[0] + + conn.close() + return exists + +### ADD ROW ### +def add_reaction_role_to_db(message_id: int, emoji: str, role_id: [int], description: str, forbidden_roles: [int] = None, required_roles: [int] = None): + """ + Add a new reaction role to the database. + :param message_id: The ID of the message. + :param emoji: The emoji associated with the role. + :param role_id: A list of role IDs to assign. + :param description: The description of the role. + :param forbidden_roles: A list of role IDs that are forbidden. + :param required_roles: A list of role IDs that are required. + """ + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + # Insert the new reaction role into the database + cursor.execute(''' + INSERT INTO reaction_roles (message_id, emoji, role_id, description, forbidden_roles, required_roles) + VALUES (?, ?, ?, ?, ?, ?) + ''', (message_id, emoji, role_id, description, forbidden_roles, required_roles)) + + conn.commit() + conn.close() + +### CHECK THEN ADD ROW ### +def check_and_add_reaction_role(message_id: int, emoji: str, role_id: int, description: str): + """ + Check if a reaction role exists in the database, and if not, add it. + :param message_id: The ID of the message. + :param emoji: The emoji associated with the role. + :param role_id: The ID of the role. :param description: The description of the role. """ - conn = sqlite3.connect(DB_PATH) - cursor = conn.cursor() - # Create the table if it doesn't exist - cursor.execute(''' - CREATE TABLE IF NOT EXISTS react_roles ( - message_id INTEGER, - role_id INTEGER, - emoji TEXT, - description TEXT - ) - ''') - - # Insert the new react-role into the database - cursor.execute(''' - INSERT INTO react_roles (message_id, role_id, emoji, description) - VALUES (?, ?, ?, ?) - ''', (message_id, role_id, emoji, description)) - - conn.commit() - conn.close() - -def get_react_roles_categories(): - """ - Get all react role categories from the database. - :return: A list of tuples containing message_id and category_name. - """ - conn = sqlite3.connect(DB_PATH) - cursor = conn.cursor() - - # Fetch all react-role categories from the database - cursor.execute(''' - SELECT * FROM react_role_categories - ''') - categories = cursor.fetchall() - - conn.close() - return categories - -def get_react_roles_by_category(category_name: str): - """ - Get all react roles for a specific category from the database. - :param category_name: The name of the category (e.g., "Category1"). - :return: A list of tuples containing message_id, role_id, emoji, and description. - """ - # Get the message id of the category - categories = get_react_roles_categories() - message_id = next((cat[0] for cat in categories if cat[1] == category_name.lower()), None) - - conn = sqlite3.connect(DB_PATH) - cursor = conn.cursor() - - # Fetch all react-roles for the specified category from the database - cursor.execute(''' - SELECT * FROM react_roles WHERE message_id = ? - ''', (message_id,)) - roles = cursor.fetchall() - - conn.close() - return roles - -def get_react_roles_by_message_id(message_id: int): - """ - Get all react roles for a specific message ID from the database. - :param message_id: The ID of the message to which the roles are associated. - :return: A list of tuples containing message_id, role_id, emoji, and description. - """ - conn = sqlite3.connect(DB_PATH) - cursor = conn.cursor() - - # Fetch all react-roles for the specified message ID from the database - cursor.execute(''' - SELECT * FROM react_roles WHERE message_id = ? - ''', (message_id,)) - roles = cursor.fetchall() - - conn.close() - return roles + if not reaction_role_exists(message_id, emoji): + console.log(f"[green]✔ DB Adding Row to table reaction_roles:[/] {message_id} ({emoji})") + add_reaction_role_to_db(message_id, emoji, role_id, description) + else: + console.log(f"[yellow]⚠️ DB Adding Row to table reaction_roles failed:[/] {message_id} ({emoji}) already exists.") diff --git a/main.py b/main.py index a311d37..b19f3f4 100644 --- a/main.py +++ b/main.py @@ -27,35 +27,33 @@ COG_PATH = Path(__file__).resolve().parent / "cogs" cogs_available = {} loaded_cog_modules = {} -# Arguments used in cog loading -COG_CONFIG = { - "react_roles": { - "guild_ids": [DEV_GUILD_ID], - "react_channel_id": REACT_ROLE_CHANNEL_ID - } -} - +# Add available cogs to the list for file in COG_PATH.iterdir(): if file.suffix == ".py" and not file.name.startswith("__"): cog_name = file.stem cogs_available[cog_name] = file +############################ +### COGS LOADING HANDLER ### +############################ + async def load_cogs(): + # Check if there are any cogs to load if not cogs_available: console.log("[yellow]⚙️ No cogs available to load...[/yellow]") return + # Check if the cogs directory exists if not COG_PATH.exists(): console.log(f"[red]❌ Cogs directory not found at {COG_PATH}[/red]") return console.print(panel(content="🔌 [bold]Loading Cogs[/bold]", style="cyan")) + # Load each cog for cog in track(cogs_available, description="Loading Cogs..."): module_path = f"cogs.{cog}" - config = COG_CONFIG.get(cog, {}) - try: bot.load_extension(module_path) loaded_cog_modules[cog] = module_path @@ -71,10 +69,10 @@ async def shutdown(): await bot.close() def handle_signals(): + # Handle shutdown signals for sig in (signal.SIGINT, signal.SIGTERM): signal.signal(sig, lambda s, f: asyncio.create_task(shutdown())) - # Bot Event Handlers @bot.event async def on_ready(): diff --git a/react_roles/react_roles.py b/react_roles/react_roles.py index 212a7bc..d344e9b 100644 --- a/react_roles/react_roles.py +++ b/react_roles/react_roles.py @@ -1,17 +1,31 @@ import discord -from data import react_roles +from data import react_roles as db_react_roles -def update_react_role_embed(category_name): - # Generate role list - roles = react_roles.get_react_roles_by_category(category_name) - role_list = [] - for role in roles: - emoji = role[2] - description = role[3] - role_list.append(f"{emoji} - {description}") +from util.console import console, panel, track_iterable as track +### EMBED CREATION ### +def generate_reaction_message_embed(description: str, thumbnail: str, uuid: str): + """ + Generate an embed message for the reaction role message. + """ # Create an embed for the react-role category - embed = discord.Embed(title=f"**{category_name.capitalize()} Roles**", - description="React to this message to gain access to the relevant text and voice channels.\n\n" + "\n".join(role_list), + embed = discord.Embed(title=f"**{description.capitalize()} Roles**", + thumbnail=f"{thumbnail}", + description="Use the reactions below to be granted access to the relevant text and " + "voice channels. Remove your reaction to remove access. Simples!!! \n\n", color=discord.Color.dark_orange()) - return embed \ No newline at end of file + embed.set_footer(text=f"Reaction Role ID: {uuid}") + return embed + +def required_emojis_for_reaction_message(message_id): + """ + Get the required emojis for a reaction message. + """ + # Get the emojis for the reaction message + message_db_data = db_react_roles.get_reaction_message_by_id(message_id) + emojis = [] + for message in message_db_data: + emojis.append(message[2]) + if not emojis: + return None + return emojis