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 d8e3a03cffb95555e66c0c59b604ffcec7807a10
parent 913b9dd89276402b4687ef7f6a352561b2bdd01a
Author: ashermorgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Mon,  3 May 2021 11:26:15 -0700

Implement eta command

Diffstat:
Mbot.py | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 152 insertions(+), 3 deletions(-)

diff --git a/bot.py b/bot.py @@ -318,6 +318,62 @@ class Countdown: # Return contributors return contributors + def eta(self, period=timedelta(days=1), tz=timedelta(hours=0)): + """ + Get countdown eta statistics. + + Parameters + ---------- + period : timedelta + The period size. The default is 1 day. + tz : timedelta + The timezone. The default is +0 (UTC) + + Returns + ------- + list + The countdown eta statistics. + """ + + # Make sure countdown has at least two messages + if (len(self.messages) < 2): + return [[], []] + + # Initialize period data + periodEnd = self.messages[0].timestamp + tz + period + lastMessage = 0 + + # Initialize result and add first data point + data = [[self.messages[0].timestamp + tz], [self.messages[0].timestamp + tz]] + + # Calculate timestamp for last data point + if (self.messages[-1].number == 0): + end = self.messages[-1].timestamp + tz + else: + end = datetime.utcnow() + tz + + # Add data points + while (periodEnd < end): + # Advance to last message in period + while (lastMessage+1 < len(self.messages) and self.messages[lastMessage+1].timestamp + tz < periodEnd): + lastMessage += 1 + + # Calculate data + rate = (self.messages[0].number - self.messages[lastMessage].number) / ((periodEnd - (self.messages[0].timestamp + tz)) / timedelta(days=1)) + eta = periodEnd + timedelta(days=self.messages[lastMessage].number/rate) + data[0] += [periodEnd] + data[1] += [eta] + + # Advance to next period + periodEnd += period + + # Add last data point + data[0] += [end] + data[1] += [self.progress()["eta"]] + + # Return eta data + return data + def leaderboard(self): """ Get countdown leaderboard. @@ -737,7 +793,7 @@ async def contributors(ctx, option=""): x = [x["author"] for x in contributors] y = [x["contributions"] for x in contributors] pieData = ax.pie(y, autopct="%1.1f%%", startangle=90) - + # Add legend ax.legend(pieData[0], [await getUsername(i) for i in x[:min(len(x), 15)]], bbox_to_anchor=(1,1.025), loc="upper left") @@ -806,6 +862,88 @@ async def deactivate(ctx): +@bot.command(aliases=["e"]) +async def eta(ctx, period="24.0"): + """ + Shows information about the estimated completion date + """ + + # Get countdown channel + channel, id = getCountdownChannel(ctx) + + # Create temp file + tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png") + tmp.close() + + # Create embed + embed=discord.Embed(title=":calendar: Countdown Estimated Completion Date", color=COLORS["embed"]) + + # Parse period + try: + period = float(period) + except: + embed.color = COLORS["error"] + embed.description = "The period must be a number" + else: + if (len(channel["countdown"].messages) < 2): + 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 = channel["countdown"].eta(timedelta(hours=period), tz=timedelta(hours=channel["timezone"])) + + # 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=channel["timezone"]) + endDiff = eta[1][-1] - datetime.utcnow() + + # Add content to embed + embed.description = f"**Countdown Channel:** <#{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") + + # Send embed + try: + await ctx.send(file=file, embed=embed) + except: + await ctx.send(embed=embed) + + # Remove temp file + try: + os.remove(tmp.name) + except: + print(f"Unable to delete temp file: {tmp.name}.") + + + @bot.command(aliases=["h", ""]) async def help(ctx, command=None): """ @@ -826,6 +964,7 @@ async def help(ctx, command=None): "**-** `reload`: Reloads the countdown cache\n", "analytics-commands": "**-** `contributors`, `c`: Shows information about countdown contributors\n" \ + "**-** `eta`, `e`: Shows information about the estimated completion date\n" \ "**-** `leaderboard`, `l`: Shows the countdown leaderboard\n" \ "**-** `progress`, `p`: Shows information about countdown progress\n" \ "**-** `speed`, `s`: Shows information about countdown speed\n", @@ -868,6 +1007,14 @@ async def help(ctx, command=None): "**Aliases:** none\n" \ "**Arguments:** none\n" \ "**Notes:** Users must have admin permissions to deactivate a countdown channel\n", + "eta": + "**Name:** eta\n" \ + "**Description:** Shows information about the estimated completion date\n" \ + f"**Usage:** `{prefixes[0]}eta|e [<period>]`\n" \ + "**Aliases:** `e`\n" \ + "**Arguments:**\n" \ + "**-** `<period>`: The size of the period in hours. The default is 24 hours.\n" \ + "**Notes:** none\n", "help": "**Name:** help\n" \ "**Description:** Shows help information\n" \ @@ -931,6 +1078,8 @@ async def help(ctx, command=None): embed.description = help_text["contributors"] elif (command.lower() in ["deactivate"]): embed.description = help_text["deactivate"] + elif (command.lower() in ["e", "eta"]): + embed.description = help_text["eta"] elif (command.lower() in ["h", "help"]): embed.description = help_text["help"] elif (command.lower() in ["l", "leaderboard"]): @@ -1010,14 +1159,14 @@ async def leaderboard(ctx, user=None): username = await getUsername(contributor["author"]) if (username.lower().startswith(user.lower())): rank = leaderboard.index(contributor) - + if (rank == None): # Get user from nickname for contributor in leaderboard: nickname = await getNickname(channel["server"], contributor["author"]) if (nickname.lower().startswith(user.lower())): rank = leaderboard.index(contributor) - + if (rank == None): # User not found embed.color = COLORS["error"]