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 09db08c9e679a3eab3da08904003182a83674a32
parent 41a95fedaf35ec07dd5a7263750837937b574642
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Sat,  3 Jul 2021 14:26:00 -0700

Improve error handling

Diffstat:
Msrc/analyticsCog.py | 303++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/bot.py | 19+++++++++++++++----
Msrc/botUtilities.py | 13++++++++++++-
Msrc/models.py | 41++++++++++++++++++++++++++++-------------
Msrc/utilitiesCog.py | 134+++++++++++++++++++++++++++++++++----------------------------------------------
5 files changed, 242 insertions(+), 268 deletions(-)

diff --git a/src/analyticsCog.py b/src/analyticsCog.py @@ -10,7 +10,7 @@ import re import tempfile # Import modules -from src.botUtilities import COLORS, getContextCountdown, getUsername, getContributor, ContributorNotFound +from src.botUtilities import COLORS, getContextCountdown, getUsername, getContributor, CommandError from src.models import POINT_RULES @@ -28,25 +28,14 @@ class Analytics(commands.Cog): Shows all countdown analytics """ - with self.databaseSessionMaker() as session: - # Get countdown channel - countdown = getContextCountdown(session, ctx) - - # Check if countdown is empty - if (len(countdown.messages) == 0): - embed=discord.Embed(title=":bar_chart: Countdown Analytics", color=COLORS["error"]) - embed.description = "The countdown is empty" - await ctx.send(embed=embed) - - # Run analytics commands - else: - await self.contributors(ctx, "") - await self.contributors(ctx, "history") - if (len(countdown.messages) >= 2): await self.eta(ctx) # Countdown must have 2 messages to run eta command - await self.heatmap(ctx) - await self.leaderboard(ctx) - await self.progress(ctx) - await self.speed(ctx) + # Run analytics commands + await self.contributors(ctx, "") + await self.contributors(ctx, "history") + await self.eta(ctx) + await self.heatmap(ctx) + await self.leaderboard(ctx) + await self.progress(ctx) + await self.speed(ctx) @@ -72,10 +61,7 @@ class Analytics(commands.Cog): embed=discord.Embed(title=":busts_in_silhouette: Countdown Contributors", color=COLORS["embed"]) # Make sure the countdown has started - if (len(countdown.messages) == 0): - embed.color = COLORS["error"] - embed.description = "The countdown is empty." - elif (option.lower() in ["h", "history"]): + if (option.lower() in ["h", "history"]): # Create figure fig, ax = plt.subplots() ax.set_xlabel("Progress") @@ -138,9 +124,7 @@ class Analytics(commands.Cog): embed.add_field(name="Contributions",value=contributions, inline=True) embed.set_image(url="attachment://image.png") else: - embed.color = COLORS["error"] - embed.description = f"Unrecognized option: `{option}`\n" - embed.description += f"Use `{(await self.bot.get_prefix(ctx))[0]}help contributors` to view help information" + raise CommandError(f"Unrecognized option: `{option}`") # Send embed try: @@ -176,55 +160,51 @@ class Analytics(commands.Cog): # Parse period try: period = float(period) - except: - embed.color = COLORS["error"] - embed.description = "The period must be a number" + except ValueError: + raise CommandError(f"Invalid number: `{period}`") + + # Make sure period is valid + if (period < 0.01): + raise CommandError("The period cannot be less than 0.01 hours") + + # Get stats + eta = countdown.eta(timedelta(hours=period)) + + # Create figure + fig, ax = plt.subplots() + ax.set_xlabel("Time") + fig.autofmt_xdate() + + # Add ETA data to graph + ax.plot(eta[0], eta[1], "C0", label="Estimated Completion Date") + + # Add reference line graph + ax.plot([eta[0][0], eta[0][-1]], [eta[0][0], eta[0][-1]], "--C1", label="Current Date") + + # Add legend + ax.legend() + + # Save graph + fig.savefig(tmp.name, bbox_inches="tight", pad_inches=0.2) + file = discord.File(tmp.name, filename="image.png") + + # Calculate embed data + maxEta = max(eta[1]) + maxDate = eta[0][eta[1].index(maxEta)] + minEta = min(eta[1][1:]) + minDate = eta[0][eta[1].index(minEta)] + end = eta[1][-1] + timedelta(hours=countdown.timezone) + endDiff = eta[1][-1] - datetime.utcnow() + + # Add content to embed + embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" + embed.description += f"**Maximum Estimate:** {maxEta.date()} (on {maxDate.date()})\n" + embed.description += f"**Minimum Estimate:** {minEta.date()} (on {minDate.date()})\n" + if endDiff < timedelta(seconds=0): + embed.description += f"**Actual Completion Date:** {end.date()} ({(-1 * endDiff).days:,} days ago)\n" else: - if (len(countdown.messages) < 2): - embed.color = COLORS["embed"] - embed.description = "The countdown must have at least two messages" - elif (period < 0.01): - embed.color = COLORS["error"] - embed.description = "The period cannot be less than 0.01 hours" - else: - # Get stats - eta = countdown.eta(timedelta(hours=period)) - - # Create figure - fig, ax = plt.subplots() - ax.set_xlabel("Time") - fig.autofmt_xdate() - - # Add ETA data to graph - ax.plot(eta[0], eta[1], "C0", label="Estimated Completion Date") - - # Add reference line graph - ax.plot([eta[0][0], eta[0][-1]], [eta[0][0], eta[0][-1]], "--C1", label="Current Date") - - # Add legend - ax.legend() - - # Save graph - fig.savefig(tmp.name, bbox_inches="tight", pad_inches=0.2) - file = discord.File(tmp.name, filename="image.png") - - # Calculate embed data - maxEta = max(eta[1]) - maxDate = eta[0][eta[1].index(maxEta)] - minEta = min(eta[1][1:]) - minDate = eta[0][eta[1].index(minEta)] - end = eta[1][-1] + timedelta(hours=countdown.timezone) - endDiff = eta[1][-1] - datetime.utcnow() - - # Add content to embed - embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" - embed.description += f"**Maximum Estimate:** {maxEta.date()} (on {maxDate.date()})\n" - embed.description += f"**Minimum Estimate:** {minEta.date()} (on {minDate.date()})\n" - if endDiff < timedelta(seconds=0): - embed.description += f"**Actual Completion Date:** {end.date()} ({(-1 * endDiff).days:,} days ago)\n" - else: - embed.description += f"**Current Estimate:** {end.date()} ({endDiff.days:,} days from now)\n" - embed.set_image(url="attachment://image.png") + embed.description += f"**Current Estimate:** {end.date()} ({endDiff.days:,} days from now)\n" + embed.set_image(url="attachment://image.png") # Send embed try: @@ -258,14 +238,10 @@ class Analytics(commands.Cog): embed=discord.Embed(title=":calendar_spiral: Countdown Heatmap", color=COLORS["embed"]) # Get user - try: - if (user == None): userID = None - else: userID = await getContributor(self.bot, countdown, user) - except ContributorNotFound: - embed.color = COLORS["error"] - embed.description = f"Contributor not found: `{user}`" - await ctx.send(embed=embed) - return + if (user == None): + userID = None + else: + userID = await getContributor(self.bot, countdown, user) # Get heatmap matrix heatmapMatrix = np.ma.masked_equal(np.array(countdown.heatmap(userID)), 0) @@ -326,10 +302,7 @@ class Analytics(commands.Cog): embed=discord.Embed(title=":trophy: Countdown Leaderboard", color=COLORS["embed"]) # Make sure the countdown has started - if (len(countdown.messages) == 0): - embed.color = COLORS["error"] - embed.description = "The countdown is empty." - elif (user is None): + if (user is None): # Add description embed.description = f"**Countdown Channel:** <#{countdown.id}>" @@ -355,16 +328,11 @@ class Analytics(commands.Cog): embed.add_field(name="Numbers", value=rules, inline=True) embed.add_field(name="Points", value=values, inline=True) else: - try: - if (re.match("^\d+$", user) and int(user) > 0 and int(user) <= len(leaderboard)): - rank = int(user) - 1 - else: - rank = [x["author"] for x in leaderboard].index(await getContributor(self.bot, countdown, user)) - except ContributorNotFound: - embed.color = COLORS["error"] - embed.description = f"Contributor not found: `{user}`" - await ctx.send(embed=embed) - return + # Get user rank + if (re.match("^\d+$", user) and int(user) > 0 and int(user) <= len(leaderboard)): + rank = int(user) - 1 + else: + rank = [x["author"] for x in leaderboard].index(await getContributor(self.bot, countdown, user)) # Add description embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" @@ -410,45 +378,40 @@ class Analytics(commands.Cog): # Create embed embed=discord.Embed(title=":chart_with_downwards_trend: Countdown Progress", color=COLORS["embed"]) - # Make sure the countdown has started - if (len(countdown.messages) == 0): - embed.color = COLORS["error"] - embed.description = "The countdown is empty." - else: - # Get progress stats - stats = countdown.progress() + # Get progress stats + stats = countdown.progress() - # Create figure - fig, ax = plt.subplots() - ax.set_xlabel("Time") - ax.set_ylabel("Progress") - fig.autofmt_xdate() + # Create figure + fig, ax = plt.subplots() + ax.set_xlabel("Time") + ax.set_ylabel("Progress") + fig.autofmt_xdate() - # Add data to graph - x = [stats["start"] + timedelta(hours=countdown.timezone)] + [x["time"] + timedelta(hours=countdown.timezone) for x in stats["progress"]] - y = [0] + [x["progress"] for x in stats["progress"]] - ax.plot(x, y) + # Add data to graph + x = [stats["start"] + timedelta(hours=countdown.timezone)] + [x["time"] + timedelta(hours=countdown.timezone) for x in stats["progress"]] + y = [0] + [x["progress"] for x in stats["progress"]] + ax.plot(x, y) - # Save graph - fig.savefig(tmp.name, bbox_inches="tight", pad_inches=0.2) - file = discord.File(tmp.name, filename="image.png") + # Save graph + fig.savefig(tmp.name, bbox_inches="tight", pad_inches=0.2) + file = discord.File(tmp.name, filename="image.png") - # Calculate embed data - start = (stats["start"] + timedelta(hours=countdown.timezone)).date() - startDiff = (datetime.utcnow() - stats["start"]).days - end = (stats["eta"] + timedelta(hours=countdown.timezone)).date() - endDiff = stats["eta"] - datetime.utcnow() + # Calculate embed data + start = (stats["start"] + timedelta(hours=countdown.timezone)).date() + startDiff = (datetime.utcnow() - stats["start"]).days + end = (stats["eta"] + timedelta(hours=countdown.timezone)).date() + endDiff = stats["eta"] - datetime.utcnow() - # Add content to embed - embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" - embed.description += f"**Progress:** {stats['total'] - stats['current']:,} / {stats['total']:,} ({round(stats['percentage'], 1)}%)\n" - embed.description += f"**Average Progress per Day:** {round(stats['rate']):,}\n" - embed.description += f"**Start Date:** {start} ({startDiff:,} days ago)\n" - if endDiff < timedelta(seconds=0): - embed.description += f"**End Date:** {end} ({(-1 * endDiff).days:,} days ago)\n" - else: - embed.description += f"**Estimated End Date:** {end} ({endDiff.days:,} days from now)\n" - embed.set_image(url="attachment://image.png") + # Add content to embed + embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" + embed.description += f"**Progress:** {stats['total'] - stats['current']:,} / {stats['total']:,} ({round(stats['percentage'], 1)}%)\n" + embed.description += f"**Average Progress per Day:** {round(stats['rate']):,}\n" + embed.description += f"**Start Date:** {start} ({startDiff:,} days ago)\n" + if endDiff < timedelta(seconds=0): + embed.description += f"**End Date:** {end} ({(-1 * endDiff).days:,} days ago)\n" + else: + embed.description += f"**Estimated End Date:** {end} ({endDiff.days:,} days from now)\n" + embed.set_image(url="attachment://image.png") # Send embed try: @@ -484,48 +447,44 @@ class Analytics(commands.Cog): # Parse period try: period = float(period) - except: - embed.color = COLORS["error"] - embed.description = "The period must be a number" + except ValueError: + raise CommandError(f"Invalid number: `{period}`") + + # Make sure period is valid + if (period < 0.01): + raise CommandError("The period cannot be less than 0.01 hours") + + # Get stats + stats = countdown.progress() + period = timedelta(hours=period) + speed = countdown.speed(period) + + # Create figure + fig, ax = plt.subplots() + ax.set_xlabel("Time") + ax.set_ylabel("Progress per Period") + fig.autofmt_xdate() + + # Add data to graph + for i in range(0, len(speed[0])): + ax.bar(speed[0][i], speed[1][i], width=period, align="edge", color="#1f77b4") + + # Save graph + fig.savefig(tmp.name, bbox_inches="tight", pad_inches=0.2) + file = discord.File(tmp.name, filename="image.png") + + # Add content to embed + embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" + embed.description += f"**Period Size:** {period}\n" + if (len(countdown.messages) > 1): + rate = (stats['total'] - stats['current'])/((countdown.messages[-1].timestamp - countdown.messages[0].timestamp) / period) else: - if (len(countdown.messages) == 0): - embed.color = COLORS["error"] - embed.description = "The countdown is empty." - elif (period < 0.01): - embed.color = COLORS["error"] - embed.description = "The period cannot be less than 0.01 hours" - else: - # Get stats - stats = countdown.progress() - period = timedelta(hours=period) - speed = countdown.speed(period) - - # Create figure - fig, ax = plt.subplots() - ax.set_xlabel("Time") - ax.set_ylabel("Progress per Period") - fig.autofmt_xdate() - - # Add data to graph - for i in range(0, len(speed[0])): - ax.bar(speed[0][i], speed[1][i], width=period, align="edge", color="#1f77b4") - - # Save graph - fig.savefig(tmp.name, bbox_inches="tight", pad_inches=0.2) - file = discord.File(tmp.name, filename="image.png") - - # Add content to embed - embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" - embed.description += f"**Period Size:** {period}\n" - if (len(countdown.messages) > 1): - rate = (stats['total'] - stats['current'])/((countdown.messages[-1].timestamp - countdown.messages[0].timestamp) / period) - else: - rate = 0 - embed.description += f"**Average Progress per Period:** {round(rate):,}\n" - embed.description += f"**Record Progress per Period:** {max(speed[1]):,}\n" - embed.description += f"**Last Period Start:** {speed[0][-1]}\n" - embed.description += f"**Progress during Last Period:** {speed[1][-1]:,}\n" - embed.set_image(url="attachment://image.png") + rate = 0 + embed.description += f"**Average Progress per Period:** {round(rate):,}\n" + embed.description += f"**Record Progress per Period:** {max(speed[1]):,}\n" + embed.description += f"**Last Period Start:** {speed[0][-1]}\n" + embed.description += f"**Progress during Last Period:** {speed[1][-1]:,}\n" + embed.set_image(url="attachment://image.png") # Send embed try: diff --git a/src/bot.py b/src/bot.py @@ -1,12 +1,13 @@ # Import dependencies import discord from discord.ext import commands +import traceback # Import modules from src import analyticsCog, utilitiesCog -from src.botUtilities import addMessage, COLORS, getCountdown, getPrefix -from src.models import getSessionMaker +from src.botUtilities import addMessage, COLORS, CountdownNotFound, ContributorNotFound, CommandError, getCountdown, getPrefix +from src.models import getSessionMaker, EmptyCountdownError @@ -70,10 +71,20 @@ class CountdownBot(commands.Bot): async def on_command_error(self, ctx, error): # Send error embed - embed=discord.Embed(title="Error", description=str(error), color=COLORS["error"]) + embed=discord.Embed(title=":warning: Error", description=str(error), color=COLORS["error"]) if (isinstance(error, commands.CommandNotFound)): embed.description = f"Command not found: `{str(error)[9:-14]}`" + elif (isinstance(error.original, CountdownNotFound)): + embed.description = f"Countdown not found" + elif (isinstance(error.original, ContributorNotFound)): + embed.description = f"Contributor not found: `{error.original.args[0]}`" + elif (isinstance(error.original, EmptyCountdownError)): + embed.description = f"The countdown is empty" + elif (isinstance(error.original, CommandError)): + embed.description = error.original.args[0] else: + # Unanticipated error embed.description = str(error) - embed.description += f"\nUse `{(await self.get_prefix(ctx))[0]}help` to view help information\n" + traceback.print_exception(type(error), error, error.__traceback__) + embed.description += f"\n\nUse `{(await self.get_prefix(ctx))[0]}help` to view help information" await ctx.send(embed=embed) diff --git a/src/botUtilities.py b/src/botUtilities.py @@ -15,9 +15,15 @@ COLORS = { # Error classes +class CommandError(Exception): + """Raised when a command encounters an anticipated error""" + pass class ContributorNotFound(Exception): """Raised when a matching countdown contributor cannot be found""" pass +class CountdownNotFound(Exception): + """Raised when a matching countdown cannot be found""" + pass @@ -149,6 +155,11 @@ def getContextCountdown(session, ctx): ------- Countdown The countdown + + Raises + ------ + CountdownNotFound + If a matching countdown cannot be found """ if (isinstance(ctx.channel, discord.channel.TextChannel)): @@ -165,7 +176,7 @@ def getContextCountdown(session, ctx): firstMessage = session.query(Message).filter(Message.author_id == ctx.author.id).order_by(Message.timestamp).first() if (firstMessage): return firstMessage.countdown - raise Exception("Countdown channel not found") + raise CountdownNotFound() diff --git a/src/models.py b/src/models.py @@ -44,6 +44,10 @@ POINT_RULES = { # Error classes +class EmptyCountdownError(Exception): + """Raised when an action cannot be completed because the countdown is empty.""" + pass + class MessageNotAllowedError(Exception): """Raised when someone posts twice in a row.""" pass @@ -137,6 +141,10 @@ class Countdown(Base): A list of contributor statistics. """ + # Make sure countdown has started + if (len(self.messages) == 0): + raise EmptyCountdownError() + # Get contributors authors = list(set([x.author_id for x in self.messages])) @@ -170,8 +178,8 @@ class Countdown(Base): """ # Make sure countdown has at least two messages - if (len(self.messages) < 2): - return [[], []] + if (len(self.messages) == 0): + raise EmptyCountdownError() # Initialize period data periodEnd = self.messages[0].timestamp + timedelta(hours=self.timezone) + period @@ -223,6 +231,10 @@ class Countdown(Base): A 7x24 2D array containing the heatmap """ + # Make sure countdown has started + if (len(self.messages) == 0): + raise EmptyCountdownError() + # Initialize result matrix result = [[0 for i in range(24)] for j in range(7)] @@ -255,8 +267,9 @@ class Countdown(Base): The leaderboard. """ + # Make sure countdown has started if (len(self.messages) == 0): - return [] + raise EmptyCountdownError() # Get list of prime numbers curTest = 5 @@ -323,17 +336,15 @@ class Countdown(Base): A dictionary containing countdown progress statistics. """ + # Make sure countdown has started + if (len(self.messages) == 0): + raise EmptyCountdownError() + # Get basic statistics - if (len(self.messages) > 0): - total = self.messages[0].number - current = self.messages[-1].number - percentage = (total - current) / total * 100 - start = self.messages[0].timestamp - else: - total = 0 - current = 0 - percentage = 0 - start = datetime.utcnow() + total = self.messages[0].number + current = self.messages[-1].number + percentage = (total - current) / total * 100 + start = self.messages[0].timestamp # Get rate statistics if (len(self.messages) > 1 and self.messages[-1].number == 0): @@ -377,6 +388,10 @@ class Countdown(Base): The countdown speed statistics. """ + # Make sure countdown has started + if (len(self.messages) == 0): + raise EmptyCountdownError() + # Calculate speed statistics data = [[], []] periodStart = datetime(2018, 1, 1) # Starts on Monday, Jan 1st diff --git a/src/utilitiesCog.py b/src/utilitiesCog.py @@ -3,7 +3,7 @@ import discord from discord.ext import commands # Import modules -from src.botUtilities import COLORS, getContextCountdown, getCountdown, loadCountdown +from src.botUtilities import COLORS, CommandError, getContextCountdown, getCountdown, loadCountdown from src.models import Countdown, Prefix, Reaction @@ -23,46 +23,41 @@ class Utilities(commands.Cog): """ with self.databaseSessionMaker() as session: - # Channel is already a countdown + # Check if channel is already a countdown if (getCountdown(session, ctx.channel.id)): - embed = discord.Embed(title="Error", description="This channel is already a countdown", color=COLORS["error"]) - await ctx.send(embed=embed) - - # Channel is a DM - elif (not isinstance(ctx.channel, discord.channel.TextChannel)): - embed = discord.Embed(title="Error", description="This command must be run inside a server", color=COLORS["error"]) - await ctx.send(embed=embed) - - # User isn't authorized - elif (not ctx.message.author.guild_permissions.administrator): - embed = discord.Embed(title="Error", description="You must be an administrator to turn a channel into a countdown", color=COLORS["error"]) - await ctx.send(embed=embed) - - # Channel is valid - else: - # 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 = [], - ) - - # Send initial response - print(f"Activated {self.bot.get_channel(ctx.channel.id)} as a countdown") - embed = discord.Embed(title=":clock3: Loading Countdown", description="@here This channel is now a countdown\nPlease wait to start counting", color=COLORS["embed"]) - msg = await ctx.send(embed=embed) - - # Load countdown - await loadCountdown(self.bot, countdown) - session.add(countdown) - session.commit() + raise CommandError("This channel is already a countdown") + + # Check if channel is a DM + if (not isinstance(ctx.channel, discord.channel.TextChannel)): + raise CommandError("This command must be run inside a server") + + # Check if user isn't authorized + if (not ctx.message.author.guild_permissions.administrator): + 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 = [], + ) + + # Send initial response + print(f"Activated {self.bot.get_channel(ctx.channel.id)} as a countdown") + embed = discord.Embed(title=":clock3: Loading Countdown", description="@here This channel is now a countdown\nPlease wait to start counting", color=COLORS["embed"]) + msg = await ctx.send(embed=embed) + + # Load countdown + await loadCountdown(self.bot, countdown) + session.add(countdown) + session.commit() - # Send final response - embed = discord.Embed(title=":white_check_mark: Countdown Activated", description="@here This channel is now a countdown\nYou may start counting!", color=COLORS["embed"]) - await msg.edit(embed=embed) + # Send final response + embed = discord.Embed(title=":white_check_mark: Countdown Activated", description="@here This channel is now a countdown\nYou may start counting!", color=COLORS["embed"]) + await msg.edit(embed=embed) @@ -77,15 +72,12 @@ class Utilities(commands.Cog): # Make sure context is in a server if (not isinstance(ctx.channel, discord.channel.TextChannel)): - embed.color = COLORS["error"] - embed.description = "This command must be run in a countdown channel or a server with a countdown channel" - await ctx.send(embed=embed) - return + raise CommandError("This command must be run in a countdown channel or a server with a countdown channel") with self.databaseSessionMaker() as session: # Get countdown channel countdown = getContextCountdown(session, ctx) - + # Get / set settings if (key is None): embed.description = f"**Countdown Channel:** <#{countdown.id}>\n" @@ -98,17 +90,14 @@ class Utilities(commands.Cog): for number in list(dict.fromkeys([x.number for x in countdown.reactions])): embed.description += f"**-** #{number}: {', '.join([x.value for x in countdown.reactions if x.number == number])}\n" elif (not ctx.message.author.guild_permissions.administrator): - embed.color = COLORS["error"] - embed.description = f"You must be an administrator to modify settings" + raise CommandError("You must be an administrator to modify settings") elif (len(args) == 0): - embed.color = COLORS["error"] - embed.description = f"Please provide a value for the setting" + raise CommandError("Please provide a value for the setting") elif (key in ["tz", "timezone"]): try: countdown.timezone = float(args[0]) except: - embed.color = COLORS["error"] - embed.description = f"Invalid timezone: {args[0]}" + raise CommandError(f"Invalid timezone: `{args[0]}`") else: embed.description = f"Timezone set to {countdown.getTimezone()}" elif (key in ["prefix", "prefixes"]): @@ -118,8 +107,7 @@ class Utilities(commands.Cog): try: number = int(args[0]) if (number < 0): - embed.color = COLORS["error"] - embed.description = f"Number must be greater than zero" + raise CommandError("Number must be greater than zero") elif (len(args) == 1): countdown.reactions = [x for x in countdown.reactions if x.number != number] embed.description = f"Removed reactions for #{number}" @@ -128,12 +116,9 @@ class Utilities(commands.Cog): countdown.reactions += [Reaction(countdown_id=countdown.id, number=number, value=x) for x in args[1:]] embed.description = f"Updated reactions for #{number}" except: - embed.color = COLORS["error"] - embed.description = f"Invalid number: {args[0]}" + raise CommandError(f"Invalid number: `{args[0]}`") else: - embed.color = COLORS["error"] - embed.description = f"Setting not found: `{key}`\n" - embed.description += f"Use `{(await self.bot.get_prefix(ctx))[0]}help config` to view the list of settings" + raise CommandError(f"Setting not found: `{key}`") # Save changes session.commit() @@ -150,27 +135,23 @@ class Utilities(commands.Cog): """ with self.databaseSessionMaker() as session: - # Channel isn't a countdown + # Check if channel isn't a countdown countdown = getCountdown(session, ctx.channel.id) if (not countdown): - embed = discord.Embed(title="Error", description="This channel isn't a countdown", color=COLORS["error"]) - await ctx.send(embed=embed) + raise CommandError("This channel isn't a countdown") - # User isn't authorized - elif (not ctx.author.guild_permissions.administrator): - embed = discord.Embed(title="Error", description="You must be an administrator to deactivate a countdown channel", color=COLORS["error"]) - await ctx.send(embed=embed) + # Check if user isn't authorized + if (not ctx.author.guild_permissions.administrator): + raise CommandError("You must be an administrator to deactivate a countdown channel") - # Channel is valid - else: - # Delete countdown - session.delete(countdown) - session.commit() + # Delete countdown + session.delete(countdown) + session.commit() - # Send response - print(f"Deactivated {self.bot.get_channel(ctx.channel.id)} as a countdown") - embed = discord.Embed(title=":octagonal_sign: Countdown Deactivated", description="@here This channel is no longer a countdown", color=COLORS["embed"]) - await ctx.send(embed=embed) + # Send response + print(f"Deactivated {self.bot.get_channel(ctx.channel.id)} as a countdown") + embed = discord.Embed(title=":octagonal_sign: Countdown Deactivated", description="@here This channel is no longer a countdown", color=COLORS["embed"]) + await ctx.send(embed=embed) @@ -356,9 +337,7 @@ class Utilities(commands.Cog): elif (command.lower() in ["s", "speed"]): embed.description = help_text["speed"] else: - embed.color = COLORS["error"] - embed.description = f"Command not found: `{command}`\n" - embed.description += f"Use `{prefixes[0]}help` to view the list of commands" + raise CommandError(f"Command not found: `{command}`") # Send embed await ctx.send(embed=embed) @@ -399,5 +378,4 @@ class Utilities(commands.Cog): 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: - embed = discord.Embed(title="Error", description="This command must be used in a countdown channel", color = COLORS["error"]) - await ctx.channel.send(embed=embed) + raise CommandError("Countdown not found\nThis command must be used in a countdown channel")