From 9c64e574fe36150e1c9b9548d47d8795163d5322 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 20 Dec 2023 21:02:04 -0500 Subject: [PATCH 1/3] Add new table to record bot presences. --- database_setup/database.sql | 5 +++++ database_setup/sql_data_classes.py | 1 + 2 files changed, 6 insertions(+) diff --git a/database_setup/database.sql b/database_setup/database.sql index 4a66960..8a2f136 100644 --- a/database_setup/database.sql +++ b/database_setup/database.sql @@ -33,4 +33,9 @@ CREATE TABLE UserBirthday ( UserId bigint unsigned not null, Birthday varchar(5) not null, PRIMARY KEY (UserId) +); + +CREATE TABLE Presences ( + Id integer PRIMARY KEY, + Presence varchar(100) not null ); \ No newline at end of file diff --git a/database_setup/sql_data_classes.py b/database_setup/sql_data_classes.py index 5f4897a..cd6406e 100644 --- a/database_setup/sql_data_classes.py +++ b/database_setup/sql_data_classes.py @@ -15,3 +15,4 @@ ElectionInformation = Base.classes.ElectionInformation CouncilMembers = Base.classes.CouncilMembers Votes = Base.classes.Votes UserBirthday = Base.classes.UserBirthday +Presences = Base.classes.Presences From 09ff620e51cfb7cb9b459fcd732a041bf658f034 Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 20 Dec 2023 21:24:44 -0500 Subject: [PATCH 2/3] Add loop for changing presence. --- cogs/loops/__init__.py | 4 +++- cogs/loops/bot_presence.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 cogs/loops/bot_presence.py diff --git a/cogs/loops/__init__.py b/cogs/loops/__init__.py index 20564de..dc7c70d 100644 --- a/cogs/loops/__init__.py +++ b/cogs/loops/__init__.py @@ -4,7 +4,8 @@ Initializing code for all loops. from nextcord.ext import commands -from .daily_loop import DailyLoop +from cogs.loops.bot_presence import BotPresence +from cogs.loops.daily_loop import DailyLoop def setup(bot: commands.Bot): @@ -12,4 +13,5 @@ def setup(bot: commands.Bot): Adds functions to bot. """ + bot.add_cog(BotPresence(bot)) bot.add_cog(DailyLoop(bot)) diff --git a/cogs/loops/bot_presence.py b/cogs/loops/bot_presence.py new file mode 100644 index 0000000..f026809 --- /dev/null +++ b/cogs/loops/bot_presence.py @@ -0,0 +1,48 @@ +""" +Functions for controlling the bot's presence. Also contains the loop. +""" + +import nextcord +from nextcord.ext import commands, tasks +from sqlalchemy import func, select +from sqlalchemy.orm import Session + +from database_setup.sql_data_classes import Presences, engine + + +class BotPresence(commands.Cog, name="Bot Presence"): + """Class for bot presence functions.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.Cog.listener() + async def on_guild_join( + self, guild: nextcord.Guild + ): # pylint: disable=unused-argument + """Listener for Guild Join. Updated presence on guild join.""" + await self.change_presence() + + @commands.Cog.listener() + async def on_ready(self): + """Starts the loop when bot is ready.""" + await self.bot.wait_until_ready() + # pylint doesn't recognize that periodic_change_presense is subclassed from tasks.loop. + self.periodic_change_presence.start() # pylint: disable=no-member + + @tasks.loop(minutes=5) + async def periodic_change_presence(self): + """Loops every 5 minutes and changes presence.""" + await self.change_presence() + + async def change_presence(self): + """Randomly chooses a presence and updates bot's presence.""" + + with Session(engine) as session: + presence = session.scalar( + select(Presences.Presence).order_by(func.random()) + ) + + await self.bot.change_presence( + activity=nextcord.CustomActivity(name="Custom Status", state=presence) + ) From fbd8419ccf6e21e7056f139d1fac9ec515dbad2e Mon Sep 17 00:00:00 2001 From: imcompiling Date: Wed, 20 Dec 2023 21:45:20 -0500 Subject: [PATCH 3/3] Add the add presence request. --- cogs/commands/request.py | 117 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 9 deletions(-) diff --git a/cogs/commands/request.py b/cogs/commands/request.py index ce1fe2d..407196b 100644 --- a/cogs/commands/request.py +++ b/cogs/commands/request.py @@ -4,8 +4,10 @@ Contains commands and views for request commands. import nextcord from nextcord.ext import commands +from sqlalchemy.orm import Session from config import DakinyConfig +from database_setup.sql_data_classes import Presences, engine class RequestCog(commands.Cog, name="Request Commands"): @@ -25,12 +27,46 @@ class RequestCog(commands.Cog, name="Request Commands"): Parent slash command for help. Never called. """ + @request.subcommand( + description="Request to add a presence to the bot's presence list." + ) + async def add_presence( + self, + interaction: nextcord.Interaction, + presence: str = nextcord.SlashOption( + required=True, + min_length=1, + max_length=100, + description="The presence you want to add.", + ), + ): + """ + Sends a message to admins to request to add a presence. + + Args: + interaction (nextcord.Interaction): Interaction that invoked the command. + presence (str): The presence to add. + """ + + config: DakinyConfig = self.bot.config + content = f'<@{interaction.user.id}> would like to add presence"{presence}."' + view = PresenceChange(presence) + await self.bot.get_channel(config.params.admin_channel_id).send( + content=content, view=view + ) + await interaction.response.send_message( + "Request sent to admins.", ephemeral=True + ) + @request.subcommand(description="Request to change your Dakiny nickname") async def change_nickname( self, interaction: nextcord.Interaction, new_nickname: str = nextcord.SlashOption( - required=True, min_length=2, max_length=32, description="Your requested new nickname." + required=True, + min_length=2, + max_length=32, + description="Your requested new nickname.", ), ): """ @@ -44,8 +80,12 @@ class RequestCog(commands.Cog, name="Request Commands"): config: DakinyConfig = self.bot.config content = f'<@{interaction.user.id}> would like to change their username to "{new_nickname}."' view = UsernameRequest(interaction.user.id, new_nickname) - await self.bot.get_channel(config.params.admin_channel_id).send(content=content, view=view) - await interaction.response.send_message("Request sent to admins.", ephemeral=True) + await self.bot.get_channel(config.params.admin_channel_id).send( + content=content, view=view + ) + await interaction.response.send_message( + "Request sent to admins.", ephemeral=True + ) class UsernameRequest(nextcord.ui.View): @@ -53,13 +93,19 @@ class UsernameRequest(nextcord.ui.View): View provides approve and deny buttons and action for a username change request. """ - def __init__(self, user_id: int, new_nickname: str, *, timeout: float | None = 180, auto_defer: bool = True): - super().__init__(timeout=timeout, auto_defer=auto_defer) + def __init__( + self, + user_id: int, + new_nickname: str, + ): + super().__init__(timeout=None, auto_defer=True) self.user_id = user_id self.new_nickname = new_nickname @nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green) - async def approve(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): + async def approve( + self, button: nextcord.ui.Button, interaction: nextcord.Interaction + ): """ Approve button for a nickname change request. Changes the target's nickname to self.new_nickname. Catches an error if the target as a higher role than the bot. @@ -73,7 +119,10 @@ class UsernameRequest(nextcord.ui.View): if user is not None: try: await user.edit(nick=self.new_nickname) - content = interaction.message.content + f"\nApproved by <@{interaction.user.id}>." + content = ( + interaction.message.content + + f"\nApproved by <@{interaction.user.id}>." + ) await interaction.response.edit_message(content=content, view=None) except nextcord.errors.Forbidden: await interaction.response.send_message( @@ -84,10 +133,13 @@ class UsernameRequest(nextcord.ui.View): interaction.message.content + "\n\nFailed. User has too high permissions for bot to change nickname." ) - await interaction.followup.edit_message(interaction.message.id, content=content, view=None) + await interaction.followup.edit_message( + interaction.message.id, content=content, view=None + ) else: await interaction.response.edit_message( - content="An error occured while changing the user's nickname.", view=None + content="An error occured while changing the user's nickname.", + view=None, ) @nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red) @@ -101,3 +153,50 @@ class UsernameRequest(nextcord.ui.View): """ content = interaction.message.content + f"\nDenied by <@{interaction.user.id}>." await interaction.response.edit_message(content=content, view=None) + + +class PresenceChange(nextcord.ui.View): + """ + View provides approve and deny buttons and action for a username change request. + """ + + def __init__( + self, + presence: str, + ): + super().__init__(timeout=None, auto_defer=True) + self.presence = presence + + @nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green) + async def approve( + self, button: nextcord.ui.Button, interaction: nextcord.Interaction + ): + """ + Approve button for a presence add request. Adds the presence to the database. + Removes the view from the interaction message. + + Args: + button (nextcord.ui.Button): Unused. + interaction (nextcord.Interaction): The interaction of the user clicking the button. + """ + + with Session(engine) as session: + session.add(Presences(Presence=self.presence)) + session.commit() + + content = ( + interaction.message.content + f"\nApproved by <@{interaction.user.id}>." + ) + await interaction.response.edit_message(content=content, view=None) + + @nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red) + async def deny(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): + """ + Deny button for a presence add request. Just removes the view and logs who denied the request. + + Args: + button (nextcord.ui.Button): Unused. + interaction (nextcord.Interaction): The interaction of the user clicking the button. + """ + content = interaction.message.content + f"\nDenied by <@{interaction.user.id}>." + await interaction.response.edit_message(content=content, view=None)