From cfb8e98d06ef5909a2ca80fb789135e41a0ecae8 Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sat, 27 Sep 2025 22:08:24 +0200 Subject: [PATCH 1/9] Move from role-based to permissions-based checks --- src/command-handler.ts | 7 +-- src/components/anti-everyone.ts | 5 +- src/components/anti-invite-links.ts | 2 +- src/components/help.ts | 2 +- src/components/moderation/kick.ts | 2 +- src/components/moderation/massban.ts | 7 ++- .../moderation/moderation-common.ts | 52 +++++---------- src/components/moderation/modlogs.ts | 7 ++- src/components/moderation/modmail.ts | 6 +- src/components/moderation/modstats.ts | 8 +-- src/components/moderation/mute.ts | 2 +- src/components/moderation/note.ts | 2 +- src/components/moderation/purge.ts | 7 ++- src/components/moderation/rolepersist.ts | 4 +- src/components/moderation/timeout.ts | 2 +- src/components/moderation/warn.ts | 2 +- src/components/thread-control.ts | 5 +- .../tccpp/components/april1/buzzwords.ts | 4 +- src/modules/tccpp/components/forum-control.ts | 5 +- src/modules/tccpp/components/roulette.ts | 7 ++- .../components/server-suggestion-reactions.ts | 9 ++- .../components/server-suggestion-tracker.ts | 20 ++++-- src/modules/tccpp/components/starboard.ts | 8 ++- src/modules/tccpp/components/utility-tools.ts | 2 +- src/wheatley.ts | 63 +++---------------- 25 files changed, 110 insertions(+), 130 deletions(-) diff --git a/src/command-handler.ts b/src/command-handler.ts index 37dc5e9e..2a260f75 100644 --- a/src/command-handler.ts +++ b/src/command-handler.ts @@ -148,8 +148,8 @@ export class CommandHandler { JSON.stringify(command_body), ); if (command.permissions !== undefined) { - const member = await this.wheatley.try_fetch_guild_member(await command_obj.get_member()); - if (!member || !member.permissions.has(command.permissions)) { + const member = await command_obj.get_member(); + if (!member.permissions.has(command.permissions)) { await command_obj.reply({ files: ["https://miro.medium.com/v2/resize:fit:750/1*lMV_u6tnu9WmFuJRyhTsFQ.jpeg"], should_text_reply: true, @@ -209,8 +209,7 @@ export class CommandHandler { const command_options: unknown[] = []; const command_object = new TextBasedCommand(interaction.commandName, command, interaction, this.wheatley); if (command.permissions !== undefined) { - const member = await this.wheatley.try_fetch_guild_member(interaction.user.id); - if (!member || !member.permissions.has(command.permissions)) { + if (await this.wheatley.fetch_member_if_permitted(interaction.user, command.permissions)) { await interaction.reply({ files: ["https://miro.medium.com/v2/resize:fit:750/1*lMV_u6tnu9WmFuJRyhTsFQ.jpeg"], }); diff --git a/src/components/anti-everyone.ts b/src/components/anti-everyone.ts index 6733ed1c..3a3e0cc4 100644 --- a/src/components/anti-everyone.ts +++ b/src/components/anti-everyone.ts @@ -25,7 +25,10 @@ export default class AntiEveryone extends BotComponent { // bot message.author.bot || // mod - this.wheatley.is_authorized_mod(message.author) || + (await this.wheatley.fetch_member_if_permitted( + message.author, + Discord.PermissionFlagsBits.MentionEveryone, + )) || // outside of TCCPP (like DMs) message.guildId != this.wheatley.guild.id ) { diff --git a/src/components/anti-invite-links.ts b/src/components/anti-invite-links.ts index 704ad931..e690a128 100644 --- a/src/components/anti-invite-links.ts +++ b/src/components/anti-invite-links.ts @@ -50,7 +50,7 @@ export default class AntiInviteLinks extends BotComponent { } async handle_message(message: Discord.Message) { - if (this.wheatley.is_authorized_mod(message.author)) { + if (await this.wheatley.fetch_member_if_permitted(message.author, Discord.PermissionFlagsBits.Administrator)) { return; } const match = match_invite(message.content); diff --git a/src/components/help.ts b/src/components/help.ts index 0061fd7a..7e35687a 100644 --- a/src/components/help.ts +++ b/src/components/help.ts @@ -82,7 +82,7 @@ export default class Help extends BotComponent { }, ), ]; - if (this.wheatley.is_authorized_mod(command.user)) { + if (await this.wheatley.fetch_member_if_permitted(command.user, Discord.PermissionFlagsBits.ModerateMembers)) { embeds.push( new Discord.EmbedBuilder().setColor(colors.wheatley).addFields( { diff --git a/src/components/moderation/kick.ts b/src/components/moderation/kick.ts index 66bfdc05..e14f15e4 100644 --- a/src/components/moderation/kick.ts +++ b/src/components/moderation/kick.ts @@ -30,7 +30,7 @@ export default class Kick extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("kick", EarlyReplyMode.visible) - .set_permissions(Discord.PermissionFlagsBits.BanMembers) + .set_permissions(Discord.PermissionFlagsBits.KickMembers) .set_description("Kick user") .add_user_option({ title: "user", diff --git a/src/components/moderation/massban.ts b/src/components/moderation/massban.ts index 8f26b15e..37eaf8a1 100644 --- a/src/components/moderation/massban.ts +++ b/src/components/moderation/massban.ts @@ -26,7 +26,12 @@ export default class Massban extends BotComponent { } if (message.content.startsWith("!wban")) { assert(message.member != null); - if (this.wheatley.is_authorized_mod(message.member)) { + if ( + await this.wheatley.fetch_member_if_permitted( + message.member, + Discord.PermissionFlagsBits.BanMembers, + ) + ) { await this.do_mass_ban(message); } else { await message.reply(`Unauthorized ${this.wheatley.emoji.access_denied}`); diff --git a/src/components/moderation/moderation-common.ts b/src/components/moderation/moderation-common.ts index efcd91eb..40b448b2 100644 --- a/src/components/moderation/moderation-common.ts +++ b/src/components/moderation/moderation-common.ts @@ -57,11 +57,11 @@ import { CommandSetBuilder } from "../../command-abstractions/command-set-builde export const duration_regex = /perm\b|(\d+)\s*([a-zA-Z]+)/; -export const moderation_on_team_member_message: string = "Can't apply this moderation on team members"; -export const joke_responses = [ +export const joke_responses_other = ["You have no power over this user :(", "lol, nice try!", "One day, maybe ;)"]; +export const joke_responses_self = [ "You won't get off that easy! ;)", "Try again next time lmao", - "Didn't work. Maybe a skill issue?", + "Didn't work. skill issue?", ]; function millis_of_time_unit(u: string) { @@ -564,14 +564,19 @@ export abstract class ModerationComponent extends BotComponent { basic_moderation_info: basic_moderation, ) { try { - if (this.wheatley.is_authorized_mod(user)) { - // Check if the mod is trying to ban themselves - if (basic_moderation_info.type == "ban" && command.user.id == user.id) { - // If the mod is trying to ban themselves then troll them ;) - await this.reply_with_error(command, unwrap(get_random_array_element(joke_responses))); - } else { - await this.reply_with_error(command, moderation_on_team_member_message); + const target = await this.wheatley.try_fetch_guild_member(user); + const issuer = unwrap(await this.wheatley.try_fetch_guild_member(command.user)); + if (target && target.roles.highest.position >= issuer.roles.highest.position) { + if ( + command.user.id == user.id && + (basic_moderation_info.type == "ban" || basic_moderation_info.type == "kick") + ) { + // Mod is trying to ban/kick themselves => troll them ;) + await this.reply_with_error(command, unwrap(get_random_array_element(joke_responses_self))); + return; } + // Mod is trying to ban/kick above their paygrade => troll them :D + await this.reply_with_error(command, unwrap(get_random_array_element(joke_responses_other))); return; } const base_moderation: basic_moderation_with_user = { ...basic_moderation_info, user: user.id }; @@ -647,32 +652,7 @@ export abstract class ModerationComponent extends BotComponent { ) { try { for (const user of users) { - if (this.wheatley.is_authorized_mod(user)) { - await this.reply_with_error(command, moderation_on_team_member_message); - continue; - } - const base_moderation: basic_moderation_with_user = { ...basic_moderation_info, user: user.id }; - if (!this.is_once_off && (await this.is_moderation_applied(base_moderation))) { - await this.reply_with_error(command, `${user.displayName} is already ${this.past_participle}`); - continue; - } - const moderation: moderation_entry = { - ...basic_moderation_info, - case_number: -1, - user: user.id, - user_name: user.displayName, - moderator: command.user.id, - moderator_name: (await command.get_member()).displayName, - reason, - issued_at: Date.now(), - duration: parse_nullable_duration(duration_string), - active: !this.is_once_off, - removed: null, - expunged: null, - link: command.get_or_forge_url(), - }; - await this.notify_user(user, this.past_participle, moderation); - await this.issue_moderation(moderation); + await this.moderation_issue_handler(command, user, duration_string, reason, basic_moderation_info); } await command.replyOrFollowUp({ embeds: [ diff --git a/src/components/moderation/modlogs.ts b/src/components/moderation/modlogs.ts index e18ea704..90b08f32 100644 --- a/src/components/moderation/modlogs.ts +++ b/src/components/moderation/modlogs.ts @@ -216,7 +216,12 @@ export default class Modlogs extends BotComponent { override async on_interaction_create(interaction: Discord.Interaction) { if (interaction.isButton()) { if (interaction.customId.startsWith("modlogs_page_")) { - if (!this.wheatley.is_authorized_mod(interaction.user)) { + if ( + !(await this.wheatley.fetch_member_if_permitted( + interaction.user, + Discord.PermissionFlagsBits.BanMembers, + )) + ) { await interaction.reply({ content: "Error: You are not authorized", ephemeral: true, diff --git a/src/components/moderation/modmail.ts b/src/components/moderation/modmail.ts index 8888e98c..7071abb0 100644 --- a/src/components/moderation/modmail.ts +++ b/src/components/moderation/modmail.ts @@ -198,8 +198,7 @@ export default class Modmail extends BotComponent { }); await this.log_action(interaction.member, "Monkey pressed the button"); try { - // can't apply roles to root - if (!this.wheatley.is_root(interaction.user)) { + if ((await this.wheatley.try_fetch_guild_member(interaction.user))?.manageable) { const member = await this.wheatley.guild.members.fetch(interaction.user.id); await member.roles.add(this.wheatley.roles.monke); this.monke_set.set(interaction.user.id, Date.now()); @@ -219,8 +218,7 @@ export default class Modmail extends BotComponent { ephemeral: true, }); try { - // can't apply roles to root - if (!this.wheatley.is_root(interaction.user)) { + if ((await this.wheatley.try_fetch_guild_member(interaction.user))?.manageable) { await member.roles.remove(this.wheatley.roles.monke); this.monke_set.remove(member.id); } diff --git a/src/components/moderation/modstats.ts b/src/components/moderation/modstats.ts index 471e02c1..6e55f711 100644 --- a/src/components/moderation/modstats.ts +++ b/src/components/moderation/modstats.ts @@ -23,7 +23,7 @@ export default class ModStats extends BotComponent { commands.add( new TextBasedCommandBuilder("modstats", EarlyReplyMode.none) .set_description("Moderator stats") - .set_permissions(Discord.PermissionFlagsBits.BanMembers) + .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .add_user_option({ title: "moderator", description: "Moderator", @@ -60,11 +60,7 @@ export default class ModStats extends BotComponent { } async modstats(command: TextBasedCommand, moderator: Discord.User | null) { - if (moderator && !(this.wheatley.is_authorized_mod(moderator) || moderator.id == this.wheatley.user.id)) { - await command.reply(`<@${moderator.id}> is not a moderator`); - return; - } - if (!this.wheatley.is_authorized_mod(command.user) && command.channel_id != this.bot_spam.id) { + if (command.channel_id != this.bot_spam.id) { await command.reply(`Please use in <#${this.bot_spam.id}>`, true); return; } diff --git a/src/components/moderation/mute.ts b/src/components/moderation/mute.ts index 6aba71a6..50e3dced 100644 --- a/src/components/moderation/mute.ts +++ b/src/components/moderation/mute.ts @@ -28,7 +28,7 @@ export default class Mute extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("mute", EarlyReplyMode.visible) - .set_permissions(Discord.PermissionFlagsBits.BanMembers) + .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Mute user") .add_user_option({ title: "user", diff --git a/src/components/moderation/note.ts b/src/components/moderation/note.ts index e5a674aa..68f4e1c5 100644 --- a/src/components/moderation/note.ts +++ b/src/components/moderation/note.ts @@ -29,7 +29,7 @@ export default class Note extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("note", EarlyReplyMode.ephemeral) - .set_permissions(Discord.PermissionFlagsBits.BanMembers) + .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Enter note in modlogs") .add_user_option({ title: "user", diff --git a/src/components/moderation/purge.ts b/src/components/moderation/purge.ts index 3808e816..3ce722d7 100644 --- a/src/components/moderation/purge.ts +++ b/src/components/moderation/purge.ts @@ -163,7 +163,12 @@ export default class Purge extends BotComponent { override async on_interaction_create(interaction: Discord.Interaction) { if (interaction.isButton()) { if (interaction.customId.startsWith("abort_purge_")) { - if (!this.wheatley.is_authorized_mod(interaction.user)) { + if ( + !(await this.wheatley.fetch_member_if_permitted( + interaction.user, + Discord.PermissionFlagsBits.ManageMessages, + )) + ) { await interaction.reply({ content: "Error: You are not authorized", ephemeral: true, diff --git a/src/components/moderation/rolepersist.ts b/src/components/moderation/rolepersist.ts index cf292ba4..e0f63567 100644 --- a/src/components/moderation/rolepersist.ts +++ b/src/components/moderation/rolepersist.ts @@ -28,7 +28,7 @@ export default class Rolepersist extends ModerationComponent { override async setup(commands: CommandSetBuilder) { commands.add( new TextBasedCommandBuilder("rolepersist", EarlyReplyMode.visible) - .set_permissions(Discord.PermissionFlagsBits.BanMembers) + .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Rolepersist add/remove") .add_subcommand( new TextBasedCommandBuilder("add", EarlyReplyMode.visible) @@ -113,7 +113,7 @@ export default class Rolepersist extends ModerationComponent { for (const [command, role] of Object.entries(aliases)) { commands.add( new TextBasedCommandBuilder(command, EarlyReplyMode.visible) - .set_permissions(Discord.PermissionFlagsBits.BanMembers) + .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description(`${capitalize(role).replace("_", " ")}`) .add_user_option({ title: "user", diff --git a/src/components/moderation/timeout.ts b/src/components/moderation/timeout.ts index 0f894c50..efa63d78 100644 --- a/src/components/moderation/timeout.ts +++ b/src/components/moderation/timeout.ts @@ -26,7 +26,7 @@ export default class Timeout extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("timeout", EarlyReplyMode.visible) - .set_permissions(Discord.PermissionFlagsBits.BanMembers) + .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Timeout add / remove") .add_subcommand( new TextBasedCommandBuilder("add", EarlyReplyMode.visible) diff --git a/src/components/moderation/warn.ts b/src/components/moderation/warn.ts index ca4d1694..2dedbd61 100644 --- a/src/components/moderation/warn.ts +++ b/src/components/moderation/warn.ts @@ -27,7 +27,7 @@ export default class Warn extends ModerationComponent { await super.setup(commands); commands.add( new TextBasedCommandBuilder("warn", EarlyReplyMode.visible) - .set_permissions(Discord.PermissionFlagsBits.BanMembers) + .set_permissions(Discord.PermissionFlagsBits.ModerateMembers) .set_description("Warn user") .add_user_option({ title: "user", diff --git a/src/components/thread-control.ts b/src/components/thread-control.ts index 3a250fd5..52f6271f 100644 --- a/src/components/thread-control.ts +++ b/src/components/thread-control.ts @@ -51,7 +51,10 @@ export default class ThreadControl extends BotComponent { return true; // just let the user do it, should be fine } const owner_id = await this.get_owner(thread); - if (owner_id == request.user.id || this.wheatley.is_authorized_mod(request.user.id)) { + if ( + owner_id == request.user.id || + (await this.wheatley.fetch_member_if_permitted(request.user, Discord.PermissionFlagsBits.ManageThreads)) + ) { return true; } else { await request.reply({ diff --git a/src/modules/tccpp/components/april1/buzzwords.ts b/src/modules/tccpp/components/april1/buzzwords.ts index ef43b69b..340f8225 100644 --- a/src/modules/tccpp/components/april1/buzzwords.ts +++ b/src/modules/tccpp/components/april1/buzzwords.ts @@ -325,7 +325,9 @@ export default class Buzzwords extends BotComponent { return; } //if(message.channel.id != "1091502908241084436") return; // for now, for testing - if (this.wheatley.is_authorized_mod(message.author)) { + if ( + await this.wheatley.fetch_member_if_permitted(message.author, Discord.PermissionFlagsBits.ModerateMembers) + ) { if (message.content.trim().startsWith("!derailed")) { const ids = message.content.match(/\d{10,}/g); if (!ids || ids.length != 1) { diff --git a/src/modules/tccpp/components/forum-control.ts b/src/modules/tccpp/components/forum-control.ts index 5febec9b..6898b239 100644 --- a/src/modules/tccpp/components/forum-control.ts +++ b/src/modules/tccpp/components/forum-control.ts @@ -56,7 +56,10 @@ export default class ForumControl extends BotComponent { if (channel.isThread()) { const thread = channel; const owner_id = await this.get_owner(thread); - if (owner_id == request.user.id || this.wheatley.is_authorized_mod(request.user.id)) { + if ( + owner_id == request.user.id || + (await this.wheatley.fetch_member_if_permitted(request.user, Discord.PermissionFlagsBits.ManageThreads)) + ) { return true; } else { await request.reply({ diff --git a/src/modules/tccpp/components/roulette.ts b/src/modules/tccpp/components/roulette.ts index d409a7e7..d1ae6ae5 100644 --- a/src/modules/tccpp/components/roulette.ts +++ b/src/modules/tccpp/components/roulette.ts @@ -110,7 +110,12 @@ export default class Roulette extends BotComponent { this.streaks.set(command.user.id, 0); await this.update_score(command.user.id); // TODO: I forget why this is here try { - if (this.wheatley.is_authorized_mod(command.user)) { + if ( + await this.wheatley.fetch_member_if_permitted( + command.user, + Discord.PermissionFlagsBits.ModerateMembers, + ) + ) { this.disabled_users.insert(command.user.id); } else { await (await command.get_member()).timeout(30 * MINUTE, "Bang"); diff --git a/src/modules/tccpp/components/server-suggestion-reactions.ts b/src/modules/tccpp/components/server-suggestion-reactions.ts index c5044ae7..f1f80966 100644 --- a/src/modules/tccpp/components/server-suggestion-reactions.ts +++ b/src/modules/tccpp/components/server-suggestion-reactions.ts @@ -41,7 +41,12 @@ export default class ServerSuggestionReactions extends BotComponent { }); await reaction.users.remove(id); } else if (root_only_reacts.has(reaction.emoji.name!)) { - if (!this.wheatley.is_root(user)) { + if ( + await this.wheatley.fetch_member_if_permitted( + user.id, + Discord.PermissionFlagsBits.Administrator, + ) + ) { M.log("removing non-root reaction", { content: reaction.message.content, reaction: reaction.emoji.name, @@ -136,7 +141,7 @@ export default class ServerSuggestionReactions extends BotComponent { reaction.users.remove(user.id).catch(this.wheatley.critical_error.bind(this.wheatley)); }, 5 * MINUTE); } else if (root_only_reacts.has(reaction.emoji.name!)) { - if (!this.wheatley.is_root(user)) { + if (await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) { M.log("removing non-root reaction", { content: reaction.message.content, reaction: reaction.emoji.name, diff --git a/src/modules/tccpp/components/server-suggestion-tracker.ts b/src/modules/tccpp/components/server-suggestion-tracker.ts index 6e76722d..785412ff 100644 --- a/src/modules/tccpp/components/server-suggestion-tracker.ts +++ b/src/modules/tccpp/components/server-suggestion-tracker.ts @@ -97,7 +97,9 @@ export default class ServerSuggestionTracker extends BotComponent { if (resolution_reactions_set.has(reaction.emoji.name!)) { const users = await reaction.users.fetch(); for (const [_, user] of users) { - if (this.wheatley.is_root(user)) { + if ( + await this.wheatley.fetch_member_if_permitted(user, Discord.PermissionFlagsBits.Administrator) + ) { roots.push({ user, emoji: reaction.emoji }); } } @@ -474,7 +476,7 @@ export default class ServerSuggestionTracker extends BotComponent { ) { const reaction = await departialize(_reaction); if (resolution_reactions_set.has(reaction.emoji.name!)) { - if (this.wheatley.is_root(user)) { + if (await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) { await this.resolve_suggestion(await departialize(reaction.message), { user: await departialize(user), emoji: reaction.emoji, @@ -487,7 +489,10 @@ export default class ServerSuggestionTracker extends BotComponent { reaction: Discord.MessageReaction | Discord.PartialMessageReaction, user: Discord.User | Discord.PartialUser, ) { - if (resolution_reactions_set.has(reaction.emoji.name!) && this.wheatley.is_root(user)) { + if ( + resolution_reactions_set.has(reaction.emoji.name!) && + (await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) + ) { const message = await departialize(reaction.message); if (!(await this.message_has_resolution_from_root(message))) { // reopen @@ -527,7 +532,7 @@ export default class ServerSuggestionTracker extends BotComponent { message.author.id == this.wheatley.user.id && user.id != this.wheatley.user.id && // ignore self - this is important for autoreacts resolution_reactions_set.has(reaction.emoji.name!) && - this.wheatley.is_root(user) + (await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) ) { // expensive-ish but this will be rare const suggestion_id = await this.reverse_lookup(message.id); @@ -565,9 +570,12 @@ export default class ServerSuggestionTracker extends BotComponent { } catch (e) { this.wheatley.critical_error(e); try { - if (this.wheatley.is_root(user)) { + const member = await this.wheatley.fetch_member_if_permitted( + user.id, + Discord.PermissionFlagsBits.Administrator, + ); + if (member) { // only send diagnostics to root - const member = await this.wheatley.guild.members.fetch(user.id); await member.send("Error while resolving suggestion"); } } catch (e) { diff --git a/src/modules/tccpp/components/starboard.ts b/src/modules/tccpp/components/starboard.ts index a750a10f..69682d81 100644 --- a/src/modules/tccpp/components/starboard.ts +++ b/src/modules/tccpp/components/starboard.ts @@ -295,7 +295,13 @@ export default class Starboard extends BotComponent { if (trigger_reaction.count <= max_non_negative) { do_delete = false; } - if (this.wheatley.is_root(message.author) || message.author.bot) { + if ( + (await this.wheatley.fetch_member_if_permitted( + message.author, + Discord.PermissionFlagsBits.Administrator, + )) || + message.author.bot + ) { do_delete = false; } if (this.deletes_in_last_24h() >= max_deletes_in_24h) { diff --git a/src/modules/tccpp/components/utility-tools.ts b/src/modules/tccpp/components/utility-tools.ts index dd1d8045..3338f4b2 100644 --- a/src/modules/tccpp/components/utility-tools.ts +++ b/src/modules/tccpp/components/utility-tools.ts @@ -32,7 +32,7 @@ export default class UtilityTools extends BotComponent { if (message.author.bot) { return; } - if (this.wheatley.is_authorized_mod(message.author)) { + if (await this.wheatley.fetch_member_if_permitted(message.author, Discord.PermissionFlagsBits.ManageChannels)) { if (message.content == "!channel-rename") { M.log("got !channel-rename"); assert(!(message.channel instanceof Discord.PartialGroupDMChannel)); diff --git a/src/wheatley.ts b/src/wheatley.ts index b52ced22..9a176c14 100644 --- a/src/wheatley.ts +++ b/src/wheatley.ts @@ -207,34 +207,6 @@ export const skill_roles_order_id = [ "331719591405551616", ]; -// General config -// TODO: Can eliminate this stuff -export const root_ids = new Set([ - "199943082441965577", // zelis - "110756651694297088", // vincent - "89441674844995584", // styx - "313597351262683138", // dot - // prevent Wheatley reactions being removed in server suggestions and also allow some elegant handling - "597216680271282192", // wheatley -]); - -export const root_mod_ids = [ - "199943082441965577", // zelis - "230282234085638155", // cas - "719255892813545502", // sampersand - "89441674844995584", // styx - "110756651694297088", // vincent - "138014214093668353", // dxpower - "313597351262683138", // dot - "413463039145410560", // karnage - "512649489300062228", // quicknir - "446584068746772480", // yinsei - "213759964789866496", // levi - "162964325823283200", // eisen -]; - -export const root_mod_ids_set = new Set(root_mod_ids); - type EventMap = { wheatley_ready: () => void; issue_moderation: (moderation: moderation_entry) => void; @@ -292,9 +264,6 @@ export class Wheatley { [k in keyof typeof skill_roles_map]: Discord.Role; } = {} as any; - // TODO: Eliminate pre-set value - root_mod_list = "jr.0, dot42, styxs, or _64"; - message_counter = new PromClient.Counter({ name: "tccpp_message_count", help: "TCCPP message count", @@ -483,8 +452,6 @@ export class Wheatley { M.log(`Fetched role ${k}`); }), ); - // fetch list of roots and mods, replace hard-coded list - await wrap(() => this.fetch_root_mod_list(this.client)); } async add_component(component: { new (w: Wheatley): T; get is_freestanding(): boolean }) { @@ -656,17 +623,17 @@ export class Wheatley { return reply_message; } - is_root(user: Discord.User | Discord.PartialUser | Discord.APIUser): boolean { - //return member.roles.cache.some(r => r.id == root_role_id); - return root_ids.has(user.id); + async fetch_member_if_permitted( + options: Discord.GuildMember | Discord.User | Discord.UserResolvable | Discord.FetchMemberOptions, + permissions: Discord.PermissionResolvable, + ) { + const member = await this.try_fetch_guild_member(options); + return member?.permissions.has(permissions) ? member : null; } - is_authorized_mod(member: Discord.GuildMember | Discord.User | string): boolean { - if (is_string(member)) { - return root_mod_ids_set.has(member); - } else { - return root_mod_ids_set.has(member.id); - } + staff_contacts() { + const roots = this.roles.root.members.map(member => `<@${member.id}>`); + return roots.length > 1 ? roots.slice(0, -1).join(", ") + `, or ${roots[roots.length - 1]}` : roots[0]; } has_skill_roles_other_than_beginner(member: Discord.GuildMember) { @@ -681,16 +648,6 @@ export class Wheatley { return skill_roles_order_id.indexOf(role instanceof Discord.Role ? role.id : role); } - async fetch_root_mod_list(client: Discord.Client) { - const tags = []; - for (const id of root_mod_ids) { - tags.push((await client.users.fetch(id)).tag); - } - assert(tags.length > 3); - this.root_mod_list = tags.slice(0, tags.length - 1).join(", ") + ", or " + tags[tags.length - 1]; - M.debug("root_mod_list", [this.root_mod_list]); - } - async is_public_channel(channel: Discord.GuildTextBasedChannel | Discord.TextBasedChannel) { return ( !(channel instanceof Discord.ForumChannel) && @@ -706,7 +663,7 @@ export class Wheatley { } async try_fetch_guild_member( - options: Discord.GuildMember | Discord.UserResolvable | Discord.FetchMemberOptions, + options: Discord.GuildMember | Discord.User | Discord.UserResolvable | Discord.FetchMemberOptions, ): Promise { if (options instanceof Discord.GuildMember) { if (options.guild.id == this.guild.id) { From c10ce94c6bb4e36cc27f5f6082bf9e8af357c71b Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sat, 27 Sep 2025 22:35:45 +0200 Subject: [PATCH 2/9] fix check --- src/command-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command-handler.ts b/src/command-handler.ts index 2a260f75..849e2895 100644 --- a/src/command-handler.ts +++ b/src/command-handler.ts @@ -209,7 +209,7 @@ export class CommandHandler { const command_options: unknown[] = []; const command_object = new TextBasedCommand(interaction.commandName, command, interaction, this.wheatley); if (command.permissions !== undefined) { - if (await this.wheatley.fetch_member_if_permitted(interaction.user, command.permissions)) { + if (!(await this.wheatley.fetch_member_if_permitted(interaction.user, command.permissions))) { await interaction.reply({ files: ["https://miro.medium.com/v2/resize:fit:750/1*lMV_u6tnu9WmFuJRyhTsFQ.jpeg"], }); From b6cfa6406155663e5cb69b8fd54cef2f4f9dfac3 Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sat, 27 Sep 2025 22:43:25 +0200 Subject: [PATCH 3/9] fix another check --- src/command-handler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/command-handler.ts b/src/command-handler.ts index 849e2895..e1d3b69f 100644 --- a/src/command-handler.ts +++ b/src/command-handler.ts @@ -148,8 +148,7 @@ export class CommandHandler { JSON.stringify(command_body), ); if (command.permissions !== undefined) { - const member = await command_obj.get_member(); - if (!member.permissions.has(command.permissions)) { + if (!(await this.wheatley.fetch_member_if_permitted(command_obj.user, command.permissions))) { await command_obj.reply({ files: ["https://miro.medium.com/v2/resize:fit:750/1*lMV_u6tnu9WmFuJRyhTsFQ.jpeg"], should_text_reply: true, From a0a642e0b72976a2f902294a64ea6cc8af93ccf6 Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sat, 27 Sep 2025 23:01:56 +0200 Subject: [PATCH 4/9] fix yet another check --- .../tccpp/components/server-suggestion-reactions.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modules/tccpp/components/server-suggestion-reactions.ts b/src/modules/tccpp/components/server-suggestion-reactions.ts index f1f80966..d8a6fc61 100644 --- a/src/modules/tccpp/components/server-suggestion-reactions.ts +++ b/src/modules/tccpp/components/server-suggestion-reactions.ts @@ -42,10 +42,10 @@ export default class ServerSuggestionReactions extends BotComponent { await reaction.users.remove(id); } else if (root_only_reacts.has(reaction.emoji.name!)) { if ( - await this.wheatley.fetch_member_if_permitted( + !(await this.wheatley.fetch_member_if_permitted( user.id, Discord.PermissionFlagsBits.Administrator, - ) + )) ) { M.log("removing non-root reaction", { content: reaction.message.content, @@ -141,7 +141,9 @@ export default class ServerSuggestionReactions extends BotComponent { reaction.users.remove(user.id).catch(this.wheatley.critical_error.bind(this.wheatley)); }, 5 * MINUTE); } else if (root_only_reacts.has(reaction.emoji.name!)) { - if (await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) { + if ( + !(await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) + ) { M.log("removing non-root reaction", { content: reaction.message.content, reaction: reaction.emoji.name, From 59402634610c6e5e3d73cfcae42c9804cccfec05 Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sun, 28 Sep 2025 00:18:48 +0200 Subject: [PATCH 5/9] rename to check_permissions --- src/command-handler.ts | 4 ++-- src/components/anti-everyone.ts | 5 +---- src/components/anti-invite-links.ts | 2 +- src/components/help.ts | 2 +- src/components/moderation/massban.ts | 7 +------ src/components/moderation/modlogs.ts | 5 +---- src/components/moderation/purge.ts | 2 +- src/components/thread-control.ts | 2 +- .../tccpp/components/april1/buzzwords.ts | 4 +--- src/modules/tccpp/components/forum-control.ts | 2 +- src/modules/tccpp/components/roulette.ts | 5 +---- .../components/server-suggestion-reactions.ts | 11 ++--------- .../components/server-suggestion-tracker.ts | 18 ++++++------------ src/modules/tccpp/components/starboard.ts | 5 +---- src/modules/tccpp/components/utility-tools.ts | 2 +- src/wheatley.ts | 4 ++-- 16 files changed, 24 insertions(+), 56 deletions(-) diff --git a/src/command-handler.ts b/src/command-handler.ts index e1d3b69f..66a3ff67 100644 --- a/src/command-handler.ts +++ b/src/command-handler.ts @@ -148,7 +148,7 @@ export class CommandHandler { JSON.stringify(command_body), ); if (command.permissions !== undefined) { - if (!(await this.wheatley.fetch_member_if_permitted(command_obj.user, command.permissions))) { + if (!(await this.wheatley.check_permissions(command_obj.user, command.permissions))) { await command_obj.reply({ files: ["https://miro.medium.com/v2/resize:fit:750/1*lMV_u6tnu9WmFuJRyhTsFQ.jpeg"], should_text_reply: true, @@ -208,7 +208,7 @@ export class CommandHandler { const command_options: unknown[] = []; const command_object = new TextBasedCommand(interaction.commandName, command, interaction, this.wheatley); if (command.permissions !== undefined) { - if (!(await this.wheatley.fetch_member_if_permitted(interaction.user, command.permissions))) { + if (!(await this.wheatley.check_permissions(interaction.user, command.permissions))) { await interaction.reply({ files: ["https://miro.medium.com/v2/resize:fit:750/1*lMV_u6tnu9WmFuJRyhTsFQ.jpeg"], }); diff --git a/src/components/anti-everyone.ts b/src/components/anti-everyone.ts index 3a3e0cc4..8ccd148a 100644 --- a/src/components/anti-everyone.ts +++ b/src/components/anti-everyone.ts @@ -25,10 +25,7 @@ export default class AntiEveryone extends BotComponent { // bot message.author.bot || // mod - (await this.wheatley.fetch_member_if_permitted( - message.author, - Discord.PermissionFlagsBits.MentionEveryone, - )) || + (await this.wheatley.check_permissions(message.author, Discord.PermissionFlagsBits.MentionEveryone)) || // outside of TCCPP (like DMs) message.guildId != this.wheatley.guild.id ) { diff --git a/src/components/anti-invite-links.ts b/src/components/anti-invite-links.ts index e690a128..b1cbf285 100644 --- a/src/components/anti-invite-links.ts +++ b/src/components/anti-invite-links.ts @@ -50,7 +50,7 @@ export default class AntiInviteLinks extends BotComponent { } async handle_message(message: Discord.Message) { - if (await this.wheatley.fetch_member_if_permitted(message.author, Discord.PermissionFlagsBits.Administrator)) { + if (await this.wheatley.check_permissions(message.author, Discord.PermissionFlagsBits.Administrator)) { return; } const match = match_invite(message.content); diff --git a/src/components/help.ts b/src/components/help.ts index 7e35687a..97cd20b3 100644 --- a/src/components/help.ts +++ b/src/components/help.ts @@ -82,7 +82,7 @@ export default class Help extends BotComponent { }, ), ]; - if (await this.wheatley.fetch_member_if_permitted(command.user, Discord.PermissionFlagsBits.ModerateMembers)) { + if (await this.wheatley.check_permissions(command.user, Discord.PermissionFlagsBits.ModerateMembers)) { embeds.push( new Discord.EmbedBuilder().setColor(colors.wheatley).addFields( { diff --git a/src/components/moderation/massban.ts b/src/components/moderation/massban.ts index 37eaf8a1..1cdb63ae 100644 --- a/src/components/moderation/massban.ts +++ b/src/components/moderation/massban.ts @@ -26,12 +26,7 @@ export default class Massban extends BotComponent { } if (message.content.startsWith("!wban")) { assert(message.member != null); - if ( - await this.wheatley.fetch_member_if_permitted( - message.member, - Discord.PermissionFlagsBits.BanMembers, - ) - ) { + if (await this.wheatley.check_permissions(message.member, Discord.PermissionFlagsBits.BanMembers)) { await this.do_mass_ban(message); } else { await message.reply(`Unauthorized ${this.wheatley.emoji.access_denied}`); diff --git a/src/components/moderation/modlogs.ts b/src/components/moderation/modlogs.ts index 90b08f32..1693195f 100644 --- a/src/components/moderation/modlogs.ts +++ b/src/components/moderation/modlogs.ts @@ -217,10 +217,7 @@ export default class Modlogs extends BotComponent { if (interaction.isButton()) { if (interaction.customId.startsWith("modlogs_page_")) { if ( - !(await this.wheatley.fetch_member_if_permitted( - interaction.user, - Discord.PermissionFlagsBits.BanMembers, - )) + !(await this.wheatley.check_permissions(interaction.user, Discord.PermissionFlagsBits.BanMembers)) ) { await interaction.reply({ content: "Error: You are not authorized", diff --git a/src/components/moderation/purge.ts b/src/components/moderation/purge.ts index 3ce722d7..72c0c552 100644 --- a/src/components/moderation/purge.ts +++ b/src/components/moderation/purge.ts @@ -164,7 +164,7 @@ export default class Purge extends BotComponent { if (interaction.isButton()) { if (interaction.customId.startsWith("abort_purge_")) { if ( - !(await this.wheatley.fetch_member_if_permitted( + !(await this.wheatley.check_permissions( interaction.user, Discord.PermissionFlagsBits.ManageMessages, )) diff --git a/src/components/thread-control.ts b/src/components/thread-control.ts index 52f6271f..4c75d643 100644 --- a/src/components/thread-control.ts +++ b/src/components/thread-control.ts @@ -53,7 +53,7 @@ export default class ThreadControl extends BotComponent { const owner_id = await this.get_owner(thread); if ( owner_id == request.user.id || - (await this.wheatley.fetch_member_if_permitted(request.user, Discord.PermissionFlagsBits.ManageThreads)) + (await this.wheatley.check_permissions(request.user, Discord.PermissionFlagsBits.ManageThreads)) ) { return true; } else { diff --git a/src/modules/tccpp/components/april1/buzzwords.ts b/src/modules/tccpp/components/april1/buzzwords.ts index 340f8225..c4fd8fa1 100644 --- a/src/modules/tccpp/components/april1/buzzwords.ts +++ b/src/modules/tccpp/components/april1/buzzwords.ts @@ -325,9 +325,7 @@ export default class Buzzwords extends BotComponent { return; } //if(message.channel.id != "1091502908241084436") return; // for now, for testing - if ( - await this.wheatley.fetch_member_if_permitted(message.author, Discord.PermissionFlagsBits.ModerateMembers) - ) { + if (await this.wheatley.check_permissions(message.author, Discord.PermissionFlagsBits.ModerateMembers)) { if (message.content.trim().startsWith("!derailed")) { const ids = message.content.match(/\d{10,}/g); if (!ids || ids.length != 1) { diff --git a/src/modules/tccpp/components/forum-control.ts b/src/modules/tccpp/components/forum-control.ts index 6898b239..b233b782 100644 --- a/src/modules/tccpp/components/forum-control.ts +++ b/src/modules/tccpp/components/forum-control.ts @@ -58,7 +58,7 @@ export default class ForumControl extends BotComponent { const owner_id = await this.get_owner(thread); if ( owner_id == request.user.id || - (await this.wheatley.fetch_member_if_permitted(request.user, Discord.PermissionFlagsBits.ManageThreads)) + (await this.wheatley.check_permissions(request.user, Discord.PermissionFlagsBits.ManageThreads)) ) { return true; } else { diff --git a/src/modules/tccpp/components/roulette.ts b/src/modules/tccpp/components/roulette.ts index d1ae6ae5..8453284b 100644 --- a/src/modules/tccpp/components/roulette.ts +++ b/src/modules/tccpp/components/roulette.ts @@ -111,10 +111,7 @@ export default class Roulette extends BotComponent { await this.update_score(command.user.id); // TODO: I forget why this is here try { if ( - await this.wheatley.fetch_member_if_permitted( - command.user, - Discord.PermissionFlagsBits.ModerateMembers, - ) + await this.wheatley.check_permissions(command.user, Discord.PermissionFlagsBits.ModerateMembers) ) { this.disabled_users.insert(command.user.id); } else { diff --git a/src/modules/tccpp/components/server-suggestion-reactions.ts b/src/modules/tccpp/components/server-suggestion-reactions.ts index d8a6fc61..5506eca2 100644 --- a/src/modules/tccpp/components/server-suggestion-reactions.ts +++ b/src/modules/tccpp/components/server-suggestion-reactions.ts @@ -41,12 +41,7 @@ export default class ServerSuggestionReactions extends BotComponent { }); await reaction.users.remove(id); } else if (root_only_reacts.has(reaction.emoji.name!)) { - if ( - !(await this.wheatley.fetch_member_if_permitted( - user.id, - Discord.PermissionFlagsBits.Administrator, - )) - ) { + if (!(await this.wheatley.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator))) { M.log("removing non-root reaction", { content: reaction.message.content, reaction: reaction.emoji.name, @@ -141,9 +136,7 @@ export default class ServerSuggestionReactions extends BotComponent { reaction.users.remove(user.id).catch(this.wheatley.critical_error.bind(this.wheatley)); }, 5 * MINUTE); } else if (root_only_reacts.has(reaction.emoji.name!)) { - if ( - !(await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) - ) { + if (!(await this.wheatley.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator))) { M.log("removing non-root reaction", { content: reaction.message.content, reaction: reaction.emoji.name, diff --git a/src/modules/tccpp/components/server-suggestion-tracker.ts b/src/modules/tccpp/components/server-suggestion-tracker.ts index 785412ff..6b4c04f5 100644 --- a/src/modules/tccpp/components/server-suggestion-tracker.ts +++ b/src/modules/tccpp/components/server-suggestion-tracker.ts @@ -97,9 +97,7 @@ export default class ServerSuggestionTracker extends BotComponent { if (resolution_reactions_set.has(reaction.emoji.name!)) { const users = await reaction.users.fetch(); for (const [_, user] of users) { - if ( - await this.wheatley.fetch_member_if_permitted(user, Discord.PermissionFlagsBits.Administrator) - ) { + if (await this.wheatley.check_permissions(user, Discord.PermissionFlagsBits.Administrator)) { roots.push({ user, emoji: reaction.emoji }); } } @@ -476,7 +474,7 @@ export default class ServerSuggestionTracker extends BotComponent { ) { const reaction = await departialize(_reaction); if (resolution_reactions_set.has(reaction.emoji.name!)) { - if (await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) { + if (await this.wheatley.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator)) { await this.resolve_suggestion(await departialize(reaction.message), { user: await departialize(user), emoji: reaction.emoji, @@ -491,7 +489,7 @@ export default class ServerSuggestionTracker extends BotComponent { ) { if ( resolution_reactions_set.has(reaction.emoji.name!) && - (await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) + (await this.wheatley.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator)) ) { const message = await departialize(reaction.message); if (!(await this.message_has_resolution_from_root(message))) { @@ -532,7 +530,7 @@ export default class ServerSuggestionTracker extends BotComponent { message.author.id == this.wheatley.user.id && user.id != this.wheatley.user.id && // ignore self - this is important for autoreacts resolution_reactions_set.has(reaction.emoji.name!) && - (await this.wheatley.fetch_member_if_permitted(user.id, Discord.PermissionFlagsBits.Administrator)) + (await this.wheatley.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator)) ) { // expensive-ish but this will be rare const suggestion_id = await this.reverse_lookup(message.id); @@ -570,13 +568,9 @@ export default class ServerSuggestionTracker extends BotComponent { } catch (e) { this.wheatley.critical_error(e); try { - const member = await this.wheatley.fetch_member_if_permitted( - user.id, - Discord.PermissionFlagsBits.Administrator, - ); - if (member) { + if (await this.wheatley.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator)) { // only send diagnostics to root - await member.send("Error while resolving suggestion"); + await user.send("Error while resolving suggestion"); } } catch (e) { this.wheatley.critical_error(e); diff --git a/src/modules/tccpp/components/starboard.ts b/src/modules/tccpp/components/starboard.ts index 69682d81..a7ef5b51 100644 --- a/src/modules/tccpp/components/starboard.ts +++ b/src/modules/tccpp/components/starboard.ts @@ -296,10 +296,7 @@ export default class Starboard extends BotComponent { do_delete = false; } if ( - (await this.wheatley.fetch_member_if_permitted( - message.author, - Discord.PermissionFlagsBits.Administrator, - )) || + (await this.wheatley.check_permissions(message.author, Discord.PermissionFlagsBits.Administrator)) || message.author.bot ) { do_delete = false; diff --git a/src/modules/tccpp/components/utility-tools.ts b/src/modules/tccpp/components/utility-tools.ts index 3338f4b2..f69925f8 100644 --- a/src/modules/tccpp/components/utility-tools.ts +++ b/src/modules/tccpp/components/utility-tools.ts @@ -32,7 +32,7 @@ export default class UtilityTools extends BotComponent { if (message.author.bot) { return; } - if (await this.wheatley.fetch_member_if_permitted(message.author, Discord.PermissionFlagsBits.ManageChannels)) { + if (await this.wheatley.check_permissions(message.author, Discord.PermissionFlagsBits.ManageChannels)) { if (message.content == "!channel-rename") { M.log("got !channel-rename"); assert(!(message.channel instanceof Discord.PartialGroupDMChannel)); diff --git a/src/wheatley.ts b/src/wheatley.ts index 9a176c14..9d86def4 100644 --- a/src/wheatley.ts +++ b/src/wheatley.ts @@ -623,12 +623,12 @@ export class Wheatley { return reply_message; } - async fetch_member_if_permitted( + async check_permissions( options: Discord.GuildMember | Discord.User | Discord.UserResolvable | Discord.FetchMemberOptions, permissions: Discord.PermissionResolvable, ) { const member = await this.try_fetch_guild_member(options); - return member?.permissions.has(permissions) ? member : null; + return member?.permissions.has(permissions) ? true : false; } staff_contacts() { From 14e3849c43fc9a8c9d125c0e0f184f941c49e2b9 Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sun, 28 Sep 2025 01:02:08 +0200 Subject: [PATCH 6/9] make check more idiomatic Co-authored-by: Jeremy Rifkin <51220084+jeremy-rifkin@users.noreply.github.com> --- src/wheatley.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wheatley.ts b/src/wheatley.ts index 9d86def4..a7322a26 100644 --- a/src/wheatley.ts +++ b/src/wheatley.ts @@ -628,7 +628,7 @@ export class Wheatley { permissions: Discord.PermissionResolvable, ) { const member = await this.try_fetch_guild_member(options); - return member?.permissions.has(permissions) ? true : false; + return !!member?.permissions.has(permissions); } staff_contacts() { From ba56a924c28a24399cd9d32ce2d9bf842789ab83 Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sun, 28 Sep 2025 01:03:49 +0200 Subject: [PATCH 7/9] loosen anti-invite-link permissions --- src/components/anti-invite-links.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/anti-invite-links.ts b/src/components/anti-invite-links.ts index b1cbf285..dbb1e23f 100644 --- a/src/components/anti-invite-links.ts +++ b/src/components/anti-invite-links.ts @@ -50,7 +50,7 @@ export default class AntiInviteLinks extends BotComponent { } async handle_message(message: Discord.Message) { - if (await this.wheatley.check_permissions(message.author, Discord.PermissionFlagsBits.Administrator)) { + if (await this.wheatley.check_permissions(message.author, Discord.PermissionFlagsBits.ModerateMembers)) { return; } const match = match_invite(message.content); From 3615ca694c3c5ed1b837b8ffe201e7b25cb27ce9 Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sun, 28 Sep 2025 01:21:37 +0200 Subject: [PATCH 8/9] revert moderation_multi_issue_handler --- .../moderation/moderation-common.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/components/moderation/moderation-common.ts b/src/components/moderation/moderation-common.ts index 40b448b2..a030bc1b 100644 --- a/src/components/moderation/moderation-common.ts +++ b/src/components/moderation/moderation-common.ts @@ -651,8 +651,35 @@ export abstract class ModerationComponent extends BotComponent { basic_moderation_info: basic_moderation, ) { try { + const issuer = unwrap(await this.wheatley.try_fetch_guild_member(command.user)); for (const user of users) { - await this.moderation_issue_handler(command, user, duration_string, reason, basic_moderation_info); + const target = await this.wheatley.try_fetch_guild_member(user); + if (target && target.roles.highest.position >= issuer.roles.highest.position) { + await this.reply_with_error(command, unwrap(get_random_array_element(joke_responses_other))); + return; + } + const base_moderation: basic_moderation_with_user = { ...basic_moderation_info, user: user.id }; + if (!this.is_once_off && (await this.is_moderation_applied(base_moderation))) { + await this.reply_with_error(command, `${user.displayName} is already ${this.past_participle}`); + continue; + } + const moderation: moderation_entry = { + ...basic_moderation_info, + case_number: -1, + user: user.id, + user_name: user.displayName, + moderator: command.user.id, + moderator_name: (await command.get_member()).displayName, + reason, + issued_at: Date.now(), + duration: parse_nullable_duration(duration_string), + active: !this.is_once_off, + removed: null, + expunged: null, + link: command.get_or_forge_url(), + }; + await this.notify_user(user, this.past_participle, moderation); + await this.issue_moderation(moderation); } await command.replyOrFollowUp({ embeds: [ From e06cbeef53fcd8242699f079979f35aa9a5df5b8 Mon Sep 17 00:00:00 2001 From: Michael Kenzel Date: Sun, 28 Sep 2025 01:29:29 +0200 Subject: [PATCH 9/9] fix moderation_multi_issue_handler --- src/components/moderation/moderation-common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/moderation/moderation-common.ts b/src/components/moderation/moderation-common.ts index a030bc1b..ed878995 100644 --- a/src/components/moderation/moderation-common.ts +++ b/src/components/moderation/moderation-common.ts @@ -656,7 +656,7 @@ export abstract class ModerationComponent extends BotComponent { const target = await this.wheatley.try_fetch_guild_member(user); if (target && target.roles.highest.position >= issuer.roles.highest.position) { await this.reply_with_error(command, unwrap(get_random_array_element(joke_responses_other))); - return; + continue; } const base_moderation: basic_moderation_with_user = { ...basic_moderation_info, user: user.id }; if (!this.is_once_off && (await this.is_moderation_applied(base_moderation))) {