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 bc248b6486c484c1ae0d227beefccdb99e35bdd4
parent ede67eb6d5db3af8128ffc68f71da75cdef4b019
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Sat, 27 Apr 2024 10:41:57 -0700

Update analytics commands to use postgresql db

Diffstat:
Mcountdown_bot/analyticsCog.py | 295++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mcountdown_bot/bot.py | 2+-
Mcountdown_bot/botUtilities.py | 30+++---------------------------
Mcountdown_bot/models.py | 20++++++++++----------
Mcountdown_bot/utilitiesCog.py | 13++++---------
Mmodels/analytics.sql | 9++++++---
6 files changed, 190 insertions(+), 179 deletions(-)

diff --git a/countdown_bot/analyticsCog.py b/countdown_bot/analyticsCog.py @@ -1,5 +1,5 @@ # Import dependencies -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import discord from discord.ext import commands from matplotlib import pyplot as plt @@ -10,15 +10,16 @@ import re import tempfile # Import modules -from .botUtilities import COLORS, getContextCountdown, getUsername, getContributor, CommandError +from .botUtilities import COLORS, getContextCountdown, getUsername, getContributor, CommandError, CountdownNotFound, getContextCountdown2 from .models import POINT_RULES class Analytics(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 @@ -45,9 +46,11 @@ class Analytics(commands.Cog): Shows information about countdown contributors """ - with self.databaseSessionMaker() as session: + with self.db_connection.cursor() as cur: # Get countdown channel - countdown = getContextCountdown(session, ctx) + countdown = getContextCountdown2(cur, ctx) + if not countdown: + raise CountdownNotFound() # Create temp file tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") @@ -65,14 +68,20 @@ class Analytics(commands.Cog): ax.yaxis.set_major_formatter(PercentFormatter()) # Get stats - contributors = countdown.historicalContributors() + cur.execute("SELECT * FROM contributorData(%s);", (countdown,)) + contributors = [x["userid"] for x in cur.fetchall()] + cur.execute("SELECT * FROM historicalContributorData(%s);", (countdown,)) + data = cur.fetchall() + + if not data: + raise CommandError("The countdown doesn't have enough messages yet") # Plot data and add legend - for author in list(contributors.keys())[:min(len(contributors), 15)]: + for author in contributors[:15]: # Top 15 contributors get included in the legend - ax.plot([x["progress"] for x in contributors[author]], [x["percentage"] * 100 for x in contributors[author]], label=await getUsername(self.bot, author)) - for author in list(contributors.keys())[15:max(len(contributors), 15)]: - ax.plot([x["progress"] for x in contributors[author]], [x["percentage"] * 100 for x in contributors[author]]) + ax.plot([x["progress"] for x in data if x["userid"] == author], [x["percentage"] for x in data if x["userid"] == author], label=await getUsername(self.bot, author)) + for author in contributors[15:]: + ax.plot([x["progress"] for x in data if x["userid"] == author], [x["percentage"] for x in data if x["userid"] == author]) ax.legend(bbox_to_anchor=(1,1.025), loc="upper left") # Save graph @@ -80,39 +89,42 @@ class Analytics(commands.Cog): file = discord.File(tmp.name, filename="image.png") # Add content to embed - embed.description = f"**Countdown Channel:** <#{countdown.id}>" + embed.description = f"**Countdown Channel:** <#{countdown}>" embed.set_image(url="attachment://image.png") elif (option == ""): # Create figure fig, ax = plt.subplots() # Get stats - contributors = countdown.contributors() + cur.execute("SELECT * FROM contributorData(%s);", (countdown,)) + data = cur.fetchall() + + if not data: + raise CommandError("The countdown doesn't have enough messages yet") # Add data to graph - x = [x["author"] for x in contributors] - y = [x["contributions"] for x in contributors] - pieData = ax.pie(y, autopct="%1.1f%%", startangle=90) + pieData = ax.pie([x["contributions"] for x in data], autopct="%1.1f%%", startangle=90) # Add legend - ax.legend(pieData[0], [await getUsername(self.bot, i) for i in x[:min(len(x), 15)]], bbox_to_anchor=(1,1.025), loc="upper left") + ax.legend(pieData[0], [await getUsername(self.bot, x["userid"]) for x in + data[:15]], bbox_to_anchor=(1,1.025), loc="upper left") # 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}>" - ranks = "" - users = "" - contributions = "" - for i in range(0, min(len(x), 20)): - ranks += f"{i+1:,}\n" - contributions += f"{y[i]:,} *({round(y[i] / len(countdown.messages) * 100, 1)}%)*\n" - users += f"<@{x[i]}>\n" - embed.add_field(name="Rank",value=ranks, inline=True) - embed.add_field(name="User",value=users, inline=True) - embed.add_field(name="Contributions",value=contributions, inline=True) + embed.description = f"**Countdown Channel:** <#{countdown}>" + ranksColumn = "" + usersColumn = "" + contributionsColumn = "" + for i in range(0, min(len(data), 20)): + ranksColumn += f"{i+1:,}\n" + contributionsColumn += f"{data[i]['contributions']:,} *({data[i]['percentage']:.1f}%)*\n" + usersColumn += f"<@{data[i]['userid']}>\n" + embed.add_field(name="Rank", value=ranksColumn, inline=True) + embed.add_field(name="User", value=usersColumn, inline=True) + embed.add_field(name="Contributions", value=contributionsColumn, inline=True) embed.set_image(url="attachment://image.png") else: raise CommandError(f"Unrecognized option: `{option}`") @@ -132,14 +144,16 @@ class Analytics(commands.Cog): @commands.command(aliases=["e"]) - async def eta(self, ctx, period="24.0"): + async def eta(self, ctx): """ Shows information about the estimated completion date """ - with self.databaseSessionMaker() as session: + with self.db_connection.cursor() as cur: # Get countdown channel - countdown = getContextCountdown(session, ctx) + countdown = getContextCountdown2(cur, ctx) + if not countdown: + raise CountdownNotFound() # Create temp file tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") @@ -148,18 +162,12 @@ class Analytics(commands.Cog): # Create embed embed=discord.Embed(title=":calendar: Countdown Estimated Completion Date", color=COLORS["embed"]) - # Parse period - try: - period = float(period) - 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)) + cur.execute("SELECT * FROM etaData(%s);", (countdown,)) + data = cur.fetchall() + + if not data: + raise CommandError("The countdown doesn't have enough messages yet") # Create figure fig, ax = plt.subplots() @@ -167,10 +175,10 @@ class Analytics(commands.Cog): fig.autofmt_xdate() # Add ETA data to graph - ax.plot(eta[0], eta[1], "C0", label="Estimated Completion Date") + ax.plot([x["_timestamp"] for x in data], [x["eta"] for x in data], "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") + ax.plot([data[0]["_timestamp"], data[-1]["_timestamp"]], [data[0]["_timestamp"], data[-1]["_timestamp"]], "--C1", label="Current Date") # Add legend ax.legend() @@ -180,15 +188,15 @@ class Analytics(commands.Cog): 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() + maxEta = max([x["eta"] for x in data]) + maxDate = [x["_timestamp"] for x in data if x["eta"] == maxEta][0] + minEta = min([x["eta"] for x in data]) + minDate = [x["_timestamp"] for x in data if x["eta"] == minEta][0] + end = data[-1]["eta"] # + timedelta(hours=countdown.timezone) + endDiff = data[-1]["eta"] - datetime.now(timezone.utc) # Add content to embed - embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" + embed.description = f"**Countdown Channel:** <#{countdown}>\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): @@ -217,9 +225,11 @@ class Analytics(commands.Cog): Shows a heatmap of when countdown messages are sent """ - with self.databaseSessionMaker() as session: + with self.db_connection.cursor() as cur: # Get countdown channel - countdown = getContextCountdown(session, ctx) + countdown = getContextCountdown2(cur, ctx) + if not countdown: + raise CountdownNotFound() # Create temp file tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") @@ -234,8 +244,19 @@ class Analytics(commands.Cog): else: userID = await getContributor(self.bot, countdown, user) - # Get heatmap matrix - heatmapMatrix = countdown.heatmap(userID) + # Get heatmap data + cur.execute("SELECT * FROM heatmapData(%s, %s);", + (countdown, userID)) + data = cur.fetchall() + + if not data: + print(countdown, userID, data) + raise CommandError("The countdown doesn't have enough messages yet") + + # Create heatmap matrix + matrix = [[0 for i in range(24)] for j in range(7)] + for row in data: + matrix[int(row["dow"])][int(row["hour"])] = row["messages"] # Define hour and weekday names hours = ["12 AM", "1 AM", "2 AM", "3 AM", "4 AM", "5 AM", "6 AM", "7 AM", "8 AM", "9 AM", "10 AM", "11 AM", "12 PM", "1 PM", "2 PM", "3 PM", "4 PM", "5 PM", "6 PM", "7 PM", "8 PM", "9 PM", "10 PM", "11 PM"] @@ -253,7 +274,7 @@ class Analytics(commands.Cog): # Add data to graph cmap = plt.get_cmap("jet").copy() cmap.set_bad("gray") - cax = ax.matshow(np.ma.masked_equal(np.array(heatmapMatrix), 0), cmap=cmap, aspect="auto") + cax = ax.matshow(np.ma.masked_equal(np.array(matrix), 0), cmap=cmap, aspect="auto") fig.colorbar(cax) # Save graph @@ -261,17 +282,17 @@ class Analytics(commands.Cog): file = discord.File(tmp.name, filename="image.png") # Get embed data - total = np.sum(heatmapMatrix) + total = np.sum(matrix) averageValue = total / (24*7) - maxValue = np.max(heatmapMatrix) - maxWeekday = np.where(heatmapMatrix == maxValue)[0][0] - maxHour = np.where(heatmapMatrix == maxValue)[1][0] - currentWeekday = ((datetime.utcnow() + timedelta(hours=countdown.timezone)).weekday() + 1) % 7 - currentHour = (datetime.utcnow() + timedelta(hours=countdown.timezone)).hour - currentValue = heatmapMatrix[currentWeekday][currentHour] + maxValue = np.max(matrix) + maxWeekday = np.where(matrix == maxValue)[0][0] + maxHour = np.where(matrix == maxValue)[1][0] + currentWeekday = (datetime.utcnow().weekday() + 1) % 7 # ((datetime.utcnow() + timedelta(hours=countdown.timezone)).weekday() + 1) % 7 + currentHour = datetime.utcnow().hour # (datetime.utcnow() + timedelta(hours=countdown.timezone)).hour + currentValue = matrix[currentWeekday][currentHour] # Add content to embed - embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" + embed.description = f"**Countdown Channel:** <#{countdown}>\n\n" if (userID): embed.description += f"**User:** <@{userID}>\n" embed.description += f"**Total Contributions:** {total:,}\n" embed.description += f"**Average Contributions per Zone:** {round(averageValue):,}\n" @@ -299,29 +320,41 @@ class Analytics(commands.Cog): Shows the countdown leaderboard """ - with self.databaseSessionMaker() as session: + with self.db_connection.cursor() as cur: # Get countdown channel - countdown = getContextCountdown(session, ctx) - - # Get leaderboard - leaderboard = countdown.leaderboard() + countdown = getContextCountdown2(cur, ctx) + if not countdown: + raise CountdownNotFound() # Create embed embed=discord.Embed(title=":trophy: Countdown Leaderboard", color=COLORS["embed"]) - # Make sure the countdown has started + # Get user + if (user == None): + userID = None + else: + userID = await getContributor(self.bot, countdown, user) + + # Get leaderboard + cur.execute("SELECT * FROM leaderboardData(%s, %s);", + (countdown, userID)) + data = cur.fetchall() + + if not data: + raise CommandError("The countdown doesn't have enough messages yet") + if (user is None): # Add description - embed.description = f"**Countdown Channel:** <#{countdown.id}>" + embed.description = f"**Countdown Channel:** <#{countdown}>" # Add leaderboard ranks = "" points = "" users = "" - for i in range(0, min(len(leaderboard), 20)): - ranks += f"{i+1:,}\n" - points += f"{leaderboard[i]['points']:,}\n" - users += f"<@{leaderboard[i]['author']}>\n" + for row in data[:20]: + ranks += f"{row['ranking']:,}\n" + points += f"{row['total']:,}\n" + users += f"<@{row['userid']}>\n" embed.add_field(name="Rank",value=ranks, inline=True) embed.add_field(name="Points",value=points, inline=True) embed.add_field(name="User",value=users, inline=True) @@ -330,34 +363,28 @@ class Analytics(commands.Cog): rules = "" values = "" for rule in POINT_RULES: - rules += f"{rule}\n" - values += f"{POINT_RULES[rule]} points\n" + rules += f"{POINT_RULES[rule][0]}\n" + values += f"{POINT_RULES[rule][1]} points\n" embed.add_field(name="Rules", value="Only 1 rule is applied towards each number", inline=False) embed.add_field(name="Numbers", value=rules, inline=True) embed.add_field(name="Points", value=values, inline=True) else: - # 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" - embed.description += f"**User:** <@{leaderboard[rank]['author']}>\n" - embed.description += f"**Rank:** #{rank + 1:,}\n" - embed.description += f"**Total Points:** {leaderboard[rank]['points']:,}\n" - embed.description += f"**Total Contributions:** {leaderboard[rank]['contributions']:,} *({round(leaderboard[rank]['contributions'] / len(countdown.messages) * 100, 1)}%)*\n" + embed.description = f"**Countdown Channel:** <#{countdown}>\n\n" + embed.description += f"**User:** <@{data[0]['userid']}>\n" + embed.description += f"**Rank:** #{data[0]['ranking']:,}\n" + embed.description += f"**Total Points:** {data[0]['total']:,}\n" + embed.description += f"**Total Contributions:** {data[0]['contributions']:,} *({round(data[0]['percentage'])}%)*\n" # Add points breakdown rules = "" points = "" percentage = "" - for category in leaderboard[rank]["breakdown"]: - rules += f"{category}\n" - points += f"{leaderboard[rank]['breakdown'][category] * POINT_RULES[category]:,} *({leaderboard[rank]['breakdown'][category]:,})*\n" - if (leaderboard[rank]['points'] > 0): - percentage += f"{round(leaderboard[rank]['breakdown'][category] * POINT_RULES[category] / leaderboard[rank]['points'] * 100, 1)}%\n" + for rule in POINT_RULES: + rules += f"{POINT_RULES[rule][0]}\n" + points += f"{data[0][rule] * POINT_RULES[rule][1]:,} *({data[0][rule]:,})*\n" + if (data[0]['total'] > 0): + percentage += f"{round(data[0][rule] * POINT_RULES[rule][1] / data[0]['total'] * 100, 1)}%\n" else: percentage += "0%\n" embed.add_field(name="Category", value=rules, inline=True) @@ -375,9 +402,11 @@ class Analytics(commands.Cog): Shows information about countdown progress """ - with self.databaseSessionMaker() as session: + with self.db_connection.cursor() as cur: # Get countdown channel - countdown = getContextCountdown(session, ctx) + countdown = getContextCountdown2(cur, ctx) + if not countdown: + raise CountdownNotFound() # Create temp file tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") @@ -387,8 +416,13 @@ class Analytics(commands.Cog): embed=discord.Embed(title=":chart_with_downwards_trend: Countdown Progress", color=COLORS["embed"]) # Get progress stats - stats = countdown.progress() - breakStats = countdown.longestBreak() + cur.execute("SELECT * FROM progressData(%s);", (countdown,)) + data = cur.fetchall() + cur.execute("CALL progressStats(%s,null,null,null,null,null,null,null,null,null,null);", (countdown,)) + stats = cur.fetchone() + + if not data: + raise CommandError("The countdown doesn't have enough messages yet") # Create figure fig, ax = plt.subplots() @@ -397,8 +431,8 @@ class Analytics(commands.Cog): 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"]] + x = [data[0]["_timestamp"]] + [x["_timestamp"] for x in data] + y = [0] + [x["progress"] for x in data] ax.plot(x, y) # Save graph @@ -406,17 +440,17 @@ class Analytics(commands.Cog): file = discord.File(tmp.name, filename="image.png") # Calculate embed data - longestBreakDuration = timedelta(days=breakStats['duration'].days, seconds=breakStats['duration'].seconds) - longestBreakStart = breakStats['start'].date() - longestBreakEnd = breakStats['end'].date() - 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() + longestBreakDuration = timedelta(days=stats["longestbreak"].days, seconds=stats["longestbreak"].seconds) + longestBreakStart = stats["longestbreakstart"].date() + longestBreakEnd = stats["longestbreakend"].date() + start = stats["starttime"].date() # (stats["starttime"] + timedelta(hours=countdown.timezone)).date() + startDiff = (datetime.now(timezone.utc) - stats["starttime"]).days + end = stats["endtime"].date() # (stats["endtime"] + timedelta(hours=countdown.timezone)).date() + endDiff = stats["endtime"] - datetime.now(timezone.utc) # 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"**Countdown Channel:** <#{countdown}>\n\n" + embed.description += f"**Progress:** {stats['progress']:,} / {stats['total']:,} ({round(stats['percentage'], 1)}%)\n" embed.description += f"**Average Progress per Day:** {round(stats['rate']):,}\n" embed.description += f"**Longest Break:** {longestBreakDuration} ({longestBreakStart} to {longestBreakEnd})\n" embed.description += f"**Start Date:** {start} ({startDiff:,} days ago)\n" @@ -441,14 +475,16 @@ class Analytics(commands.Cog): @commands.command(aliases=["s"]) - async def speed(self, ctx, period="24.0"): + async def speed(self, ctx, period="24"): """ Shows information about countdown speed """ - with self.databaseSessionMaker() as session: + with self.db_connection.cursor() as cur: # Get countdown channel - countdown = getContextCountdown(session, ctx) + countdown = getContextCountdown2(cur, ctx) + if not countdown: + raise CountdownNotFound() # Create temp file tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") @@ -459,18 +495,16 @@ class Analytics(commands.Cog): # Parse period try: - period = float(period) + period = int(period) 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 data + cur.execute("SELECT * FROM speedData(%s, %s);", (countdown, period)) + data = cur.fetchall() - # Get stats - stats = countdown.progress() - period = timedelta(hours=period) - speed = countdown.speed(period) + if not data: + raise CommandError("The countdown doesn't have enough messages yet") # Create figure fig, ax = plt.subplots() @@ -479,24 +513,27 @@ class Analytics(commands.Cog): 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") + period = timedelta(hours=period) + for row in data: + ax.bar(row["periodstart"], row["messages"], 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") + # Calculate embed data + maxSpeed = max([x["messages"] for x in data]) + avgSpeed = round(sum([x["messages"] for x in data]) / len(data)) + curSpeed = data[-1]["messages"] + curPeriod = data[-1]["periodstart"] + # Add content to embed - embed.description = f"**Countdown Channel:** <#{countdown.id}>\n\n" + embed.description = f"**Countdown Channel:** <#{countdown}>\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.description += f"**Average Progress per Period:** {avgSpeed:,}\n" + embed.description += f"**Record Progress per Period:** {maxSpeed:,}\n" + embed.description += f"**Last Period Start:** {curPeriod}\n" + embed.description += f"**Progress during Last Period:** {curSpeed:,}\n" embed.set_image(url="attachment://image.png") # Send embed diff --git a/countdown_bot/bot.py b/countdown_bot/bot.py @@ -30,7 +30,7 @@ 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(analyticsCog.Analytics(self, self.databaseSessionMaker, self.db_connection)) diff --git a/countdown_bot/botUtilities.py b/countdown_bot/botUtilities.py @@ -96,32 +96,8 @@ async def getContributor(bot, countdown, text): If a matching contributor cannot be found """ - # Get countdown contributors - contributors = [x["author"] for x in countdown.contributors()] - - # Get user from mention - if (re.match("^<@!\d+>$", text) and int(text[3:-1]) in contributors): - return int(text[3:-1]) - elif (re.match("^<@!\d+>$", text)): - raise ContributorNotFound(text) - - # Get user from username - for contributor in contributors: - try: - username = await getUsername(bot, contributor) - except: - continue - if (username.lower().startswith(text.lower())): - return contributor - - # Get user from nickname - for contributor in contributors: - try: - nickname = await getNickname(bot, countdown.server_id, contributor) - except: - continue - if (nickname.lower().startswith(text.lower())): - return contributor + if (re.match("^<@\d+>$", text)): + return int(text[2:-1]) raise ContributorNotFound(text) @@ -258,7 +234,7 @@ def getPrefix(conn, ctx, default): with conn.cursor() as cur: cur.execute("SELECT * FROM getPrefixes(%s, %s);", - (ctx.channel.guild.id, ctx.channel.id)) + (ctx.channel.guild.id if ctx.channel.guild else None, ctx.channel.id)) prefixes = cur.fetchall() return [x["prefix"] for x in prefixes] if prefixes else default diff --git a/countdown_bot/models.py b/countdown_bot/models.py @@ -29,16 +29,16 @@ def getSessionMaker(location): # The rules for awarding leaderboard points POINT_RULES = { - "1000s": 1000, - "1001s": 500, - "200s": 200, - "201s": 100, - "100s": 100, - "101s": 50, - "Prime Numbers": 15, - "Odd Numbers": 12, - "Even Numbers": 10, - "First Number": 0, + "r1": ("First Number", 0), + "r2": ("1000s", 1000), + "r3": ("1001s", 500), + "r4": ("200s", 200), + "r5": ("201s", 100), + "r6": ("100s", 100), + "r7": ("101s", 50), + # "r8": ("Prime Numbers", 15), + "r8": ("Odd Numbers", 12), + "r9": ("Even Numbers", 10), } diff --git a/countdown_bot/utilitiesCog.py b/countdown_bot/utilitiesCog.py @@ -260,13 +260,11 @@ class Utilities(commands.Cog): "eta": "**Name:** eta\n" \ "**Description:** Shows information about the estimated completion date\n" \ - f"**Usage:** `{prefixes[0]}eta|e [<period>]`\n" \ + f"**Usage:** `{prefixes[0]}eta|e`\n" \ "**Aliases:** `e`\n" \ - "**Arguments:**\n" \ - "**-** `<period>`: The size of the period in hours (the default is 24 hours)\n" \ + "**Arguments:** none\n" \ "**Examples:**\n" \ f"**-** `{prefixes[0]}eta`\n" \ - f"**-** `{prefixes[0]}eta 48`\n" \ "**Notes:** none\n", "heatmap": "**Name:** heatmap\n" \ @@ -274,11 +272,10 @@ class Utilities(commands.Cog): f"**Usage:** `{prefixes[0]}heatmap [<user>]`\n" \ "**Aliases:** none\n" \ "**Arguments:**\n" \ - "**-** `<user>`: The username or nickname of the user to view heatmap information about. If no value is supplied, the general heatmap will be shown\n" \ + "**-** `<user>`: The user to view heatmap information about. If no value is supplied, the general heatmap will be shown\n" \ "**Examples:**\n" \ f"**-** `{prefixes[0]}heatmap`\n" \ f"**-** `{prefixes[0]}heatmap @Alice`\n" \ - f"**-** `{prefixes[0]}heatmap Bob`\n" \ "**Notes:** none\n", "help": "**Name:** help\n" \ @@ -297,12 +294,10 @@ class Utilities(commands.Cog): f"**Usage:** `{prefixes[0]}leaderboard|l [<user>]`\n" \ "**Aliases:** `l`\n" \ "**Arguments:**\n" \ - "**-** `<user>`: The rank, username, or nickname of the user to view leaderboard information about. If no value is supplied, the whole leaderboard will be shown\n" \ + "**-** `<user>`: The user to view leaderboard information about. If no value is supplied, the whole leaderboard will be shown\n" \ "**Examples:**\n" \ f"**-** `{prefixes[0]}leaderboard`\n" \ - f"**-** `{prefixes[0]}leaderboard 1`\n" \ f"**-** `{prefixes[0]}leaderboard @Alice`\n" \ - f"**-** `{prefixes[0]}leaderboard Bob`\n" \ "**Notes:** The leaderboard embed will only show the top 20 contributors\n", "ping": "**Name:** ping\n" \ diff --git a/models/analytics.sql b/models/analytics.sql @@ -66,12 +66,12 @@ BEGIN RETURN QUERY SELECT timestamp, - to_timestamp(startTime + value * + to_timestamp(startTime + total * (extract(epoch FROM timestamp) - startTime) / (total - value) ) AS eta FROM messages WHERE countdownID = _countdownID - AND extract(epoch FROM timestamp) != startTime + AND value != total ORDER BY messageID; END $$; @@ -298,7 +298,10 @@ BEGIN -- Calculate longestBreak, longestBreakStart, and longestBreakEnd SELECT timestamp, - LEAD(timestamp, 1, NOW()) OVER (ORDER BY timestamp) - timestamp AS delta + CASE + WHEN value = 0 THEN '0' + ELSE LEAD(timestamp, 1, NOW()) OVER (ORDER BY timestamp) - timestamp + END AS delta INTO longestBreakStart, longestBreak FROM messages WHERE messages.countdownID = _countdownID