Skip to content

Commit

Permalink
Statistics migration
Browse files Browse the repository at this point in the history
  • Loading branch information
MattBSG committed Jun 10, 2024
1 parent adb8f6a commit b75444b
Showing 1 changed file with 64 additions and 90 deletions.
154 changes: 64 additions & 90 deletions modules/statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import logging
import time
import typing
from datetime import datetime, timedelta, timezone
from datetime import datetime, date, timedelta, timezone

import config
import discord
import pymongo
import pytz
from discord.ext import commands
from discord import app_commands

import tools

Expand All @@ -20,43 +21,58 @@ class StatCommands(commands.Cog, name='Statistic Commands'):
def __init__(self, bot):
self.bot = bot

@commands.group(name='stats', invoke_without_command=True)
@commands.has_any_role(config.moderator, config.eh)
async def _stats(self, ctx):
return await ctx.send_help(self._stats)
@app_commands.guilds(discord.Object(id=config.nintendoswitch))
@app_commands.default_permissions(view_audit_log=True)
@app_commands.checks.has_any_role(config.moderator, config.eh)
class StatsCommand(app_commands.Group):
pass

@_stats.command(name='server')
@commands.has_any_role(config.moderator, config.eh)
async def _stats_server(self, ctx, start_date=None, end_date=None):
stats_group = StatsCommand(name='stats', description='View various statistics about the server and it\'s members')

async def _stats_server_autocomplete(self, interaction: discord.Interaction, current: str) -> typing.List[app_commands.Choice[str]]:
if not current:
# Blank, suggest today's date
isodate = date.today().isoformat()
return [app_commands.Choice(name=isodate, value=isodate)]

else:
return []


@stats_group.command(name='server')
@app_commands.autocomplete(start=_stats_server_autocomplete)
@app_commands.autocomplete(end=_stats_server_autocomplete)
@app_commands.describe(start='The start date to search, in YYYY-MM-DD format', end='The end date to search, in YYYY-MM-DD format')
async def _stats_server(self, interaction: discord.Interaction, start: typing.Optional[str]=None, end: typing.Optional[str]=None):
'''Returns server activity statistics'''
msg = await ctx.send('One moment, crunching message and channel data...')
await interaction.response.send_message('One moment, crunching message and channel data...')

try:
searchDate = (
datetime.now(tz=timezone.utc)
if not start_date
else datetime.strptime(start_date, '%Y-%m-%d').replace(tzinfo=pytz.UTC)
if not start
else datetime.strptime(start, '%Y-%m-%d').replace(tzinfo=pytz.UTC)
)
searchDate = searchDate.replace(hour=0, minute=0, second=0)
endDate = (
searchDate + timedelta(days=30)
if not end_date
else datetime.strptime(end_date, '%Y-%m-%d').replace(tzinfo=pytz.UTC)
if not end
else datetime.strptime(end, '%Y-%m-%d').replace(tzinfo=pytz.UTC)
)
endDate = endDate.replace(hour=23, minute=59, second=59)

