import asyncio import random import asqlite import discord import discord.ext import discord.ext.commands from discord import Color, app_commands from discord.ext import commands from discord.ui import View class Fireboard(commands.Cog): def __init__(self, bot): self.bot = bot self.locked_messages = [] self.disabled = False # Check if the bot has the fireboard_pool attribute if not hasattr(bot, 'fireboard_pool') or bot.fireboard_pool is None: print("Warning: Bot does not have fireboard_pool initialized. Fireboard functionality will be disabled.") self.disabled = True return self.fireboard_pool: asqlite.Pool = bot.fireboard_pool self.bot.loop.create_task(self.setup()) # SQL Setup async def setup(self): async with self.fireboard_pool.acquire() as sql: if ( await sql.fetchone( "SELECT name FROM sqlite_master WHERE type='table' AND name='fireMessages';" ) is None ): # Fire Messages - messages that are active on the fireboard await sql.execute( "CREATE TABLE fireMessages (serverID int, msgID int, boardMsgID int, reactionAmount int)" ) if ( await sql.fetchone( "SELECT name FROM sqlite_master WHERE type='table' AND name='fireSettings';" ) is None ): # Fire Settings - server properties for fireboard await sql.execute( "CREATE TABLE fireSettings (serverID int, reactionAmount int, emoji text, channelID int, ignoreBots int)" ) if ( await sql.fetchone( "SELECT name FROM sqlite_master WHERE type='table' AND name='fireChannelBlacklist';" ) is None ): # Fire Channel Blacklist - blacklisted channels await sql.execute( "CREATE TABLE fireChannelBlacklist (serverID int, channelID int)" ) if ( await sql.fetchone( "SELECT name FROM sqlite_master WHERE type='table' AND name='fireRoleBlacklist';" ) is None ): # Fire Role Blacklist - blacklisted roles await sql.execute( "CREATE TABLE fireRoleBlacklist (serverID int, roleID int)" ) await sql.commit() await self.refresh_fire_lists() # List refresh function async def refresh_fire_lists(self): async with self.fireboard_pool.acquire() as sql: self.fire_messages = await sql.fetchall("SELECT * FROM fireMessages") self.fire_settings = await sql.fetchall("SELECT * FROM fireSettings") self.fire_channel_blacklist = await sql.fetchall( "SELECT * FROM fireChannelBlacklist" ) self.fire_role_blacklist = await sql.fetchall( "SELECT * FROM fireRoleBlacklist" ) # Listen for reactions @commands.Cog.listener() async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): self.bot: discord.ext.commands.Bot # Stop if this is a DM if payload.guild_id is None: return queued = False # Lock system if payload.message_id in self.locked_messages: queued = True while payload.message_id in self.locked_messages: await asyncio.sleep(0.5) self.locked_messages.append(payload.message_id) try: fetched = False # Find server config for server in self.fire_settings: if server[0] == payload.guild_id: react_minimum = server[1] emoji = server[2] channel_id = server[3] ignore_bots = True if int(server[4]) == 1 else False fetched = True # Stop if server has no config (fireboard isn't enabled) if not fetched: return # Stop if message is by Titanium if payload.message_author_id == self.bot.user.id: return # Stop if emoji doesn't match if str(payload.emoji) != emoji: return # --- Edit board message if it already exists --- if payload.message_id in [message[1] for message in self.fire_messages]: message = [ message for message in self.fire_messages if message[1] == payload.message_id ][0] # Only fetch updated reaction count if I have queued or reaction amount is undefined if queued or message[3] is None: # Fetch message and channel try: msg_channel = await self.bot.fetch_channel(payload.channel_id) message = await msg_channel.fetch_message(payload.message_id) except discord.errors.NotFound: return # Stop if not enough reactions for reaction in message.reactions: if str(reaction.emoji) == emoji: react_count = reaction.count break if react_count < react_minimum: return else: react_count = None async with self.fireboard_pool.acquire() as sql: # Set updated react count if react_count is not None: await sql.execute( "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", ( react_count, payload.message_id, ), ) await self.refresh_fire_lists() else: await sql.execute( "UPDATE fireMessages SET reactionAmount = reactionAmount + 1 WHERE msgID = ?", (payload.message_id,), ) await self.refresh_fire_lists() # Get message from message list message = [ message for message in self.fire_messages if message[1] == payload.message_id ][0] # Get board message board_channel = await self.bot.fetch_channel(channel_id) board_message = await board_channel.fetch_message(message[2]) await board_message.edit( content=f"**{message[3]} {emoji}** | <@{payload.message_author_id}> | <#{payload.channel_id}>", embeds=board_message.embeds, ) return # Stop if message is in a blacklisted channel if payload.channel_id in [ channel[1] for channel in self.fire_channel_blacklist ]: return # Stop if message is by a blacklisted role guild = await self.bot.fetch_guild(payload.guild_id) member = await guild.fetch_member(payload.user_id) if any( role[1] in [role.id for role in member.roles] for role in self.fire_role_blacklist ): return # Fetch message and channel try: msg_channel = await self.bot.fetch_channel(payload.channel_id) message = await msg_channel.fetch_message(payload.message_id) except discord.errors.NotFound: return # Stop if message is by a bot if ignore_bots and message.author.bot: return # Check if this is a thread if isinstance(msg_channel, discord.Thread): # Check if parent channel is NSFW if msg_channel.parent.nsfw: return else: # Stop if message is in an NSFW channel if message.channel.nsfw: return # Stop if not enough reactions for reaction in message.reactions: if str(reaction.emoji) == emoji: react_count = reaction.count break if react_count < react_minimum: return # --- Send message to fireboard --- # Create embed embed = discord.Embed(description=message.content, color=Color.random()) embed.set_author( name=message.author.name, icon_url=message.author.display_avatar.url ) embed.timestamp = message.created_at # Jump to message button view = View() view.add_item( discord.ui.Button( label="Jump to Message", url=message.jump_url, style=discord.ButtonStyle.url, ) ) embed_list = [embed] # Add reply embed if message.reference: try: reply_message = await msg_channel.fetch_message( message.reference.message_id ) reply_embed = discord.Embed( title="Replying To", description=reply_message.content, color=Color.random(), ) reply_embed.set_author( name=reply_message.author.name, icon_url=reply_message.author.display_avatar.url, ) reply_embed.timestamp = reply_message.created_at embed_list.insert(0, reply_embed) except discord.errors.NotFound: pass # Send message board_channel = await self.bot.fetch_channel(channel_id) board_message = await board_channel.send( content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", embeds=embed_list, view=view, files=[ await attachment.to_file() for attachment in message.attachments ], ) async with self.fireboard_pool.acquire() as sql: # Insert message to DB await sql.execute( "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", ( payload.guild_id, payload.message_id, board_message.id, react_count, ), ) await sql.commit() await self.refresh_fire_lists() except Exception as e: raise e finally: self.locked_messages.remove(payload.message_id) # Listen for reaction removal @commands.Cog.listener() async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): self.bot: discord.ext.commands.Bot queued = False # Stop if this is a DM if payload.guild_id is None: return # Lock system if payload.message_id in self.locked_messages: queued = True while payload.message_id in self.locked_messages: await asyncio.sleep(0.5) self.locked_messages.append(payload.message_id) try: fetched = False # Find server config for server in self.fire_settings: if server[0] == payload.guild_id: react_minimum = server[1] emoji = server[2] channel_id = server[3] ignore_bots = True if int(server[4]) == 1 else False fetched = True # Stop if server has no config (fireboard isn't enabled) if not fetched: return # Stop if message is by Titanium if payload.message_author_id == self.bot.user.id: return # Stop if emoji doesn't match if str(payload.emoji) != emoji: return # --- Edit board message if it already exists --- if payload.message_id in [message[1] for message in self.fire_messages]: prev_react_count = [ message for message in self.fire_messages if message[1] == payload.message_id ][0][3] # Only fetch updated reaction count if I have queued if queued or prev_react_count is None: # Fetch message and channel try: msg_channel = await self.bot.fetch_channel(payload.channel_id) message = await msg_channel.fetch_message(payload.message_id) except discord.errors.NotFound: return # Stop if not enough reactions for reaction in message.reactions: if str(reaction.emoji) == emoji: react_count = reaction.count break if react_count < react_minimum: return else: react_count = None async with self.fireboard_pool.acquire() as sql: # Set updated react count if react_count is not None: await sql.execute( "UPDATE fireMessages SET reactionAmount = ? WHERE msgID = ?", ( react_count, payload.message_id, ), ) await self.refresh_fire_lists() else: await sql.execute( "UPDATE fireMessages SET reactionAmount = reactionAmount - 1 WHERE msgID = ?", (payload.message_id,), ) await self.refresh_fire_lists() # Get message from message list message = [ message for message in self.fire_messages if message[1] == payload.message_id ][0] # Get board message board_channel = await self.bot.fetch_channel(channel_id) board_message = await board_channel.fetch_message(message[2]) # Remove message if not enough reactions if message[3] < react_minimum: await board_message.delete() async with self.fireboard_pool.acquire() as sql: await sql.execute( "DELETE FROM fireMessages WHERE msgID = ?", (payload.message_id,), ) await sql.commit() await self.refresh_fire_lists() return # Workaround for lack of message author ID content = board_message.content content = content.replace( f"{prev_react_count} {emoji}", f"{message[3]} {emoji}" ) await board_message.edit(content=content) return # Stop if message is in a blacklisted channel if payload.channel_id in [ channel[1] for channel in self.fire_channel_blacklist ]: return # Stop if message is by a blacklisted role guild = await self.bot.fetch_guild(payload.guild_id) member = await guild.fetch_member(payload.user_id) if any( role[1] in [role.id for role in member.roles] for role in self.fire_role_blacklist ): return # Fetch message and channel try: msg_channel = await self.bot.fetch_channel(payload.channel_id) message = await msg_channel.fetch_message(payload.message_id) except discord.errors.NotFound: return # Stop if message is by a bot if ignore_bots and message.author.bot: return # Check if this is a thread if isinstance(msg_channel, discord.Thread): # Check if parent channel is NSFW if msg_channel.parent.nsfw: return else: # Stop if message is in an NSFW channel if message.channel.nsfw: return # Get reaction count react_count = 0 for reaction in message.reactions: if str(reaction.emoji) == emoji: react_count = reaction.count break # Stop if not enough reactions if react_count < react_minimum: return # --- Send message to fireboard --- # Create embed embed = discord.Embed(description=message.content, color=Color.random()) embed.set_author( name=message.author.name, icon_url=message.author.display_avatar.url ) embed.timestamp = message.created_at # Jump to message button view = View() view.add_item( discord.ui.Button( label="Jump to Message", url=message.jump_url, style=discord.ButtonStyle.url, ) ) embed_list = [embed] # Add reply embed if message.reference: try: reply_message = await msg_channel.fetch_message( message.reference.message_id ) reply_embed = discord.Embed( title="Replying To", description=reply_message.content, color=Color.random(), ) reply_embed.set_author( name=reply_message.author.name, icon_url=reply_message.author.display_avatar.url, ) reply_embed.timestamp = reply_message.created_at embed_list.insert(0, reply_embed) except discord.errors.NotFound: pass # Send message board_channel = await self.bot.fetch_channel(channel_id) board_message = await board_channel.send( content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", embeds=embed_list, view=view, files=[ await attachment.to_file() for attachment in message.attachments ], ) async with self.fireboard_pool.acquire() as sql: # Insert message to DB await sql.execute( "INSERT INTO fireMessages (serverID, msgID, boardMsgID, reactionAmount) VALUES (?, ?, ?, ?)", ( payload.guild_id, payload.message_id, board_message.id, react_count, ), ) await sql.commit() await self.refresh_fire_lists() except Exception as e: raise e finally: self.locked_messages.remove(payload.message_id) # Listen for message reaction clear @commands.Cog.listener() async def on_raw_reaction_clear(self, payload: discord.RawReactionClearEvent): self.bot: discord.ext.commands.Bot # Stop if this is a DM if payload.guild_id is None: return # Lock system if payload.message_id in self.locked_messages: while payload.message_id in self.locked_messages: await asyncio.sleep(0.5) self.locked_messages.append(payload.message_id) try: # Only trigger if message is already in the fireboard DB if payload.message_id in [message[1] for message in self.fire_messages]: # Find server config for server in self.fire_settings: if server[0] == payload.guild_id: channel_id = server[3] # Get guild try: guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) except discord.errors.NotFound: return # Get message channel channel: discord.abc.GuildChannel = await guild.fetch_channel( payload.channel_id ) # Get our message message: discord.Message = await channel.fetch_message( payload.message_id ) # See if board message is already present for fire_message in self.fire_messages: if fire_message[1] == message.id: async with self.fireboard_pool.acquire() as sql: try: # Delete message try: channel: discord.TextChannel = ( await guild.fetch_channel(channel_id) ) except discord.errors.NotFound: await sql.execute( "DELETE FROM fireSettings WHERE serverID = ?", (payload.guild_id,), ) await sql.commit() self.locked_messages.remove(payload.message_id) return board_message = await channel.fetch_message( fire_message[2] ) await board_message.delete() # Delete message from DB await sql.execute( "DELETE FROM fireMessages WHERE msgID = ?", (message.id,), ) await sql.commit() await self.refresh_fire_lists() return except discord.errors.NotFound: # Delete message from DB await sql.execute( "DELETE FROM fireMessages WHERE msgID = ?", (message.id,), ) await sql.commit() await self.refresh_fire_lists() return else: return except Exception as e: raise e finally: self.locked_messages.remove(payload.message_id) # Listen for specific emoji being cleared @commands.Cog.listener() async def on_raw_reaction_clear_emoji( self, payload: discord.RawReactionClearEmojiEvent ): self.bot: discord.ext.commands.Bot # Stop if this is a DM if payload.guild_id is None: return # Lock system if payload.message_id in self.locked_messages: while payload.message_id in self.locked_messages: await asyncio.sleep(0.5) self.locked_messages.append(payload.message_id) try: # Only trigger if message is already in the fireboard DB if payload.message_id in [message[1] for message in self.fire_messages]: for server in self.fire_settings: if server[0] == payload.guild_id: emoji = server[2] channel_id = server[3] # Only trigger if cleared emoji is our emoji if str(payload.emoji) == emoji: # Fetch server try: guild: discord.Guild = await self.bot.fetch_guild( payload.guild_id ) except discord.errors.NotFound: return # Get message channel channel: discord.abc.GuildChannel = await guild.fetch_channel( payload.channel_id ) # See if board message is already present for fire_message in self.fire_messages: if fire_message[1] == payload.message_id: async with self.fireboard_pool.acquire() as sql: try: # Fetch fireboard channel try: channel: discord.TextChannel = ( await guild.fetch_channel(channel_id) ) except discord.errors.NotFound: await sql.execute( "DELETE FROM fireSettings WHERE serverID = ?", (payload.guild_id,), ) await sql.commit() return # Delete message board_message = await channel.fetch_message( fire_message[2] ) await board_message.delete() # Delete message from DB await sql.execute( "DELETE FROM fireMessages WHERE msgID = ?", (payload.message_id,), ) await sql.commit() await self.refresh_fire_lists() return except discord.errors.NotFound: # Delete message from DB await sql.execute( "DELETE FROM fireMessages WHERE msgID = ?", (payload.message_id,), ) await sql.commit() await self.refresh_fire_lists() return else: return except Exception as e: raise e finally: self.locked_messages.remove(payload.message_id) # Listen for message being deleted @commands.Cog.listener() async def on_raw_message_delete(self, payload: discord.RawMessageDeleteEvent): self.bot: discord.ext.commands.Bot # Stop if this is a DM if payload.guild_id is None: return # Lock system if payload.message_id in self.locked_messages: while payload.message_id in self.locked_messages: await asyncio.sleep(0.5) self.locked_messages.append(payload.message_id) try: # Only trigger if message is already in the fireboard DB if payload.message_id in [message[1] for message in self.fire_messages]: # Fetch server config for server in self.fire_settings: if server[0] == payload.guild_id: channel_id = server[3] # Fetch server try: guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) except discord.errors.NotFound: return # Get message channel channel: discord.abc.GuildChannel = await guild.fetch_channel( payload.channel_id ) # See if board message is already present for fire_message in self.fire_messages: if fire_message[1] == payload.message_id: async with self.fireboard_pool.acquire() as sql: try: # Fetch fireboard channel try: channel: discord.TextChannel = ( await guild.fetch_channel(channel_id) ) except discord.errors.NotFound: await sql.execute( "DELETE FROM fireSettings WHERE serverID = ?", (payload.guild_id,), ) await sql.commit() return # Delete message board_message = await channel.fetch_message( fire_message[2] ) await board_message.delete() # Delete message from DB await sql.execute( "DELETE FROM fireMessages WHERE msgID = ?", (payload.message_id,), ) await sql.commit() await self.refresh_fire_lists() return except discord.errors.NotFound: # Delete message from DB await sql.execute( "DELETE FROM fireMessages WHERE msgID = ?", (payload.message_id,), ) await sql.commit() await self.refresh_fire_lists() return else: return except Exception as e: raise e finally: self.locked_messages.remove(payload.message_id) # Listen for message being edited @commands.Cog.listener() async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent): self.bot: discord.ext.commands.Bot # Stop if this is a DM if payload.guild_id is None: return # Lock system if payload.message_id in self.locked_messages: while payload.message_id in self.locked_messages: await asyncio.sleep(0.5) self.locked_messages.append(payload.message_id) try: # Only trigger if message is already in the fireboard DB if payload.message_id in [message[1] for message in self.fire_messages]: # Fetch server config for server in self.fire_settings: if server[0] == payload.guild_id: channel_id = server[3] # Fetch server try: guild: discord.Guild = await self.bot.fetch_guild(payload.guild_id) except discord.errors.NotFound: return # Get message channel channel: discord.abc.GuildChannel = await guild.fetch_channel( payload.channel_id ) # Get our message message: discord.Message = await channel.fetch_message( payload.message_id ) embed = discord.Embed(description=message.content, color=Color.random()) embed.set_author( name=message.author.name, icon_url=message.author.display_avatar.url ) embed.timestamp = message.created_at # Jump to message button view = View() view.add_item( discord.ui.Button( label="Jump to Message", url=message.jump_url, style=discord.ButtonStyle.url, ) ) embed_list = [embed] # Add reply embed if message.reference: try: reply_message = await channel.fetch_message( message.reference.message_id ) reply_embed = discord.Embed( title="Replying To", description=reply_message.content, color=Color.random(), ) reply_embed.set_author( name=reply_message.author.name, icon_url=reply_message.author.display_avatar.url, ) reply_embed.timestamp = reply_message.created_at embed_list.insert(0, reply_embed) except discord.errors.NotFound: pass try: channel: discord.TextChannel = await guild.fetch_channel(channel_id) except discord.errors.NotFound: async with self.fireboard_pool.acquire() as sql: await sql.execute( "DELETE FROM fireSettings WHERE serverID = ?", (payload.guild_id,), ) await sql.commit() return # Find previous fireboard message try: for fire_message in self.fire_messages: if ( fire_message[0] == payload.guild_id and fire_message[1] == payload.message_id ): # Edit with updated embed - reaction amount stays the same board_message = await channel.fetch_message(fire_message[2]) await board_message.edit( embeds=embed_list, attachments=message.attachments ) except discord.errors.NotFound: # Message not found async with self.fireboard_pool.acquire() as sql: # Delete message from DB await sql.execute( "DELETE FROM fireMessages WHERE msgID = ?", (payload.message_id,), ) await sql.commit() await self.refresh_fire_lists() return else: return except Exception as e: raise e finally: self.locked_messages.remove(payload.message_id) # Listen for fireboard channel delete @commands.Cog.listener() async def on_guild_channel_delete(self, channel: discord.abc.GuildChannel): # Only trigger if server has fireboard enabled if channel.guild.id in [guild[0] for guild in self.fire_settings]: for server in self.fire_settings: if server[0] == channel.guild.id: if server[3] == channel.id: async with self.fireboard_pool.acquire() as sql: # Delete fireboard config await sql.execute( "DELETE FROM fireMessages WHERE serverID = ?", (channel.guild.id,), ) await sql.execute( "DELETE FROM fireSettings WHERE serverID = ?", (channel.guild.id,), ) await sql.commit() await self.refresh_fire_lists() return else: return # Listen for server being left / deleted @commands.Cog.listener() async def on_guild_remove(self, guild: discord.Guild): # Only trigger if server has fireboard enabled if guild.id in [guild[0] for guild in self.fire_settings]: for server in self.fire_settings: if server[0] == guild.id: async with self.fireboard_pool.acquire() as sql: # Delete fireboard config await sql.execute( "DELETE FROM fireMessages WHERE serverID = ?", (guild.id,) ) await sql.execute( "DELETE FROM fireSettings WHERE serverID = ?", (guild.id,) ) await sql.commit() await self.refresh_fire_lists() return else: return # Command group setup context = discord.app_commands.AppCommandContext( guild=True, dm_channel=False, private_channel=False ) installs = discord.app_commands.AppInstallationType(guild=True, user=False) fireGroup = app_commands.Group( name="fireboard", description="Fireboard related commands.", allowed_contexts=context, allowed_installs=installs, ) # Random fireboard message command @fireGroup.command( name="random", description="Get a random message from the fireboard." ) @app_commands.describe( ephemeral="Optional: whether to send the command output as a dismissible message only visible to you. Defaults to false." ) async def random_fireboard( self, interaction: discord.Interaction, ephemeral: bool = False ): await interaction.response.defer(ephemeral=ephemeral) channel_id = None # Find server config for server in self.fire_settings: if server[0] == interaction.guild_id: channel_id = server[3] if channel_id is None: embed = discord.Embed( title="Error", description="Fireboard is not enabled in this server.", color=Color.red(), ) await interaction.followup.send(embed=embed, ephemeral=ephemeral) return # Fetch channel try: channel = await interaction.guild.fetch_channel(channel_id) except discord.errors.NotFound: embed = discord.Embed( title="Error", description="Can't find the fireboard channel. Please contact a server admin.", color=Color.red(), ) await interaction.followup.send(embed=embed, ephemeral=ephemeral) return # Fetch messages async with self.fireboard_pool.acquire() as sql: messages = await sql.fetchall( "SELECT * FROM fireMessages WHERE serverID = ?", (interaction.guild_id,) ) if not messages: embed = discord.Embed( title="Error", description="No messages found in the fireboard.", color=Color.red(), ) await interaction.followup.send(embed=embed, ephemeral=ephemeral) return else: while messages != []: message = random.choice(messages) try: board_message = await channel.fetch_message(message[2]) view = View().from_message(board_message) files = [ await attachment.to_file() for attachment in board_message.attachments ] await interaction.followup.send( content=board_message.content, embeds=board_message.embeds, view=view, ephemeral=ephemeral, files=files, allowed_mentions=discord.AllowedMentions.none(), ) return except discord.errors.NotFound: messages.remove(message) embed = discord.Embed( title="Error", description="No messages found in the fireboard.", color=Color.red(), ) await interaction.followup.send(embed=embed, ephemeral=ephemeral) # Command group setup context = discord.app_commands.AppCommandContext( guild=True, dm_channel=False, private_channel=False ) installs = discord.app_commands.AppInstallationType(guild=True, user=False) perms = discord.Permissions(manage_guild=True) fireSetupGroup = app_commands.Group( name="fireboard-setup", description="Control the fireboard.", allowed_contexts=context, allowed_installs=installs, default_permissions=perms, ) # Fireboard enable command @fireSetupGroup.command( name="enable", description="Enable the fireboard in the current channel." ) async def enable_fireboard(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) # Check fireboard status if interaction.guild.id in [guild[0] for guild in self.fire_settings]: embed = discord.Embed( title="Fireboard is already enabled.", color=Color.green() ) await interaction.followup.send(embed=embed, ephemeral=True) else: # Default settings react_minimum = 3 emoji = "🔥" channel_id = interaction.channel_id ignore_bots = True embed = discord.Embed( title="Fireboard", description="This channel has been configured as the server fireboard.", color=Color.random(), ) embed.set_footer(text="Feel free to delete this message!") try: channel = await interaction.guild.fetch_channel(channel_id) await channel.send(embed=embed) except discord.errors.Forbidden or discord.errors.NotFound: embed = discord.Embed( title="Error", description="Looks like I can't send messages in this channel. Check permissions and try again.", color=Color.random(), ) await interaction.followup.send(embed=embed, ephemeral=True) return async with self.fireboard_pool.acquire() as sql: # Insert to DB, refresh lists await sql.execute( "INSERT INTO fireSettings (serverID, reactionAmount, emoji, channelID, ignoreBots) VALUES (?, ?, ?, ?, ?)", ( interaction.guild_id, react_minimum, emoji, channel_id, ignore_bots, ), ) await sql.commit() await self.refresh_fire_lists() embed = discord.Embed( title="Enabled", description="Fireboard has been enabled in the current channel.", color=Color.green(), ) embed.add_field( name="Info", value=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", ) await interaction.followup.send(embed=embed, ephemeral=True) class ConfirmDisableView(View): def __init__(self): super().__init__(timeout=60) async def disable_fireboard( self, interaction: discord.Interaction, pool: asqlite.Pool ): async with pool.acquire() as sql: try: await sql.execute( "DELETE FROM fireMessages WHERE serverID = ?", (interaction.guild_id,), ) await sql.execute( "DELETE FROM fireSettings WHERE serverID = ?", (interaction.guild_id,), ) await sql.execute( "DELETE FROM fireChannelBlacklist WHERE serverID = ?", (interaction.guild_id,), ) await sql.execute( "DELETE FROM fireRoleBlacklist WHERE serverID = ?", (interaction.guild_id,), ) await sql.commit() return True except Exception: return False @discord.ui.button(label="Disable", style=discord.ButtonStyle.red) async def confirm( self, interaction: discord.Interaction, button: discord.ui.Button ): await interaction.response.defer(ephemeral=True) success = await self.disable_fireboard(interaction, self.pool) if success: embed = discord.Embed( title="Done!", description="Fireboard was disabled.", color=Color.green(), ) await self.cog.refresh_fire_lists() # pylint: disable=no-member else: embed = discord.Embed( title="Error", description="Failed to disable fireboard.", color=Color.red(), ) await interaction.edit_original_response(embed=embed, view=None) self.stop() async def on_timeout(self): for item in self.children: item.disabled = True embed = discord.Embed( title="Timeout", description="You didn't press the button in time.", color=Color.red(), ) await self.message.edit(embed=embed, view=self) # Fireboard disable command @fireSetupGroup.command(name="disable", description="Disable the server fireboard.") async def disable_fireboard(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) if interaction.guild_id in [guild[0] for guild in self.fire_settings]: view = self.ConfirmDisableView() view.pool = self.fireboard_pool view.cog = self embed = discord.Embed( title="Are you sure?", description="All data about this server's fireboard will be deleted. This cannot be undone!", color=Color.orange(), ) message = await interaction.followup.send( embed=embed, view=view, ephemeral=True, wait=True ) view.message = message else: await interaction.followup.send( "Fireboard is not enabled in this server!", ephemeral=True ) # Fireboard server info command @fireSetupGroup.command( name="info", description="View fireboard config for this server." ) async def fireboard_info(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) # Check fireboard status if interaction.guild.id in [guild[0] for guild in self.fire_settings]: # Fetch server settings for server in self.fire_settings: if server[0] == interaction.guild_id: react_minimum = server[1] emoji = server[2] channel_id = server[3] ignore_bots = True if int(server[4]) == 1 else False embed = discord.Embed( title="Server Fireboard Settings", description=f"**Reaction Requirement:** `{react_minimum} reactions`\n**Fireboard Channel:** <#{channel_id}>\n**Emoji:** {emoji}\n**Ignore Bots:** `{ignore_bots}`", color=Color.random(), ) await interaction.followup.send(embed=embed, ephemeral=True) else: embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) await interaction.followup.send(embed=embed, ephemeral=True) # Fireboard set emoji command @fireSetupGroup.command(name="emoji", description="Set a custom fireboard emoji.") async def fireboard_emoji(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=False) # Check fireboard status if interaction.guild.id in [guild[0] for guild in self.fire_settings]: embed = discord.Embed( title="Waiting for Reaction", description="🔄 React with this message with your target emoji to set the fireboard emoji.", color=Color.orange(), ) msg = await interaction.followup.send(embed=embed, ephemeral=False) def check(reaction, user): return user == interaction.user and reaction.message.id == msg.id # Wait for a reaction try: reaction, user = await self.bot.wait_for( "reaction_add", timeout=60.0, check=check ) reaction: discord.Reaction = reaction async with self.fireboard_pool.acquire() as sql: # Change emoji in DB, refresh lists await sql.execute( "UPDATE fireSettings SET emoji = ? WHERE serverID = ?", ( str(reaction.emoji), interaction.guild_id, ), ) await sql.commit() embed = discord.Embed( title="Emoji Set", description=f"Set emoji to **{str(reaction.emoji)}.**", color=Color.green(), ) await self.refresh_fire_lists() await interaction.edit_original_response(embed=embed) except asyncio.TimeoutError: # Timed out embed = discord.Embed( title="Timed Out", description="You didn't react in time.", color=Color.red(), ) await interaction.edit_original_response(embed=embed) else: embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) await interaction.followup.send(embed=embed, ephemeral=False) # Fireboard set channel command @fireSetupGroup.command( name="channel", description="Set the channel for fireboard messages to be sent in.", ) async def fireboard_channel( self, interaction: discord.Interaction, channel: discord.TextChannel ): await interaction.response.defer(ephemeral=True) # Check fireboard status if interaction.guild.id in [guild[0] for guild in self.fire_settings]: embed = discord.Embed( title="Fireboard", description="This channel has been configured as the server fireboard.", color=Color.random(), ) embed.set_footer(text="Feel free to delete this message!") try: await channel.send(embed=embed) except discord.errors.NotFound: embed = discord.Embed( title="Error", description="Looks like I can't find that channel. Check permissions and try again.", color=Color.random(), ) await interaction.followup.send(embed=embed, ephemeral=True) return except discord.errors.Forbidden as e: embed = discord.Embed( title="Error", description="Looks like I can't send messages in that channel. Check permissions and try again.", color=Color.red(), ) await interaction.followup.send(embed=embed, ephemeral=True) return async with self.fireboard_pool.acquire() as sql: # Update channel in DB, refresh lists await sql.execute( "UPDATE fireSettings SET channelID = ? WHERE serverID = ?", ( channel.id, interaction.guild_id, ), ) await sql.commit() await self.refresh_fire_lists() embed = discord.Embed( title="Channel Set", description=f"Fireboard channel has been set to **{channel.mention}.**", color=Color.green(), ) await interaction.followup.send(embed=embed, ephemeral=True) else: embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) await interaction.followup.send(embed=embed, ephemeral=True) # Fireboard set requirement command @fireSetupGroup.command( name="requirement", description="Set required reaction amount for message to be posted on the fireboard.", ) async def fireboard_requirement( self, interaction: discord.Interaction, amount: int ): await interaction.response.defer(ephemeral=True) # Check fireboard status if interaction.guild.id in [guild[0] for guild in self.fire_settings]: embed = discord.Embed( title="Set", description=f"Reaction requirement has been set to **{amount} reactions.**", color=Color.green(), ) async with self.fireboard_pool.acquire() as sql: # Update reaction requirement in DB, refresh lists await sql.execute( "UPDATE fireSettings SET reactionAmount = ? WHERE serverID = ?", ( amount, interaction.guild_id, ), ) await sql.commit() await self.refresh_fire_lists() await interaction.followup.send(embed=embed, ephemeral=True) else: embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) await interaction.followup.send(embed=embed, ephemeral=True) # Fireboard ignore bots command @fireSetupGroup.command( name="ignore-bots", description="Whether bot messages are ignored in the fireboard. Defaults to true.", ) async def fireboard_ignore_bots( self, interaction: discord.Interaction, value: bool ): await interaction.response.defer(ephemeral=True) # Check fireboard status if interaction.guild.id in [guild[0] for guild in self.fire_settings]: embed = discord.Embed( title="Set", description=f"Bot messages will **{'be ignored.' if value else 'not be ignored.'}**", color=Color.green(), ) async with self.fireboard_pool.acquire() as sql: # Update setting in DB, refresh lists await sql.execute( "UPDATE fireSettings SET ignoreBots = ? WHERE serverID = ?", ( value, interaction.guild_id, ), ) await sql.commit() await self.refresh_fire_lists() await interaction.followup.send(embed=embed, ephemeral=True) else: embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) await interaction.followup.send(embed=embed, ephemeral=True) # Fireboard role blacklist @fireSetupGroup.command( name="channel-blacklist", description="Toggle the blacklist for a channel. NSFW channels are always blacklisted.", ) async def fireboard_channel_blacklist( self, interaction: discord.Interaction, channel: discord.abc.GuildChannel ): await interaction.response.defer(ephemeral=True) # Check fireboard status if interaction.guild_id in [guild[0] for guild in self.fire_settings]: async with self.fireboard_pool.acquire() as sql: if channel.id in [ channelEntry[1] for channelEntry in self.fire_channel_blacklist ]: await sql.execute( "DELETE FROM fireChannelBlacklist WHERE serverID = ? AND channelID = ?", ( interaction.guild_id, channel.id, ), ) await sql.commit() await self.refresh_fire_lists() embed = discord.Embed( title="Set", description=f"Removed {channel.mention} from the channel blacklist.", ) await interaction.followup.send(embed=embed, ephemeral=True) else: await sql.execute( "INSERT INTO fireChannelBlacklist (serverID, channelID) VALUES (?, ?)", ( interaction.guild_id, channel.id, ), ) await sql.commit() await self.refresh_fire_lists() embed = discord.Embed( title="Set", description=f"Added {channel.mention} to the channel blacklist.", ) await interaction.followup.send(embed=embed, ephemeral=True) else: embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) await interaction.followup.send(embed=embed, ephemeral=True) # Fireboard role blacklist @fireSetupGroup.command( name="role-blacklist", description="Toggle the blacklist for a role." ) async def fireboard_role_blacklist( self, interaction: discord.Interaction, role: discord.Role ): await interaction.response.defer(ephemeral=True) # Check fireboard status if interaction.guild_id in [guild[0] for guild in self.fire_settings]: async with self.fireboard_pool.acquire() as sql: if role.id in [roleEntry[1] for roleEntry in self.fire_role_blacklist]: await sql.execute( "DELETE FROM fireRoleBlacklist WHERE serverID = ? AND roleID = ?", ( interaction.guild_id, role.id, ), ) await sql.commit() await self.refresh_fire_lists() embed = discord.Embed( title="Set", description=f"Removed {role.mention} from the role blacklist.", ) await interaction.followup.send(embed=embed, ephemeral=True) else: await sql.execute( "INSERT INTO fireRoleBlacklist (serverID, roleID) VALUES (?, ?)", ( interaction.guild_id, role.id, ), ) await sql.commit() await self.refresh_fire_lists() embed = discord.Embed( title="Set", description=f"Added {role.mention} to the role blacklist.", ) await interaction.followup.send(embed=embed, ephemeral=True) else: embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) await interaction.followup.send(embed=embed, ephemeral=True) # Fireboard role blacklist @fireSetupGroup.command( name="blacklists", description="View this server's role and channel blacklists." ) async def fireboard_blacklists(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) # Check fireboard status if interaction.guild_id in [guild[0] for guild in self.fire_settings]: class BlacklistViewer(View): def __init__(self): super().__init__(timeout=240) self.fire_channel_blacklist: list self.fire_role_blacklist: list self.interaction: discord.Interaction async def on_timeout(self) -> None: for item in self.children: item.disabled = True await self.interaction.edit_original_response(view=self) @discord.ui.button( label="Role Blacklist", style=discord.ButtonStyle.gray, row=0, custom_id="role", disabled=True, ) async def role( self, interaction: discord.Interaction, button: discord.ui.Button ): await interaction.response.defer(ephemeral=True) for item in self.children: if item.custom_id == "channel": item.disabled = False else: item.disabled = True my_roles = [] for role in self.fire_role_blacklist: if role[0] == interaction.guild_id: my_roles.append(f"<@&{role[1]}>") if my_roles != []: embed = discord.Embed( title="Role Blacklist", description="\n".join(my_roles), color=Color.random(), ) await interaction.edit_original_response(embed=embed, view=self) else: embed = discord.Embed( title="Role Blacklist", description="No roles have been blacklisted.", color=Color.random(), ) await interaction.edit_original_response(embed=embed, view=self) @discord.ui.button( label="Channel Blacklist", style=discord.ButtonStyle.gray, row=0, custom_id="channel", ) async def channel( self, interaction: discord.Interaction, button: discord.ui.Button ): await interaction.response.defer(ephemeral=True) for item in self.children: if item.custom_id == "role": item.disabled = False else: item.disabled = True my_channels = [] for channel in self.fire_channel_blacklist: if channel[0] == interaction.guild_id: my_channels.append(f"<#{channel[1]}>") if my_channels != []: embed = discord.Embed( title="Channel Blacklist", description="\n".join(my_channels), color=Color.random(), ) await interaction.edit_original_response(embed=embed, view=self) else: embed = discord.Embed( title="Channel Blacklist", description="No channels have been blacklisted.", color=Color.random(), ) await interaction.edit_original_response(embed=embed, view=self) view_instance = BlacklistViewer() view_instance.fire_channel_blacklist = self.fire_channel_blacklist view_instance.fire_role_blacklist = self.fire_role_blacklist view_instance.interaction = interaction my_roles = [] for role in self.fire_role_blacklist: if role[0] == interaction.guild_id: my_roles.append(f"<@&{role[1]}>") if my_roles != []: embed = discord.Embed( title="Role Blacklist", description="\n".join(my_roles), color=Color.random(), ) await interaction.followup.send( embed=embed, view=view_instance, ephemeral=True ) else: embed = discord.Embed( title="Role Blacklist", description="No roles have been blacklisted.", color=Color.random(), ) await interaction.followup.send( embed=embed, view=view_instance, ephemeral=True ) else: embed = discord.Embed(title="Fireboard is not enabled.", color=Color.red()) await interaction.followup.send(embed=embed, ephemeral=True) async def setup(bot):import asyncio import random import json import os import discord from discord import Color, app_commands from discord.ext import commands from discord.ui import View FIREBOARD_PATH = "assets/cogs/files/fireboard.json" def load_data(): if not os.path.exists(FIREBOARD_PATH): return {} with open(FIREBOARD_PATH, "r") as f: return json.load(f) def save_data(data): os.makedirs(os.path.dirname(FIREBOARD_PATH), exist_ok=True) with open(FIREBOARD_PATH, "w") as f: json.dump(data, f, indent=2) class Fireboard(commands.Cog): def __init__(self, bot): self.bot = bot self.data = load_data() self.locked_messages = [] def save(self): save_data(self.data) def get_guild(self, guild_id): return self.data.setdefault(str(guild_id), { "settings": None, "messages": [], "role_blacklist": [], "channel_blacklist": [] }) @commands.Cog.listener() async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): if payload.guild_id is None: return guild_data = self.get_guild(payload.guild_id) settings = guild_data["settings"] if not settings: return react_minimum = settings["reactionAmount"] emoji = settings["emoji"] channel_id = settings["channelID"] ignore_bots = settings["ignoreBots"] if str(payload.emoji) != emoji: return if payload.channel_id in guild_data["channel_blacklist"]: return if payload.message_id in self.locked_messages: return self.locked_messages.append(payload.message_id) try: channel = await self.bot.fetch_channel(payload.channel_id) message = await channel.fetch_message(payload.message_id) if ignore_bots and message.author.bot: return if hasattr(message.channel, "nsfw") and message.channel.nsfw: return react_count = 0 for reaction in message.reactions: if str(reaction.emoji) == emoji: react_count = reaction.count break if react_count < react_minimum: return # Already on fireboard? for m in guild_data["messages"]: if m["msgID"] == payload.message_id: return # Send to fireboard board_channel = await self.bot.fetch_channel(channel_id) embed = discord.Embed(description=message.content, color=Color.random()) embed.set_author(name=message.author.name, icon_url=message.author.display_avatar.url) embed.timestamp = message.created_at view = View() view.add_item(discord.ui.Button(label="Jump to Message", url=message.jump_url, style=discord.ButtonStyle.url)) files = [await a.to_file() for a in message.attachments] board_message = await board_channel.send( content=f"**{react_count} {emoji}** | {message.author.mention} | <#{payload.channel_id}>", embed=embed, view=view, files=files ) guild_data["messages"].append({ "msgID": payload.message_id, "boardMsgID": board_message.id, "reactionAmount": react_count }) self.save() finally: if payload.message_id in self.locked_messages: self.locked_messages.remove(payload.message_id) @commands.Cog.listener() async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): if payload.guild_id is None: return guild_data = self.get_guild(payload.guild_id) settings = guild_data["settings"] if not settings: return emoji = settings["emoji"] channel_id = settings["channelID"] if str(payload.emoji) != emoji: return for m in guild_data["messages"]: if m["msgID"] == payload.message_id: channel = await self.bot.fetch_channel(channel_id) try: board_message = await channel.fetch_message(m["boardMsgID"]) await board_message.delete() except Exception: pass guild_data["messages"].remove(m) self.save() break @app_commands.command(name="fireboard_enable", description="Enable the fireboard in this channel.") async def fireboard_enable(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) guild_data = self.get_guild(interaction.guild_id) if guild_data["settings"]: await interaction.followup.send("Fireboard is already enabled.", ephemeral=True) return guild_data["settings"] = { "reactionAmount": 3, "emoji": "🔥", "channelID": interaction.channel_id, "ignoreBots": True } self.save() await interaction.followup.send("Fireboard enabled in this channel.", ephemeral=True) @app_commands.command(name="fireboard_disable", description="Disable the fireboard.") async def fireboard_disable(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) guild_data = self.get_guild(interaction.guild_id) guild_data["settings"] = None guild_data["messages"] = [] self.save() await interaction.followup.send("Fireboard disabled.", ephemeral=True) @app_commands.command(name="fireboard_info", description="Show fireboard settings.") async def fireboard_info(self, interaction: discord.Interaction): await interaction.response.defer(ephemeral=True) guild_data = self.get_guild(interaction.guild_id) settings = guild_data["settings"] if not settings: await interaction.followup.send("Fireboard is not enabled.", ephemeral=True) return embed = discord.Embed( title="Fireboard Settings", description=f"**Reaction Requirement:** `{settings['reactionAmount']}`\n" f"**Fireboard Channel:** <#{settings['channelID']}>\n" f"**Emoji:** {settings['emoji']}\n" f"**Ignore Bots:** `{settings['ignoreBots']}`", color=Color.random() ) await interaction.followup.send(embed=embed, ephemeral=True) async def setup(bot): await bot.add_cog(Fireboard(bot))