Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9f18b0d
Bump py-cord to 2.8
MattyTheHacker May 18, 2026
8d31037
Fix mypy errors
MattyTheHacker May 18, 2026
95c3a40
Ruff format
MattyTheHacker May 18, 2026
c678386
[autofix.ci] apply automated fixes
autofix-ci[bot] May 18, 2026
2f139e5
Fix more mypy errors
MattyTheHacker May 18, 2026
3dd8cae
Add ignores
MattyTheHacker May 18, 2026
98808d2
Fix type union errors
MattyTheHacker May 18, 2026
1f56d87
Fix flake8 error
MattyTheHacker May 18, 2026
2039e2e
Merge main into bump-pycord
automatic-pr-updater[bot] May 19, 2026
efee50f
Merge main into bump-pycord
automatic-pr-updater[bot] May 23, 2026
1c0065f
Merge main into bump-pycord
automatic-pr-updater[bot] May 25, 2026
f1f675e
Merge main into bump-pycord
automatic-pr-updater[bot] May 25, 2026
1456c75
Merge main into bump-pycord
automatic-pr-updater[bot] May 26, 2026
ed4141b
Merge main into bump-pycord
automatic-pr-updater[bot] May 27, 2026
70eba44
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 3, 2026
8a13c8f
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 3, 2026
a9fc816
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 4, 2026
a598e15
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 7, 2026
342df28
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 8, 2026
8f70166
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 10, 2026
62bfe25
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 17, 2026
d75a36e
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 18, 2026
12f0f6e
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 21, 2026
48c67ce
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 23, 2026
a5f76ae
Merge main into bump-pycord
automatic-pr-updater[bot] Jun 24, 2026
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()
Comment thread
MattyTheHacker marked this conversation as resolved.


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