except ValueError:
return await msg.edit(
return await interaction.edit_original_response(
content=f'{config.redTick} Invalid date provided. Please make sure it is in the format of `yyyy-mm-dd`'
)

if not start_date:
if not start:
messages = mclient.bowser.messages.find({'timestamp': {'$gte': (int(time.time()) - (60 * 60 * 24 * 30))}})

else:
if endDate <= searchDate:
return await msg.edit(
content=f'{config.redTick} Invalid dates provided. The end date is before the starting date. `{ctx.prefix}stats server [starting date] [ending date]`'
return await interaction.edit_original_response(
content=f'{config.redTick} Invalid dates provided. The end date cannot be before the starting date. `/stats server [starting date] [ending date]`'
)

messages = mclient.bowser.messages.find(
Expand All @@ -79,7 +95,7 @@ async def _stats_server(self, ctx, start_date=None, end_date=None):
else:
userCounts[message['author']] += 1

if not start_date:
if not start:
puns = mclient.bowser.puns.find(
{
'timestamp': {'$gte': (int(time.time()) - (60 * 60 * 24 * 30))},
Expand Down Expand Up @@ -107,30 +123,30 @@ async def _stats_server(self, ctx, start_date=None, end_date=None):
else:
topChannelsList.append(f'*Deleted channel* ({x[1]})')

await msg.edit(content='One moment, crunching member data...')
await interaction.edit_original_response(content='One moment, crunching member data...')
netJoins = 0
netLeaves = 0
for member in mclient.bowser.users.find({'joins': {'$ne': []}}):
for join in member['joins']:
if not start_date and (searchDate.timestamp() - (60 * 60 * 24 * 30)) <= join <= endDate.timestamp():
if not start and (searchDate.timestamp() - (60 * 60 * 24 * 30)) <= join <= endDate.timestamp():
netJoins += 1

elif start_date and searchDate.timestamp() <= join <= endDate.timestamp():
elif start and searchDate.timestamp() <= join <= endDate.timestamp():
netJoins += 1

for leave in member['leaves']:
if not start_date and (searchDate.timestamp() - (60 * 60 * 24 * 30)) <= leave <= endDate.timestamp():
if not start and (searchDate.timestamp() - (60 * 60 * 24 * 30)) <= leave <= endDate.timestamp():
netLeaves += 1

elif start_date and searchDate.timestamp() <= leave <= endDate.timestamp():
elif start and searchDate.timestamp() <= leave <= endDate.timestamp():
netLeaves += 1

activeChannels = ', '.join(topChannelsList)
premiumTier = 'No tier' if ctx.guild.premium_tier == 0 else f'Tier {ctx.guild.premium_tier}'
premiumTier = 'No tier' if interaction.guild.premium_tier == 0 else f'Tier {interaction.guild.premium_tier}'

dayStr = (
'In the last 30 days'
if not start_date
if not start
else 'Between ' + searchDate.strftime('%Y-%m-%d') + ' and ' + endDate.strftime('%Y-%m-%d')
)
netMembers = netJoins - netLeaves
Expand All @@ -141,26 +157,25 @@ async def _stats_server(self, ctx, start_date=None, end_date=None):
)

embed = discord.Embed(
title=f'{ctx.guild.name} Statistics',
description=f'Current member count is **{ctx.guild.member_count}**\n*__{dayStr}...__*\n\n'
title=f'{interaction.guild.name} Statistics',
description=f'Current member count is **{interaction.guild.member_count}**\n*__{dayStr}...__*\n\n'
f':incoming_envelope: **{msgCount}** messages have been sent\n:information_desk_person: **{len(userCounts)}** members were active\n'
f'{netMemberStr}:hammer: **{puns}** punishment actions were handed down\n\n:bar_chart: The most active channels by message count were {activeChannels}',
color=0xD267BA,
)
embed.set_thumbnail(url=ctx.guild.icon.url)
embed.set_thumbnail(url=interaction.guild.icon.url)
embed.add_field(
name='Guild features',
value=f'**Guild flags:** {", ".join(ctx.guild.features)}\n'
f'**Boost level:** {premiumTier}\n**Number of boosters:** {ctx.guild.premium_subscription_count}',
value=f'**Guild flags:** {", ".join(interaction.guild.features)}\n'
f'**Boost level:** {premiumTier}\n**Number of boosters:** {interaction.guild.premium_subscription_count}',
)

return await msg.edit(content=None, embed=embed)
return await interaction.edit_original_response(content=None, embed=embed)

@_stats.command(name='users')
@commands.has_any_role(config.moderator, config.eh)
async def _stats_users(self, ctx):
@stats_group.command(name='users')
async def _stats_users(self, interaction: discord.Interaction):
'''Returns most active users'''
msg = await ctx.send('One moment, crunching the numbers...')
await interaction.response.send_message('One moment, crunching the numbers...')
messages = mclient.bowser.messages.find({'timestamp': {'$gt': (int(time.time()) - (60 * 60 * 24 * 30))}})
msgCounts = {}
for message in messages:
Expand All @@ -179,44 +194,36 @@ async def _stats_users(self, ctx):
color=0xD267BA,
)
for x in topSenders:
msgUser = ctx.guild.get_member(x[0])
msgUser = interaction.guild.get_member(x[0])
if not msgUser:
msgUser = await self.bot.fetch_user(x[0])

embed.add_field(name=str(msgUser), value=str(x[1]))

return await msg.edit(content=None, embed=embed)
return await interaction.edit_original_response(content=None, embed=embed)

@_stats.command(name='roles', aliases=['role'])
@commands.has_any_role(config.moderator, config.eh)
@stats_group.command(name='roles')
@app_commands.describe(role='A specific role to provide detailed statistics on')
async def _stats_roles(
self, ctx, *, role: typing.Optional[typing.Union[discord.Role, int, str]]
self, interaction: discord.Interaction, role: typing.Optional[discord.Role]
): # TODO: create and pull role add/remove data from events
'''Returns statistics on the ownership of roles'''

await interaction.response.send_message('One moment, crunching the numbers...')
if role:
if type(role) is int:
role = ctx.guild.get_role(role)
if not role:
return await ctx.send(f'{config.redTick} There is no role by that ID')

elif type(role) is str:
role = discord.utils.get(ctx.guild.roles, name=role)
if not role:
return await ctx.send(f'{config.redTick} There is no role by that name')

lines = []
desc = f'There are currently **{len(role.members)}** users with the **{role.name}** role:\n\n'
for member in role.members:
lines.append(f'* {member} ({member.id})')

title = f'{ctx.guild.name} Role Statistics'
title = f'{interaction.guild.name} Role Statistics'
fields = tools.convert_list_to_fields(lines)
await interaction.delete_original_response()
# TODO: embed pagination need to be converted to UI Kit
return await tools.send_paginated_embed(
self.bot,
ctx.channel,
interaction.channel,
fields,
owner=ctx.author,
owner=interaction.user,
title=title,
description=desc,
color=0xD267BA,
Expand All @@ -225,50 +232,17 @@ async def _stats_roles(

else:
roleCounts = []
for role in reversed(ctx.guild.roles):
for role in reversed(interaction.guild.roles):
roleCounts.append(f'**{role.name}:** {len(role.members)}')

roleList = '\n'.join(roleCounts)
embed = discord.Embed(
title=f'{ctx.guild.name} Role Statistics',
title=f'{interaction.guild.name} Role Statistics',
description=f'Server role list and respective member count\n\n{roleList}',
color=0xD267BA,
)

return await ctx.send(embed=embed)

@_stats.command(name='channels', hidden=True)
@commands.has_any_role(config.moderator, config.eh)
async def _stats_channels(self, ctx):
'''Returns most active channels'''
return await ctx.send(f'{config.redTick} Channel statistics are not ready for use')

async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError):
if not ctx.command:
return

cmd_str = ctx.command.full_parent_name + ' ' + ctx.command.name if ctx.command.parent else ctx.command.name
if isinstance(error, commands.MissingRequiredArgument):
return await ctx.send(
f'{config.redTick} Missing one or more required arguments. See `{ctx.prefix}help {cmd_str}`',
delete_after=15,
)

elif isinstance(error, commands.BadArgument):
return await ctx.send(
f'{config.redTick} One or more provided arguments are invalid. See `{ctx.prefix}help {cmd_str}`',
delete_after=15,
)

elif isinstance(error, commands.CheckFailure):
return await ctx.send(f'{config.redTick} You do not have permission to run this command.', delete_after=15)

else:
await ctx.send(
f'{config.redTick} An unknown exception has occured, if this continues to happen contact the developer.',
delete_after=15,
)
raise error
return await interaction.edit_original_response(content=None, embed=embed)


async def setup(bot):
Expand Down

0 comments on commit b75444b

Please sign in to comment.