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
5 changes: 3 additions & 2 deletions cogs/command_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ async def on_application_command_error(
command_name: str = (
ctx.command.callback.__name__
if (
hasattr(ctx.command, "callback")
ctx.command
and 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 "unknown")
)
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 @@ -252,7 +252,9 @@ async def _perform_induction(
applicant_role, reason=INDUCT_AUDIT_MESSAGE
)

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

Expand Down
8 changes: 4 additions & 4 deletions cogs/kill.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@
class ConfirmKillView(View):
"""A discord.View containing two buttons to confirm shutting down TeX-Bot."""

@discord.ui.button(
@discord.ui.button( # type: ignore[arg-type]
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."""
logger.debug('"Confirm" button pressed. %s', interaction)

@discord.ui.button(
@discord.ui.button( # type: ignore[arg-type]
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 @@ -76,7 +76,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.GuildEmoji | discord.AppEmoji | None = self.bot.get_emoji(
743218410409820213
)
if not tex_emoji:
tex_emoji = discord.utils.get(main_guild.emojis, name="TeX")

Expand Down
9 changes: 7 additions & 2 deletions cogs/remind_me.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ async def remind_me(
parsed_time: tuple[time.struct_time, int] = parsedatetime.Calendar().parseDT(
delay, tzinfo=timezone.get_current_timezone()
)
if not ctx.channel:
await self.command_send_error(
ctx, message="This command can only be used in channels."
)
return

if parsed_time[1] == 0:
await self.command_send_error(
Expand Down Expand Up @@ -305,8 +310,8 @@ async def clear_reminders_backlog(self) -> None:
discord.utils.utcnow() - reminder.send_datetime
)
if time_since_reminder_needed_to_be_sent > datetime.timedelta(minutes=15):
user: discord.User | None = await self.bot.get_or_fetch_user(
int(reminder.discord_member.discord_id)
user: discord.User | None = await self.bot.get_or_fetch(
object_type=discord.User, object_id=int(reminder.discord_member.discord_id)
)

if not user:
Expand Down
4 changes: 2 additions & 2 deletions cogs/send_introduction_reminders.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,13 @@ async def send_error(
logging_message=logging_message,
)

@ui.button(
@ui.button( # type: ignore[arg-type]
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
34 changes: 33 additions & 1 deletion cogs/stats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,15 @@ async def channel_stats(
# 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

channel_id: int = ctx.channel_id
if not ctx.channel or isinstance(
ctx.channel, (discord.CategoryChannel, discord.ForumChannel)
):
await self.command_send_error(
ctx, message="This command can only be used in text channels."
)
return
Comment thread
MattyTheHacker marked this conversation as resolved.

channel_id: int | None = ctx.channel_id

if str_channel_id:
if not re.fullmatch(r"\A\d{17,20}\Z", str_channel_id):
Expand Down Expand Up @@ -157,6 +165,14 @@ 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 or isinstance(
ctx.channel, (discord.CategoryChannel, discord.ForumChannel)
):
await self.command_send_error(
ctx, message="This command can only be used in text channels."
)
return
Comment thread
MattyTheHacker marked this conversation as resolved.

await ctx.defer(ephemeral=True)

message_counts: Mapping[str, Mapping[str, int]] = await get_server_message_counts(
Expand Down Expand Up @@ -237,6 +253,14 @@ async def user_stats(self, ctx: "TeXBotApplicationContext") -> None:
interaction_member: discord.Member = await self.bot.get_main_guild_member(ctx.user)
guest_role: discord.Role = await self.bot.guest_role

if not ctx.channel or isinstance(
ctx.channel, (discord.CategoryChannel, discord.ForumChannel)
):
await self.command_send_error(
ctx, message="This command can only be used in text channels."
)
return
Comment thread
MattyTheHacker marked this conversation as resolved.

if guest_role not in interaction_member.roles:
await self.command_send_error(
ctx,
Expand Down Expand Up @@ -314,6 +338,14 @@ 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 or isinstance(
ctx.channel, (discord.CategoryChannel, discord.ForumChannel)
):
await self.command_send_error(
ctx, message="This command can only be used in text channels."
)
return
Comment thread
MattyTheHacker marked this conversation as resolved.

await ctx.defer(ephemeral=True)

left_member_counts: dict[str, int] = {
Expand Down
47 changes: 29 additions & 18 deletions cogs/strike.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ async def perform_moderation_action(
class ConfirmStrikeMemberView(View):
"""A discord.View containing two buttons to confirm giving the member a strike."""

@discord.ui.button(
@discord.ui.button( # type: ignore[arg-type]
label="Yes", style=discord.ButtonStyle.red, custom_id="yes_strike_member"
)
async def yes_strike_member_button_callback( # type: ignore[misc]
async def yes_strike_member_button_callback(
self, _: discord.Button, interaction: discord.Interaction
) -> None:
"""
Expand All @@ -110,10 +110,10 @@ async def yes_strike_member_button_callback( # type: ignore[misc]
view=None
) # NOTE: Despite removing the view within the normal command processing loop, the view also needs to be removed here to prevent an Unknown Webhook error

@discord.ui.button(
@discord.ui.button( # type: ignore[arg-type]
label="No", style=discord.ButtonStyle.grey, custom_id="no_strike_member"
)
async def no_strike_member_button_callback( # type: ignore[misc]
async def no_strike_member_button_callback(
self, _: discord.Button, interaction: discord.Interaction
) -> None:
"""
Expand All @@ -133,10 +133,10 @@ async def no_strike_member_button_callback( # type: ignore[misc]
class ConfirmManualModerationView(View):
"""A discord.View to confirm manually applying a moderation action."""

@discord.ui.button(
@discord.ui.button( # type: ignore[arg-type]
label="Yes", style=discord.ButtonStyle.red, custom_id="yes_manual_moderation_action"
)
async def yes_manual_moderation_action_button_callback( # type: ignore[misc]
async def yes_manual_moderation_action_button_callback(
self, _: discord.Button, interaction: discord.Interaction
) -> None:
"""
Expand All @@ -153,10 +153,10 @@ async def yes_manual_moderation_action_button_callback( # type: ignore[misc]
view=None
) # NOTE: Despite removing the view within the normal command processing loop, the view also needs to be removed here to prevent an Unknown Webhook error

@discord.ui.button(
@discord.ui.button( # type: ignore[arg-type]
label="No", style=discord.ButtonStyle.grey, custom_id="no_manual_moderation_action"
)
async def no_manual_moderation_action_button_callback( # type: ignore[misc]
async def no_manual_moderation_action_button_callback(
self, _: discord.Button, interaction: discord.Interaction
) -> None:
"""
Expand All @@ -177,10 +177,10 @@ async def no_manual_moderation_action_button_callback( # type: ignore[misc]
class ConfirmStrikesOutOfSyncWithBanView(View):
"""A discord.View containing two buttons to confirm banning a member with > 3 strikes."""

@discord.ui.button(
@discord.ui.button( # type: ignore[arg-type]
label="Yes", style=discord.ButtonStyle.red, custom_id="yes_out_of_sync_ban_member"
)
async def yes_out_of_sync_ban_member_button_callback( # type: ignore[misc]
async def yes_out_of_sync_ban_member_button_callback(
self, _: discord.Button, interaction: discord.Interaction
) -> None:
"""
Expand All @@ -197,10 +197,10 @@ async def yes_out_of_sync_ban_member_button_callback( # type: ignore[misc]
view=None
) # NOTE: Despite removing the view within the normal command processing loop, the view also needs to be removed here to prevent an Unknown Webhook error

@discord.ui.button(
@discord.ui.button( # type: ignore[arg-type]
label="No", style=discord.ButtonStyle.grey, custom_id="no_out_of_sync_ban_member"
)
async def no_out_of_sync_ban_member_button_callback( # type: ignore[misc]
async def no_out_of_sync_ban_member_button_callback(
self, _: discord.Button, interaction: discord.Interaction
) -> None:
"""
Expand Down Expand Up @@ -264,7 +264,7 @@ async def _send_strike_user_message(
async def _confirm_perform_moderation_action(
self,
message_sender_component: "MessageSavingSenderComponent",
interaction_user: discord.User,
interaction_user: discord.User | discord.Member,
strike_user: discord.Member,
confirm_strike_message: str,
actual_strike_amount: int,
Expand Down Expand Up @@ -315,7 +315,7 @@ async def _confirm_perform_moderation_action(
async def _confirm_increase_strike(
self,
message_sender_component: "MessageSavingSenderComponent",
interaction_user: discord.User,
interaction_user: discord.User | discord.Member,
strike_user: discord.User | discord.Member,
member_strikes: DiscordMemberStrikes,
button_callback_channel: discord.TextChannel | discord.DMChannel,
Expand Down Expand Up @@ -401,6 +401,12 @@ async def _command_perform_strike(
Also calls the process of performing the appropriate moderation action,
given the new number of strikes that the member has.
"""
if not isinstance(ctx.channel, (discord.TextChannel, discord.DMChannel)):
await self.command_send_error(
ctx, message="This command can only be used in text channels or DMs."
)
return

if strike_member.bot:
await self.command_send_error(
ctx,
Expand Down Expand Up @@ -504,7 +510,8 @@ async def _confirm_manual_add_strike( # noqa: PLR0915
async for _audit_log_entry in main_guild.audit_logs(
after=discord.utils.utcnow() - datetime.timedelta(minutes=1), action=action
)
if _audit_log_entry.target.id
if _audit_log_entry.target
and _audit_log_entry.target.id
== strike_user.id # NOTE: IDs are checked here rather than the objects themselves as the audit log provides an unusual object type in some cases.
)
except (StopIteration, StopAsyncIteration):
Expand Down Expand Up @@ -750,9 +757,13 @@ async def on_member_update(self, before: discord.Member, after: discord.Member)

audit_log_entry: discord.AuditLogEntry
async for audit_log_entry in main_guild.audit_logs(limit=5):
FOUND_CORRECT_AUDIT_LOG_ENTRY: bool = audit_log_entry.target.id == after.id and (
audit_log_entry.action
== discord.AuditLogAction.auto_moderation_user_communication_disabled
FOUND_CORRECT_AUDIT_LOG_ENTRY: bool = (
(audit_log_entry.target is not None)
and (audit_log_entry.target.id == after.id)
and (
audit_log_entry.action
== discord.AuditLogAction.auto_moderation_user_communication_disabled
)
)
if FOUND_CORRECT_AUDIT_LOG_ENTRY:
await self._confirm_manual_add_strike(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ main = [
"matplotlib>=3.10",
"mplcyberpunk>=0.7",
"parsedatetime>=2.6",
"py-cord>=2.6,<2.7",
"py-cord>=2.8",
Comment thread
MattyTheHacker marked this conversation as resolved.
"python-dotenv>=1.0",
"python-logging-discord-handler>=0.1",
"typed_classproperties>=1.2",
Expand Down
1 change: 0 additions & 1 deletion stubs/discord/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ from .team import *
from .template import *
from .threads import *
from .user import *
from .voice_client import *
from .webhook import *
from .welcome_screen import *
from .widget import *
Expand Down
2 changes: 1 addition & 1 deletion utils/message_sender_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async def delete(self) -> None:
await self.sent_message.delete()

else:
await self.sent_message.delete_original_message()
await self.sent_message.delete_original_response()


class ChannelMessageSender(MessageSavingSenderComponent):
Expand Down
5 changes: 3 additions & 2 deletions utils/tex_bot_base_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,11 @@ async def command_send_error(
COMMAND_NAME: Final[str] = (
ctx.command.callback.__name__
if (
hasattr(ctx.command, "callback")
ctx.command
and 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 "unknown")
)

await self.send_error(
Expand Down
2 changes: 1 addition & 1 deletion utils/tex_bot_contexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ class TeXBotApplicationContext(discord.ApplicationContext):

bot: "TeXBot" # type: ignore[mutable-override]

respond: "Callable[..., Awaitable[Interaction | WebhookMessage]]" # type: ignore[explicit-any]
respond: "Callable[..., Awaitable[Interaction | WebhookMessage]]" # type: ignore[assignment, explicit-any]
9 changes: 5 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading