diff --git a/src/command-handler.ts b/src/command-handler.ts index 37dc5e9e..66a3ff67 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 this.wheatley.try_fetch_guild_member(await command_obj.get_member()); - if (!member || !member.permissions.has(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, @@ -209,8 +208,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.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 6733ed1c..8ccd148a 100644 --- a/src/components/anti-everyone.ts +++ b/src/components/anti-everyone.ts @@ -25,7 +25,7 @@ export default class AntiEveryone extends BotComponent { // bot message.author.bot || // mod - this.wheatley.is_authorized_mod(message.author) || + (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 704ad931..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 (this.wheatley.is_authorized_mod(message.author)) { + if (await this.wheatley.check_permissions(message.author, Discord.PermissionFlagsBits.ModerateMembers)) { return; } const match = match_invite(message.content); diff --git a/src/components/help.ts b/src/components/help.ts index 0061fd7a..97cd20b3 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.check_permissions(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..1cdb63ae 100644 --- a/src/components/moderation/massban.ts +++ b/src/components/moderation/massban.ts @@ -26,7 +26,7 @@ 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.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/moderation-common.ts b/src/components/moderation/moderation-common.ts index efcd91eb..ed878995 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 }; @@ -646,9 +651,11 @@ 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) { - if (this.wheatley.is_authorized_mod(user)) { - await this.reply_with_error(command, moderation_on_team_member_message); + 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))); continue; } const base_moderation: basic_moderation_with_user = { ...basic_moderation_info, user: user.id }; diff --git a/src/components/moderation/modlogs.ts b/src/components/moderation/modlogs.ts index e18ea704..1693195f 100644 --- a/src/components/moderation/modlogs.ts +++ b/src/components/moderation/modlogs.ts @@ -216,7 +216,9 @@ 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.check_permissions(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..72c0c552 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.check_permissions( + 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..4c75d643 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.check_permissions(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..c4fd8fa1 100644 --- a/src/modules/tccpp/components/april1/buzzwords.ts +++ b/src/modules/tccpp/components/april1/buzzwords.ts @@ -325,7 +325,7 @@ 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.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 5febec9b..b233b782 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.check_permissions(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..8453284b 100644 --- a/src/modules/tccpp/components/roulette.ts +++ b/src/modules/tccpp/components/roulette.ts @@ -110,7 +110,9 @@ 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.check_permissions(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..5506eca2 100644 --- a/src/modules/tccpp/components/server-suggestion-reactions.ts +++ b/src/modules/tccpp/components/server-suggestion-reactions.ts @@ -41,7 +41,7 @@ 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.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator))) { M.log("removing non-root reaction", { content: reaction.message.content, reaction: reaction.emoji.name, @@ -136,7 +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 (!this.wheatley.is_root(user)) { + 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 6e76722d..6b4c04f5 100644 --- a/src/modules/tccpp/components/server-suggestion-tracker.ts +++ b/src/modules/tccpp/components/server-suggestion-tracker.ts @@ -97,7 +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 (this.wheatley.is_root(user)) { + if (await this.wheatley.check_permissions(user, Discord.PermissionFlagsBits.Administrator)) { roots.push({ user, emoji: reaction.emoji }); } } @@ -474,7 +474,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.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator)) { await this.resolve_suggestion(await departialize(reaction.message), { user: await departialize(user), emoji: reaction.emoji, @@ -487,7 +487,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.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator)) + ) { const message = await departialize(reaction.message); if (!(await this.message_has_resolution_from_root(message))) { // reopen @@ -527,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!) && - this.wheatley.is_root(user) + (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); @@ -565,10 +568,9 @@ export default class ServerSuggestionTracker extends BotComponent { } catch (e) { this.wheatley.critical_error(e); try { - if (this.wheatley.is_root(user)) { + if (await this.wheatley.check_permissions(user.id, Discord.PermissionFlagsBits.Administrator)) { // only send diagnostics to root - const member = await this.wheatley.guild.members.fetch(user.id); - 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 a750a10f..a7ef5b51 100644 --- a/src/modules/tccpp/components/starboard.ts +++ b/src/modules/tccpp/components/starboard.ts @@ -295,7 +295,10 @@ 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.check_permissions(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..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 (this.wheatley.is_authorized_mod(message.author)) { + 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 b52ced22..a7322a26 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 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); } - 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) {