countdown-bot

A Discord bot that runs countdown games and generates analytics
git clone https://git.ashermorgan.net/countdown-bot/
Log | Files | Refs | README

commit 3961e7ffe852ab629c329bf5d5ee05252eb3a4de
parent c15ffe9b4ce2879587d5524014d495f99b2445a8
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Wed, 24 Apr 2024 18:26:43 -0700

Update activate, deactivate, and reload commands

Diffstat:
Mcountdown_bot/bot.py | 5++++-
Mcountdown_bot/botUtilities.py | 10+++++-----
Mcountdown_bot/utilitiesCog.py | 66++++++++++++++++++++++++++++++------------------------------------
Mmodels/tables.sql | 12+++++++++---
Mmodels/utilities.sql | 28++++++++++++++++++++++++++++
5 files changed, 76 insertions(+), 45 deletions(-)

diff --git a/countdown_bot/bot.py b/countdown_bot/bot.py @@ -29,8 +29,8 @@ class CountdownBot(commands.Bot): async def setup_hook(self): + await self.add_cog(utilitiesCog.Utilities(self, self.databaseSessionMaker, self.db_connection)) await self.add_cog(analyticsCog.Analytics(self, self.databaseSessionMaker)) - await self.add_cog(utilitiesCog.Utilities(self, self.databaseSessionMaker)) @@ -80,6 +80,9 @@ class CountdownBot(commands.Bot): async def on_command_error(self, ctx, error): + # Rollback database transaction + self.db_connection.rollback() + # Send error embed embed=discord.Embed(title=":warning: Error", description=str(error), color=COLORS["error"]) if (isinstance(error, commands.CommandNotFound)): diff --git a/countdown_bot/botUtilities.py b/countdown_bot/botUtilities.py @@ -166,7 +166,7 @@ def isCountdown(cur, id): """ cur.execute("CALL isCountdown(%s, null);", - (ctx.channel.id,)) + (id,)) return cur.fetchone()[0] @@ -324,17 +324,17 @@ async def loadCountdown(bot, countdown): The bot to load messages with cur : psycopg.cursor The database cursor - countdown : Countdown - The countdown to load messages for + countdown : int + The ID of the countdown to load messages for """ with bot.db_connection.cursor() as cur: # Clear countdown - cur.execute("CALL clearCountdown(%s);", (countdown.id,)) + cur.execute("CALL clearCountdown(%s);", (countdown,)) # Get Discord messages messages = [message async for message in - bot.get_channel(countdown.id).history(limit=10100)] + bot.get_channel(countdown).history(limit=10100)] messages.reverse() # Add messages to countdown diff --git a/countdown_bot/utilitiesCog.py b/countdown_bot/utilitiesCog.py @@ -3,15 +3,16 @@ import discord from discord.ext import commands # Import modules -from .botUtilities import COLORS, CommandError, getContextCountdown, getCountdown, loadCountdown +from .botUtilities import COLORS, CommandError, isCountdown, getContextCountdown, getCountdown, loadCountdown from .models import Countdown, Prefix, Reaction class Utilities(commands.Cog): - def __init__(self, bot, databaseSessionMaker): + def __init__(self, bot, databaseSessionMaker, db_connection): self.bot = bot self.databaseSessionMaker = databaseSessionMaker + self.db_connection = db_connection self.bot.remove_command("help") @@ -22,9 +23,9 @@ class Utilities(commands.Cog): Turns a channel into a countdown """ - with self.databaseSessionMaker() as session: + with self.db_connection.cursor() as cur: # Check if channel is already a countdown - if (getCountdown(session, ctx.channel.id)): + if (isCountdown(cur, ctx.channel.id)): raise CommandError("This channel is already a countdown") # Check if channel is a DM @@ -36,14 +37,8 @@ class Utilities(commands.Cog): raise CommandError("You must be an administrator to turn a channel into a countdown") # Create countdown - countdown = Countdown( - id = ctx.channel.id, - server_id = ctx.channel.guild.id, - timezone = 0, - prefixes = [Prefix(countdown_id=ctx.channel.id, value=x) for x in self.bot.prefixes], - reactions = [], - messages = [], - ) + cur.execute("CALL createCountdown(%s, %s, %s);", + (ctx.channel.id, ctx.channel.guild.id, self.bot.prefixes[0])) # Send initial response self.bot.logger.info(f"Activated {self.bot.get_channel(ctx.channel.id)} (ID {ctx.channel.id}) as a countdown") @@ -51,9 +46,8 @@ class Utilities(commands.Cog): msg = await ctx.send(embed=embed) # Load countdown - await loadCountdown(self.bot, countdown) - session.add(countdown) - session.commit() + await loadCountdown(self.bot, ctx.channel.id) + self.db_connection.commit() # Send final response embed = discord.Embed(title=":white_check_mark: Countdown Activated", description="This channel is now a countdown\nYou may start counting!", color=COLORS["embed"]) @@ -136,10 +130,9 @@ class Utilities(commands.Cog): Deactivates a countdown channel """ - with self.databaseSessionMaker() as session: + with self.db_connection.cursor() as cur: # Check if channel isn't a countdown - countdown = getCountdown(session, ctx.channel.id) - if (not countdown): + if (not isCountdown(cur, ctx.channel.id)): raise CommandError("This channel isn't a countdown") # Check if user isn't authorized @@ -147,8 +140,9 @@ class Utilities(commands.Cog): raise CommandError("You must be an administrator to deactivate a countdown channel") # Delete countdown - session.delete(countdown) - session.commit() + cur.execute("CALL deleteCountdown(%s);", + (ctx.channel.id,)) + self.db_connection.commit() # Send response self.bot.logger.info(f"Deactivated {self.bot.get_channel(ctx.channel.id)} (ID {ctx.channel.id}) as a countdown") @@ -402,20 +396,20 @@ class Utilities(commands.Cog): Reloads the countdown cache """ - with self.databaseSessionMaker() as session: - countdown = getCountdown(session, ctx.channel.id) - if (countdown): - # Send initial response - embed = discord.Embed(title=":clock3: Reloading Countdown Cache", description="Please wait to continue counting", color=COLORS["embed"]) - msg = await ctx.channel.send(embed=embed) - - # Reload messages - await loadCountdown(self.bot, countdown) - session.commit() - - # Send final response - self.bot.logger.info(f"Reloaded messages from {self.bot.get_channel(ctx.channel.id)} (ID {ctx.channel.id})") - embed = discord.Embed(title=":white_check_mark: Countdown Cache Reloaded", description="Done! You may continue counting!", color=COLORS["embed"]) - await msg.edit(embed=embed) - else: + with self.db_connection.cursor() as cur: + # Check if channel isn't a countdown + if (not isCountdown(cur, ctx.channel.id)): raise CommandError("Countdown not found\nThis command must be used in a countdown channel") + + # Send initial response + embed = discord.Embed(title=":clock3: Reloading Countdown Cache", description="Please wait to continue counting", color=COLORS["embed"]) + msg = await ctx.channel.send(embed=embed) + + # Reload messages + await loadCountdown(self.bot, ctx.channel.id) + self.db_connection.commit() + + # Send final response + self.bot.logger.info(f"Reloaded messages from {self.bot.get_channel(ctx.channel.id)} (ID {ctx.channel.id})") + embed = discord.Embed(title=":white_check_mark: Countdown Cache Reloaded", description="Done! You may continue counting!", color=COLORS["embed"]) + await msg.edit(embed=embed) diff --git a/models/tables.sql b/models/tables.sql @@ -18,7 +18,9 @@ CREATE TABLE messages ( userID BIGINT NOT NULL, -- The author's Discord user ID value INT NOT NULL, -- The message's numeric value timestamp TIMESTAMPTZ NOT NULL, -- The message timestamp - FOREIGN KEY (countdownID) REFERENCES countdowns(countdownID) + FOREIGN KEY (countdownID) + REFERENCES countdowns(countdownID) + ON DELETE CASCADE ); -- Records bot command prefixes @@ -26,7 +28,9 @@ CREATE table prefixes ( prefixID SERIAL PRIMARY KEY, -- The prefix ID countdownID BIGINT NOT NULL, -- The countdown ID value VARCHAR(8) NOT NULL, -- The prefix - FOREIGN KEY (countdownID) REFERENCES countdowns(countdownID) + FOREIGN KEY (countdownID) + REFERENCES countdowns(countdownID) + ON DELETE CASCADE ); -- Records custom countdown reactions @@ -35,5 +39,7 @@ CREATE table reactions ( countdownID BIGINT NOT NULL, -- The countdown ID number INT NOT NULL, -- The prefix value CHAR NOT NULL, -- The reaction - FOREIGN KEY (countdownID) REFERENCES countdowns(countdownID) + FOREIGN KEY (countdownID) + REFERENCES countdowns(countdownID) + ON DELETE CASCADE ); diff --git a/models/utilities.sql b/models/utilities.sql @@ -3,6 +3,8 @@ DROP FUNCTION IF EXISTS getReactions; DROP PROCEDURE IF EXISTS addMessage; DROP TYPE IF EXISTS addMessageResults; +DROP PROCEDURE IF EXISTS deleteCountdown; +DROP PROCEDURE IF EXISTS createCountdown; DROP PROCEDURE IF EXISTS clearCountdown; DROP PROCEDURE IF EXISTS isCountdown; DROP PROCEDURE IF EXISTS getUserContextCountdown; @@ -118,6 +120,32 @@ BEGIN END $$; +-- Create a new countdown +CREATE PROCEDURE createCountdown ( + _countdownID IN BIGINT, -- The countdown channel ID + _serverID IN BIGINT, -- The server ID + prefix IN VARCHAR(8) -- The initial prefix +) +LANGUAGE plpgsql AS $$ +BEGIN + INSERT INTO countdowns (countdownID, serverID) + VALUES (_countdownID, _serverID); + INSERT INTO prefixes (countdownID, value) + VALUES (_countdownID, prefix); +END +$$; + +-- Delete a countdown +CREATE PROCEDURE deleteCountdown ( + _countdownID IN BIGINT -- The countdown channel ID +) +LANGUAGE plpgsql AS $$ +BEGIN + DELETE + FROM countdowns + WHERE countdownID = _countdownID; +END +$$; -- Possible results of the addMessage procedure CREATE TYPE addMessageResults AS ENUM (