async def _send_stats(self, ctx, preamble): placings = 5 database_key = f"race.scores:{ctx.channel.id}" if database.zcard(database_key) == 0: logger.info(f"no users in {database_key}") await ctx.send("There are no users in the database.") return if placings > database.zcard(database_key): placings = database.zcard(database_key) leaderboard_list = database.zrevrangebyscore( database_key, "+inf", "-inf", 0, placings, True ) embed = discord.Embed( type="rich", colour=discord.Color.blurple(), title=preamble ) embed.set_author(name="Bird ID - An Ornithology Bot") leaderboard = "" for i, stats in enumerate(leaderboard_list): if ctx.guild is not None: user = await fetch_get_user(int(stats[0]), ctx=ctx, member=True) else: user = None if user is None: user = await fetch_get_user(int(stats[0]), ctx=ctx, member=False) if user is None: user_info = "**Deleted**" else: user_info = f"**{esc(user.name)}#{user.discriminator}**" else: user_info = f"**{esc(user.name)}#{user.discriminator}** ({user.mention})" leaderboard += f"{i+1}. {user_info} - {int(stats[1])}\n" start = int(database.hget(f"race.data:{ctx.channel.id}", "start")) elapsed = str(datetime.timedelta(seconds=round(time.time()) - start)) embed.add_field( name="Options", value=await self._get_options(ctx), inline=False ) embed.add_field( name="Stats", value=f"**Race Duration:** `{elapsed}`", inline=False ) embed.add_field(name="Leaderboard", value=leaderboard, inline=False) if ctx.author: if database.zscore(database_key, str(ctx.author.id)) is not None: placement = int(database.zrevrank(database_key, str(ctx.author.id))) + 1 embed.add_field( name="You:", value=f"You are #{placement}.", inline=False ) else: embed.add_field( name="You:", value="You haven't answered any correctly." ) await ctx.send(embed=embed)
async def _send_stats(self, ctx, preamble): database_key = f"session.incorrect:{ctx.author.id}" embed = discord.Embed(type="rich", colour=discord.Color.blurple(), title=preamble) embed.set_author(name="Bird ID - An Ornithology Bot") if database.zcard(database_key) != 0: leaderboard_list = database.zrevrangebyscore( database_key, "+inf", "-inf", 0, 5, True) leaderboard = "" for i, stats in enumerate(leaderboard_list): leaderboard += ( f"{i+1}. **{stats[0].decode('utf-8')}** - {int(stats[1])}\n" ) else: logger.info(f"no birds in {database_key}") leaderboard = "**There are no missed birds.**" embed.add_field(name="Options", value=await self._get_options(ctx), inline=False) embed.add_field(name="Stats", value=await self._get_stats(ctx), inline=False) embed.add_field(name="Top Missed Birds", value=leaderboard, inline=False) await ctx.send(embed=embed)
async def send_leaderboard(ctx, title, page, database_key=None, data=None): logger.info("building/sending leaderboard") if database_key is None and data is None: raise GenericError("database_key and data are both NoneType", 990) if database_key is not None and data is not None: raise GenericError("database_key and data are both set", 990) if page < 1: page = 1 entry_count = ( int(database.zcard(database_key)) if database_key is not None else data.count() ) page = (page * 10) - 10 if entry_count == 0: logger.info(f"no items in {database_key}") await ctx.send("There are no items in the database.") return if page > entry_count: page = entry_count - (entry_count % 10) items_per_page = 10 leaderboard_list = ( map( lambda x: (x[0].decode("utf-8"), x[1]), database.zrevrangebyscore( database_key, "+inf", "-inf", page, items_per_page, True ), ) if database_key is not None else data.iloc[page : page + items_per_page - 1].items() ) embed = discord.Embed(type="rich", colour=discord.Color.blurple()) embed.set_author(name="Bird ID - An Ornithology Bot") leaderboard = "" for i, stats in enumerate(leaderboard_list): leaderboard += f"{i+1+page}. **{stats[0]}** - {int(stats[1])}\n" embed.add_field(name=title, value=leaderboard, inline=False) await ctx.send(embed=embed)
async def user_lb(self, ctx, title, page, database_key=None, data=None): if database_key is None and data is None: raise GenericError("database_key and data are both NoneType", 990) if database_key is not None and data is not None: raise GenericError("database_key and data are both set", 990) if page < 1: page = 1 user_amount = ( int(database.zcard(database_key)) if database_key is not None else data.count() ) page = (page * 10) - 10 if user_amount == 0: logger.info(f"no users in {database_key}") await ctx.send("There are no users in the database.") return if page >= user_amount: page = user_amount - (user_amount % 10 if user_amount % 10 != 0 else 10) users_per_page = 10 leaderboard_list = ( database.zrevrangebyscore( database_key, "+inf", "-inf", page, users_per_page, True ) if database_key is not None else data.iloc[page : page + users_per_page - 1].items() ) embed = discord.Embed(type="rich", colour=discord.Color.blurple()) embed.set_author(name="Bird ID - An Ornithology Bot") leaderboard = "" for i, stats in enumerate(leaderboard_list): if ctx.guild is not None: user = ctx.guild.get_member(int(stats[0])) else: user = None if user is None: user = self.bot.get_user(int(stats[0])) if user is None: user = "******" else: user = f"**{user.name}#{user.discriminator}**" else: user = f"**{user.name}#{user.discriminator}** ({user.mention})" leaderboard += f"{i+1+page}. {user} - {int(stats[1])}\n" embed.add_field(name=title, value=leaderboard, inline=False) user_score = ( database.zscore(database_key, str(ctx.author.id)) if database_key is not None else data.get(str(ctx.author.id)) ) if user_score is not None: if database_key is not None: placement = int(database.zrevrank(database_key, str(ctx.author.id))) + 1 distance = int( database.zrevrange( database_key, placement - 2, placement - 2, True )[0][1] ) - int(user_score) else: placement = int(data.rank(ascending=False)[str(ctx.author.id)]) distance = int(data.iloc[placement - 2] - user_score) if placement == 1: embed.add_field( name="You:", value=f"You are #{placement} on the leaderboard.\nYou are in first place.", inline=False, ) elif distance == 0: embed.add_field( name="You:", value=f"You are #{placement} on the leaderboard.\nYou are tied with #{placement-1}", inline=False, ) else: embed.add_field( name="You:", value=f"You are #{placement} on the leaderboard.\nYou are {distance} away from #{placement-1}", inline=False, ) else: embed.add_field(name="You:", value="You haven't answered any correctly.") await ctx.send(embed=embed)
async def stats(self, ctx, topic="help"): logger.info("command: stats") if topic in ("scores", "score", "s"): topic = "scores" elif topic in ("usage", "u"): topic = "usage" elif topic in ("web", "w"): topic = "web" elif topic in ("help", ""): topic = "help" else: valid_topics = ("help", "scores", "usage", "web") await ctx.send( f"**`{topic}` is not a valid topic!**\nValid Topics: `{'`, `'.join(valid_topics)}`" ) return embed = discord.Embed( title="Bot Stats", type="rich", color=discord.Color.blue(), ) if topic == "help": embed.description = ( "**Available statistic topics.**\n" + "This command is in progress and more stats may be added. " + "If there is a statistic you would like to see here, " + "please let us know in the support server.") embed.add_field( name="Scores", value= "`b!stats [scores|score|s]`\n*Displays stats about scores.*", ).add_field( name="Usage", value="`b!stats [usage|u]`\n*Displays stats about usage.*", ).add_field( name="Web", value="`b!stats [web|w]`\n*Displays stats about web usage.*", ) elif topic == "scores": embed.description = "**Score Statistics**" scores = self.generate_series("users:global") scores = scores[scores > 0] c, d = np.histogram(scores, bins=range(0, 1100, 100), range=(0, 1000)) c = (c / len(scores) * 100).round(1) embed.add_field( name="Totals", inline=False, value="**Sum of top 10 user scores:** `{:,}`\n".format( scores.nlargest(n=10).sum()) + "**Sum of all positive user scores:** `{:,}`\n".format( scores.sum()), ).add_field( name="Computations", inline=False, value="**Mean of all positive user scores:** `{:,.2f}`\n". format(scores.mean()) + "**Median of all positive user scores:** `{:,.1f}`\n".format( scores.median()), ).add_field( name="Distributions", inline=False, value= f"**Number of users with scores over mean:** `{len(scores[scores > scores.mean()])}`\n" + "**Percentage of users with scores over mean:** `{:.1%}`". format(len(scores[scores > scores.mean()]) / len(scores)) + "\n**Percentage of users with scores between:**\n" + "".join( f"\u2192 *{d[i]}-{d[i+1]-1}*: `{c[i]}%`\n" # \u2192 is the "Rightwards Arrow" for i in range(len(c))), ) elif topic == "usage": embed.description = "**Usage Statistics**" today = datetime.datetime.now(datetime.timezone.utc).date() past_month = pd.date_range( # pylint: disable=no-member today - datetime.timedelta(29), today).date keys = list(f"daily.score:{str(date)}" for date in past_month) keys = ["users:global"] + keys titles = reversed(range( 1, 32)) # label columns by # days ago, today is 1 day ago month = self.generate_dataframe(keys, titles) total = month.loc[:, 31] month = month.loc[:, 30:1] # remove totals column month = month.loc[(month != 0).any(1)] # remove users with all 0s week = month.loc[:, 7:1] # generate week from month week = week.loc[(week != 0).any(1)] today = week.loc[:, 1] # generate today from week today = today.loc[today != 0] channels_see = len(list(self.bot.get_all_channels())) channels_used = int(database.zcard("score:global")) embed.add_field( name="Today (Since midnight UTC)", inline=False, value="**Accounts that answered at least 1 correctly:** `{:,}`\n" .format(len(today)) + "**Total birds answered correctly:** `{:,}`\n".format( today.sum()), ).add_field( name="Last 7 Days", inline=False, value="**Accounts that answered at least 1 correctly:** `{:,}`\n" .format(len(week)) + "**Total birds answered correctly:** `{:,}`\n".format( week.sum().sum()), ).add_field( name="Last 30 Days", inline=False, value="**Accounts that answered at least 1 correctly:** `{:,}`\n" .format(len(month)) + "**Total birds answered correctly:** `{:,}`\n".format( month.sum().sum()), ).add_field( name="Total", inline=False, value="**Channels the bot can see:** `{:,}`\n".format( channels_see) + "**Channels that have used the bot at least once:** `{:,} ({:,.1%})`\n" .format(channels_used, channels_used / channels_see) + "*(Note: Deleted channels or channels that the bot can't see anymore are still counted).*\n" + "**Accounts that have used any command at least once:** `{:,}`\n" .format(len(total)) + "**Accounts that answered at least 1 correctly:** `{:,} ({:,.1%})`\n" .format(len(total[total > 0]), len(total[total > 0]) / len(total)), ) elif topic == "web": embed.description = "**Web Usage Statistics**" today = datetime.datetime.now(datetime.timezone.utc).date() past_month = pd.date_range( # pylint: disable=no-member today - datetime.timedelta(29), today).date web_score = (f"daily.webscore:{str(date)}" for date in past_month) web_usage = (f"daily.web:{str(date)}" for date in past_month) titles = tuple(reversed(range( 1, 31))) # label columns by # days ago, today is 1 day ago web_score_month = self.generate_dataframe(web_score, titles) web_score_week = web_score_month.loc[:, 7: 1] # generate week from month web_score_week = web_score_week.loc[( web_score_week != 0).any(1)] # remove users with no correct answers web_score_today = web_score_week.loc[:, 1] # generate today from week web_score_today = web_score_today.loc[ web_score_today != 0] # remove users with no correct answers web_usage_month = self.generate_dataframe(web_usage, titles, index=("check", "skip", "hint")) web_usage_week = web_usage_month.loc[:, 7:1] web_usage_today = web_usage_week.loc[:, 1] score_totals_keys = sorted( map( lambda x: x.decode("utf-8"), database.scan_iter(match="daily.webscore:????-??-??", count=5000), )) score_totals_titles = map(lambda x: x.split(":")[1], score_totals_keys) web_score_total = self.generate_dataframe(score_totals_keys, score_totals_titles) usage_totals_keys = sorted( map( lambda x: x.decode("utf-8"), database.scan_iter(match="daily.web:????-??-??", count=5000), )) usage_totals_titles = map(lambda x: x.split(":")[1], usage_totals_keys) web_usage_total = self.generate_dataframe(usage_totals_keys, usage_totals_titles, index=("check", "skip", "hint")) embed.add_field( name="Today (Since midnight UTC)", inline=False, value="**Accounts that answered at least 1 correctly:** `{:,}`\n" .format(len(web_score_today)) + "**Total birds answered correctly:** `{:,}`\n".format( web_score_today.sum()) + "**Check command usage:** `{:,}`\n".format( web_usage_today.loc["check"]) + "**Skip command usage:** `{:,}`\n".format( web_usage_today.loc["skip"]) + "**Hint command usage:** `{:,}`\n".format( web_usage_today.loc["hint"]), ).add_field( name="Last 7 Days", inline=False, value="**Accounts that answered at least 1 correctly:** `{:,}`\n" .format(len(web_score_week)) + "**Total birds answered correctly:** `{:,}`\n".format( web_score_week.sum().sum()) + "**Check command usage:** `{:,}`\n".format( web_usage_week.loc["check"].sum()) + "**Skip command usage:** `{:,}`\n".format( web_usage_week.loc["skip"].sum()) + "**Hint command usage:** `{:,}`\n".format( web_usage_week.loc["hint"].sum()), ).add_field( name="Last 30 Days", inline=False, value="**Accounts that answered at least 1 correctly:** `{:,}`\n" .format(len(web_score_month)) + "**Total birds answered correctly:** `{:,}`\n".format( web_score_month.sum().sum()) + "**Check command usage:** `{:,}`\n".format( web_usage_month.loc["check"].sum()) + "**Skip command usage:** `{:,}`\n".format( web_usage_month.loc["skip"].sum()) + "**Hint command usage:** `{:,}`\n".format( web_usage_month.loc["hint"].sum()), ).add_field( name="Total", inline=False, value="**Accounts that answered at least 1 correctly:** `{:,}`\n" .format(len(web_score_total)) + "**Total birds answered correctly:** `{:,}`\n".format( web_score_total.sum().sum()) + "**Check command usage:** `{:,}`\n".format( web_usage_total.loc["check"].sum()) + "**Skip command usage:** `{:,}`\n".format( web_usage_total.loc["skip"].sum()) + "**Hint command usage:** `{:,}`\n".format( web_usage_total.loc["hint"].sum()), ) await ctx.send(embed=embed) return