Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions cogs/command_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,17 @@ async def on_application_command_error(
if isinstance(error, discord.ApplicationCommandInvokeError) and isinstance(
error.original, GuildDoesNotExistError
):
command_name: str = (
ctx.command.callback.__name__
if (
hasattr(ctx.command, "callback")
and not ctx.command.callback.__name__.startswith("_")
command_name: str | None = (
(
ctx.command.callback.__name__
if (
hasattr(ctx.command, "callback")
and not ctx.command.callback.__name__.startswith("_")
)
else ctx.command.qualified_name
)
else ctx.command.qualified_name
if ctx.command
else None
)
logger.critical(
" ".join(
Expand Down
4 changes: 3 additions & 1 deletion cogs/induct.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ async def _perform_induction(
applicant_role, reason=INDUCT_AUDIT_MESSAGE
)

tex_emoji: discord.Emoji | None = self.bot.get_emoji(743218410409820213)
tex_emoji: discord.AppEmoji | discord.GuildEmoji | None = self.bot.get_emoji(
743218410409820213
)
if not tex_emoji:
tex_emoji = discord.utils.get(main_guild.emojis, name="TeX")

Expand Down
4 changes: 2 additions & 2 deletions cogs/kill.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ConfirmKillView(View):
@discord.ui.button(
label="SHUTDOWN", style=discord.ButtonStyle.red, custom_id="shutdown_confirm"
)
async def confirm_shutdown_button_callback( # type: ignore[misc]
async def confirm_shutdown_button_callback(
self, _: discord.Button, interaction: discord.Interaction
) -> None:
"""When the shutdown button is pressed, delete the message."""
Expand All @@ -37,7 +37,7 @@ async def confirm_shutdown_button_callback( # type: ignore[misc]
@discord.ui.button(
label="CANCEL", style=discord.ButtonStyle.grey, custom_id="shutdown_cancel"
)
async def cancel_shutdown_button_callback( # type: ignore[misc]
async def cancel_shutdown_button_callback(
self, _: discord.Button, interaction: discord.Interaction
) -> None:
"""When the cancel button is pressed, delete the message."""
Expand Down
4 changes: 3 additions & 1 deletion cogs/make_applicant.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ async def _perform_make_applicant(
await applicant_member.add_roles(applicant_role, reason=AUDIT_MESSAGE)
logger.debug("Applicant role given to user %s", applicant_member)

tex_emoji: discord.Emoji | None = self.bot.get_emoji(743218410409820213)
tex_emoji: discord.AppEmoji | discord.GuildEmoji | None = self.bot.get_emoji(
743218410409820213
)
if not tex_emoji:
tex_emoji = discord.utils.get(main_guild.emojis, name="TeX")

Expand Down
7 changes: 7 additions & 0 deletions cogs/remind_me.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ async def remind_me(

The "remind_me" command responds with the given message after the specified time.
"""
if not ctx.channel:
await self.command_send_error(
ctx,
message="Interaction channel was None while trying to set a reminder.",
)
return

parsed_time: tuple[time.struct_time, int] = parsedatetime.Calendar().parseDT(
delay, tzinfo=timezone.get_current_timezone()
)
Expand Down
8 changes: 3 additions & 5 deletions cogs/send_introduction_reminders.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@

import discord
import emoji
from discord import ui
from discord.ext import tasks
from discord.ui import View
from django.core.exceptions import ValidationError

import utils
Expand Down Expand Up @@ -185,7 +183,7 @@ async def send_introduction_reminders(self) -> None:
)[0],
)

class OptOutIntroductionRemindersView(View):
class OptOutIntroductionRemindersView(discord.ui.View):
"""
A discord.View containing a button to opt-in/out of introduction reminders.

Expand Down Expand Up @@ -227,13 +225,13 @@ async def send_error(
logging_message=logging_message,
)

@ui.button(
@discord.ui.button(
label="Opt-out of introduction reminders",
custom_id="opt_out_introduction_reminders_button",
style=discord.ButtonStyle.red,
emoji=discord.PartialEmoji.from_str(emoji.emojize(":no_good:", language="alias")),
)
async def opt_out_introduction_reminders_button_callback( # type: ignore[misc]
async def opt_out_introduction_reminders_button_callback(
self, button: discord.Button, interaction: discord.Interaction
) -> None:
"""
Expand Down
127 changes: 107 additions & 20 deletions cogs/stats/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Contains cog classes for any stats interactions."""

import math
import re
from typing import TYPE_CHECKING

import discord
Expand Down Expand Up @@ -83,32 +82,64 @@ async def channel_stats(
The "channel_stats" command sends a graph of the stats about messages sent in the given
channel.
"""
# NOTE: Shortcut accessors are placed at the top of the function so that the exceptions they raise are displayed before any further errors may be sent
main_guild: discord.Guild = self.bot.main_guild
if not ctx.channel or not isinstance(
ctx.channel, (discord.TextChannel, discord.DMChannel)
):
await self.command_send_error(
ctx,
message="Channel statistics cannot be sent in this channel.",
)
return

channel_id: int = ctx.channel_id
stats_channel: discord.TextChannel | None = None

if str_channel_id:
if not re.fullmatch(r"\A\d{17,20}\Z", str_channel_id):
if not str_channel_id:
if not isinstance(ctx.channel, discord.TextChannel):
await self.command_send_error(
ctx, message=f"{str_channel_id!r} is not a valid channel ID."
ctx,
message=(
"User did not provide a channel ID and the interaction channel "
"is not a text channel."
),
)
return
stats_channel = ctx.channel

channel_id = int(str_channel_id)
if not stats_channel:
try:
channel_id: int = int(str_channel_id)
except ValueError:
await self.command_send_error(
ctx,
message="The provided channel ID was not a valid integer.",
)
return

channel: discord.TextChannel | None = discord.utils.get(
main_guild.text_channels, id=channel_id
)
if not channel:
await self.command_send_error(
ctx, message=f"Text channel with ID {str(channel_id)!r} does not exist."
)
return
result_channel = ctx.bot.get_channel(channel_id)
if not result_channel:
await self.command_send_error(
ctx,
message="The provided channel ID was not valid or could not be found.",
)
return

if not isinstance(result_channel, discord.TextChannel):
await self.command_send_error(
ctx,
message=(
"The provided channel ID relates to a channel type "
"that is not supported."
),
)
return

stats_channel = result_channel

await ctx.defer(ephemeral=True)

message_counts: Mapping[str, int] = await get_channel_message_counts(channel=channel)
message_counts: Mapping[str, int] = await get_channel_message_counts(
channel=stats_channel
)

if math.ceil(max(message_counts.values()) / 15) < 1:
await self.command_send_error(
Expand All @@ -128,11 +159,11 @@ async def channel_stats(
amount_of_time_formatter(settings["STATISTICS_DAYS"].days, "day")
})"""
),
title=f"Most Active Roles in #{channel.name}",
filename=f"{channel.name}_channel_stats.png",
title=f"Most Active Roles in #{stats_channel.name}",
filename=f"{stats_channel.name}_channel_stats.png",
description=(
"Bar chart of the number of messages "
f"sent by different roles in {channel.mention}."
f"sent by different roles in {stats_channel.mention}."
),
extra_text=(
"Messages sent by members with multiple roles are counted once "
Expand All @@ -157,6 +188,26 @@ async def server_stats(self, ctx: "TeXBotApplicationContext") -> None:
main_guild: discord.Guild = self.bot.main_guild
guest_role: discord.Role = await self.bot.guest_role

if not ctx.channel:
await self.command_send_error(
ctx,
message=(
"Interaction channel was None while attempting to send server stats."
),
)
return

if isinstance(
ctx.channel, (discord.VoiceChannel, discord.ForumChannel, discord.CategoryChannel)
):
await self.command_send_error(
ctx,
message=(
"Server stats cannot be sent in a voice, forum, or category channel."
),
)
return

await ctx.defer(ephemeral=True)

message_counts: Mapping[str, Mapping[str, int]] = await get_server_message_counts(
Expand Down Expand Up @@ -248,6 +299,22 @@ async def user_stats(self, ctx: "TeXBotApplicationContext") -> None:
)
return

if not ctx.channel:
await self.command_send_error(
ctx,
message=("Interaction channel was None while attempting to send user stats."),
)
return

if isinstance(
ctx.channel, (discord.VoiceChannel, discord.ForumChannel, discord.CategoryChannel)
):
await self.command_send_error(
ctx,
message=("User stats cannot be sent in a voice, forum, or category channel."),
)
return

await ctx.defer(ephemeral=True)

message_counts: dict[str, int] = {"Total": 0}
Expand Down Expand Up @@ -314,6 +381,26 @@ async def left_member_stats(self, ctx: "TeXBotApplicationContext") -> None:
# NOTE: Shortcut accessors are placed at the top of the function so that the exceptions they raise are displayed before any further errors may be sent
main_guild: discord.Guild = self.bot.main_guild

if not ctx.channel:
await self.command_send_error(
ctx,
message=(
"Interaction channel was None while attempting to send left member stats."
),
)
return

if isinstance(
ctx.channel, (discord.VoiceChannel, discord.ForumChannel, discord.CategoryChannel)
):
await self.command_send_error(
ctx,
message=(
"Left member stats cannot be sent in a voice, forum, or category channel."
),
)
return

await ctx.defer(ephemeral=True)

left_member_counts: dict[str, int] = {
Expand Down
Loading
Loading