From 2f88a13ba9a628234fd2152ac4be35ebcfbb5ee7 Mon Sep 17 00:00:00 2001 From: TrisTOON <36267812+TrisTOON@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:55:56 +0100 Subject: [PATCH 1/2] Reorders structure type checking and always returns null for error cases --- src/commands/apply.ts | 8 +++--- src/commands/approve.ts | 29 +++++++++---------- src/commands/chat.ts | 20 ++++++++++--- src/commands/gate.ts | 20 ++++++++++--- src/commands/help.ts | 52 ++++++++++++++++++--------------- src/commands/refuse.ts | 29 +++++++++---------- src/commands/roadmap.ts | 18 ++++++------ src/commands/tracker.ts | 18 ++++++------ src/commands/verify.ts | 8 +++--- src/hooks/application.ts | 8 +++--- src/hooks/approval.ts | 8 +++--- src/hooks/record.ts | 4 +-- src/hooks/refusal.ts | 8 +++--- src/hooks/verification.ts | 8 +++--- src/rules/rule7.ts | 46 +++++++++++++++--------------- src/shicka.ts | 60 +++++++++++++++------------------------ 16 files changed, 179 insertions(+), 165 deletions(-) diff --git a/src/commands/apply.ts b/src/commands/apply.ts index 285d88f..f7ffa50 100644 --- a/src/commands/apply.ts +++ b/src/commands/apply.ts @@ -45,15 +45,15 @@ const applyCommand: Command = { const {member, guild, locale}: ChatInputCommandInteraction<"cached"> = interaction; const resolvedLocale: Locale = resolve(locale); const {roles}: Guild = guild; - const applyingRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === commandApplyingRole; - }); + }) ?? null; if (applyingRole == null) { return; } - const verifiedRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const verifiedRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === commandVerifiedRole; - }); + }) ?? null; if (verifiedRole == null) { return; } diff --git a/src/commands/approve.ts b/src/commands/approve.ts index 2d63a84..6dd9d92 100644 --- a/src/commands/approve.ts +++ b/src/commands/approve.ts @@ -52,31 +52,30 @@ const approveCommand: Command = { const {guild, locale, targetMessage}: MessageContextMenuCommandInteraction<"cached"> = interaction; const resolvedLocale: Locale = resolve(locale); const {roles}: Guild = guild; - const applyingRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === commandApplyingRole; - }); + }) ?? null; if (applyingRole == null) { return; } - const verifiedRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const verifiedRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === commandVerifiedRole; - }); + }) ?? null; if (verifiedRole == null) { return; } - const member: GuildMember | undefined = await (async (): Promise => { + const member: GuildMember | null = await (async (): Promise => { try { - const {author, member}: Message = targetMessage; + const {author}: Message = targetMessage; + const memberId: string = author.id; + const member: GuildMember | null = guild.members.cache.get(memberId) ?? null; if (member == null) { - const memberId: string = author.id; - const member: GuildMember | undefined = guild.members.cache.get(memberId); - if (member == null) { - return await guild.members.fetch(memberId); - } - return member; + return await guild.members.fetch(memberId); } return member; - } catch {} + } catch { + return null; + } })(); if (member == null) { await interaction.reply({ @@ -96,9 +95,9 @@ const approveCommand: Command = { return; } await targetMessage.react("✅"); - const reaction: MessageReaction | undefined = targetMessage.reactions.cache.find((reaction: MessageReaction): boolean => { + const reaction: MessageReaction | null = targetMessage.reactions.cache.find((reaction: MessageReaction): boolean => { return (reaction.emoji.name ?? "") === "❎"; - }); + }) ?? null; if (reaction != null) { await reaction.users.remove(); } diff --git a/src/commands/chat.ts b/src/commands/chat.ts index fb07ceb..baef2e5 100644 --- a/src/commands/chat.ts +++ b/src/commands/chat.ts @@ -322,17 +322,29 @@ const chatCommand: Command = { }); return; } - const message: Message | undefined = await (async (): Promise | undefined> => { + const message: Message | null = await (async (): Promise | null> => { try { if (channel.isThreadOnly()) { - const thread: ThreadChannel | undefined = channel.threads.cache.get(identifier); + const thread: ThreadChannel | null = await (async (): Promise | null> => { + try { + const thread: ThreadChannel | null = channel.threads.cache.get(identifier) ?? null; + if (thread == null) { + return await channel.threads.fetch(identifier); + } + return thread; + } catch { + return null; + } + })(); if (thread == null) { - return; + return null; } return await thread.messages.fetch(identifier); } return await channel.messages.fetch(identifier); - } catch {} + } catch { + return null; + } })(); if (message == null) { await interaction.reply({ diff --git a/src/commands/gate.ts b/src/commands/gate.ts index 3332ec3..a86cbee 100644 --- a/src/commands/gate.ts +++ b/src/commands/gate.ts @@ -157,17 +157,29 @@ const gateCommand: Command = { }); return; } - const message: Message | undefined = await (async (): Promise | undefined> => { + const message: Message | null = await (async (): Promise | null> => { try { if (channel.isThreadOnly()) { - const thread: ThreadChannel | undefined = channel.threads.cache.get(identifier); + const thread: ThreadChannel | null = await (async (): Promise | null> => { + try { + const thread: ThreadChannel | null = channel.threads.cache.get(identifier) ?? null; + if (thread == null) { + return await channel.threads.fetch(identifier); + } + return thread; + } catch { + return null; + } + })(); if (thread == null) { - return; + return null; } return await thread.messages.fetch(identifier); } return await channel.messages.fetch(identifier); - } catch {} + } catch { + return null; + } })(); if (message == null) { await interaction.reply({ diff --git a/src/commands/help.ts b/src/commands/help.ts index 7d5b4aa..af77180 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -82,19 +82,19 @@ function hasAdministratorPermission(channel: GuildBasedChannel, member: GuildMem } function hasChannelPermission(permissions: Collection, applicationOrCommand: ClientApplication | ApplicationCommand, channel: GuildBasedChannel): boolean | null { const allChannelsId: string = `${BigInt(channel.guild.id) - 1n}`; - const applicationOrCommandPermissions: ApplicationCommandPermissions[] | undefined = permissions.get(applicationOrCommand.id); + const applicationOrCommandPermissions: ApplicationCommandPermissions[] | null = permissions.get(applicationOrCommand.id) ?? null; if (applicationOrCommandPermissions == null) { return null; } - const channelPermission: ApplicationCommandPermissions | undefined = applicationOrCommandPermissions.find((permission: ApplicationCommandPermissions): boolean => { + const channelPermission: ApplicationCommandPermissions | null = applicationOrCommandPermissions.find((permission: ApplicationCommandPermissions): boolean => { return permission.type === ApplicationCommandPermissionType.Channel && permission.id === channel.id; - }); + }) ?? null; if (channelPermission != null) { return channelPermission.permission; } - const allChannelsPermission: ApplicationCommandPermissions | undefined = applicationOrCommandPermissions.find((permission: ApplicationCommandPermissions): boolean => { + const allChannelsPermission: ApplicationCommandPermissions | null = applicationOrCommandPermissions.find((permission: ApplicationCommandPermissions): boolean => { return permission.type === ApplicationCommandPermissionType.Channel && permission.id === allChannelsId; - }); + }) ?? null; if (allChannelsPermission != null) { return allChannelsPermission.permission; } @@ -102,13 +102,13 @@ function hasChannelPermission(permissions: Collection, applicationOrCommand: ClientApplication | ApplicationCommand, member: GuildMember): boolean | null { const allUsersId: string = member.guild.id; - const applicationOrCommandPermissions: ApplicationCommandPermissions[] | undefined = permissions.get(applicationOrCommand.id); + const applicationOrCommandPermissions: ApplicationCommandPermissions[] | null = permissions.get(applicationOrCommand.id) ?? null; if (applicationOrCommandPermissions == null) { return null; } - const userPermission: ApplicationCommandPermissions | undefined = applicationOrCommandPermissions.find((permission: ApplicationCommandPermissions): boolean => { + const userPermission: ApplicationCommandPermissions | null = applicationOrCommandPermissions.find((permission: ApplicationCommandPermissions): boolean => { return permission.type === ApplicationCommandPermissionType.User && permission.id === member.id; - }); + }) ?? null; if (userPermission != null) { return userPermission.permission; } @@ -122,9 +122,9 @@ function hasMemberPermission(permissions: Collection { + const allUsersPermission: ApplicationCommandPermissions | null = applicationOrCommandPermissions.find((permission: ApplicationCommandPermissions): boolean => { return permission.type === ApplicationCommandPermissionType.User && permission.id === allUsersId; - }); + }) ?? null; if (allUsersPermission != null) { return allUsersPermission.permission; } @@ -205,26 +205,32 @@ const helpCommand: Command = { return; } const applicationCommands: Collection = guild.commands.cache; - const permissions: Collection | undefined = await (async (): Promise | undefined> => { + const permissions: Collection | null = await (async (): Promise | null> => { try { return await guild.commands.permissions.fetch({}); - } catch {} + } catch { + return null; + } })(); if (permissions == null) { return; } - const webhooks: Collection | undefined = await (async (): Promise | undefined> => { + const webhooks: Collection | null = await (async (): Promise | null> => { try { return await guild.fetchWebhooks(); - } catch {} + } catch { + return null; + } })(); if (webhooks == null) { return; } - const autoModerationRules: Collection | undefined = await (async (): Promise | undefined> => { + const autoModerationRules: Collection | null = await (async (): Promise | null> => { try { return await guild.autoModerationRules.fetch(); - } catch {} + } catch { + return null; + } })(); if (autoModerationRules == null) { return; @@ -233,9 +239,9 @@ const helpCommand: Command = { const descriptions: Localized<(groups: {}) => string>[] = [ Object.keys(commands).map string> | null>((commandName: string): Localized<(groups: {}) => string> | null => { const command: Command = commands[commandName as keyof typeof commands]; - const applicationCommand: ApplicationCommand | undefined = applicationCommands.find((applicationCommand: ApplicationCommand): boolean => { + const applicationCommand: ApplicationCommand | null = applicationCommands.find((applicationCommand: ApplicationCommand): boolean => { return applicationCommand.name === commandName; - }); + }) ?? null; if (applicationCommand == null) { return null; } @@ -256,9 +262,9 @@ const helpCommand: Command = { }), Object.keys(hooks).map string> | null>((hookName: string): Localized<(groups: {}) => string> | null => { const hook: Hook = hooks[hookName as keyof typeof hooks]; - const webhook: Webhook | undefined = webhooks.find((webhook: Webhook): boolean => { + const webhook: Webhook | null = webhooks.find((webhook: Webhook): boolean => { return webhook.name === hookName; - }); + }) ?? null; if (webhook == null) { return null; } @@ -277,9 +283,9 @@ const helpCommand: Command = { }), Object.keys(rules).map string> | null>((ruleName: string): Localized<(groups: {}) => string> | null => { const rule: Rule = rules[ruleName as keyof typeof rules]; - const autoModerationRule: AutoModerationRule | undefined = autoModerationRules.find((autoModerationRule: AutoModerationRule): boolean => { + const autoModerationRule: AutoModerationRule | null = autoModerationRules.find((autoModerationRule: AutoModerationRule): boolean => { return autoModerationRule.name === ruleName; - }); + }) ?? null; if (autoModerationRule == null) { return null; } @@ -295,7 +301,7 @@ const helpCommand: Command = { if (channelId == null) { return null; } - const channel: GuildBasedChannel | undefined = autoModerationRule.guild.channels.cache.get(channelId); + const channel: GuildBasedChannel | null = autoModerationRule.guild.channels.cache.get(channelId) ?? null; if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { return null; } diff --git a/src/commands/refuse.ts b/src/commands/refuse.ts index fe2b5ec..95a175f 100644 --- a/src/commands/refuse.ts +++ b/src/commands/refuse.ts @@ -52,31 +52,30 @@ const refuseCommand: Command = { const {guild, locale, targetMessage}: MessageContextMenuCommandInteraction<"cached"> = interaction; const resolvedLocale: Locale = resolve(locale); const {roles}: Guild = guild; - const applyingRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === commandApplyingRole; - }); + }) ?? null; if (applyingRole == null) { return; } - const verifiedRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const verifiedRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === commandVerifiedRole; - }); + }) ?? null; if (verifiedRole == null) { return; } - const member: GuildMember | undefined = await (async (): Promise => { + const member: GuildMember | null = await (async (): Promise => { try { - const {author, member}: Message = targetMessage; + const {author}: Message = targetMessage; + const memberId: string = author.id; + const member: GuildMember | null = guild.members.cache.get(memberId) ?? null; if (member == null) { - const memberId: string = author.id; - const member: GuildMember | undefined = guild.members.cache.get(memberId); - if (member == null) { - return await guild.members.fetch(memberId); - } - return member; + return await guild.members.fetch(memberId); } return member; - } catch {} + } catch { + return null; + } })(); if (member == null) { await interaction.reply({ @@ -96,9 +95,9 @@ const refuseCommand: Command = { return; } await targetMessage.react("❎"); - const reaction: MessageReaction | undefined = targetMessage.reactions.cache.find((reaction: MessageReaction): boolean => { + const reaction: MessageReaction | null = targetMessage.reactions.cache.find((reaction: MessageReaction): boolean => { return (reaction.emoji.name ?? "") === "✅"; - }); + }) ?? null; if (reaction != null) { await reaction.users.remove(); } diff --git a/src/commands/roadmap.ts b/src/commands/roadmap.ts index 8de196a..bace214 100644 --- a/src/commands/roadmap.ts +++ b/src/commands/roadmap.ts @@ -1,6 +1,12 @@ import type { ChatInputCommandInteraction, + ForumChannel, GuildBasedChannel, + MediaChannel, + NewsChannel, + StageChannel, + TextChannel, + VoiceChannel, } from "discord.js"; import type Command from "../commands.js"; import type {ApplicationCommand, ApplicationCommandData, ApplicationUserInteraction} from "../commands.js"; @@ -44,15 +50,9 @@ const roadmapCommand: Command = { } const {guild, locale}: ChatInputCommandInteraction<"cached"> = interaction; const resolvedLocale: Locale = resolve(locale); - const channel: GuildBasedChannel | null = ((): GuildBasedChannel | null => { - const channel: GuildBasedChannel | undefined = guild.channels.cache.find((channel: GuildBasedChannel): boolean => { - return channel.name === commandIntentChannel; - }); - if (channel == null || channel.type === ChannelType.GuildCategory || channel.isThread()) { - return null; - } - return channel; - })(); + const channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null = guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { + return channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === commandIntentChannel; + }) ?? null; function formatMessage(locale: Locale): string { return replyLocalizations[locale]({ intent: (): string => { diff --git a/src/commands/tracker.ts b/src/commands/tracker.ts index 0e48678..553d488 100644 --- a/src/commands/tracker.ts +++ b/src/commands/tracker.ts @@ -1,6 +1,12 @@ import type { ChatInputCommandInteraction, + ForumChannel, GuildBasedChannel, + MediaChannel, + NewsChannel, + StageChannel, + TextChannel, + VoiceChannel, } from "discord.js"; import type Command from "../commands.js"; import type {ApplicationCommand, ApplicationCommandData, ApplicationUserInteraction} from "../commands.js"; @@ -68,15 +74,9 @@ const trackerCommand: Command = { } const {guild, locale}: ChatInputCommandInteraction<"cached"> = interaction; const resolvedLocale: Locale = resolve(locale); - const channel: GuildBasedChannel | null = ((): GuildBasedChannel | null => { - const channel: GuildBasedChannel | undefined = guild.channels.cache.find((channel: GuildBasedChannel): boolean => { - return channel.name === commandIntentChannel; - }); - if (channel == null || channel.type === ChannelType.GuildCategory || channel.isThread()) { - return null; - } - return channel; - })(); + const channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null = guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { + return channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === commandIntentChannel; + }) ?? null; const links: Localized<(groups: {}) => string>[] = []; for (const item of data) { const link: Localized<(groups: {}) => string> = composeAll(linkLocalizations, localize((locale: Locale): LinkGroups => { diff --git a/src/commands/verify.ts b/src/commands/verify.ts index 85042ff..dce7a5e 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -48,12 +48,12 @@ const verifyCommand: Command = { const {member, guild, locale}: ChatInputCommandInteraction<"cached"> = interaction; const resolvedLocale: Locale = resolve(locale); const {name, roles}: Guild = guild; - const applyingRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === commandApplyingRole; - }); - const verifiedRole: Role | undefined = roles.cache.find((role: Role): boolean => { + }) ?? null; + const verifiedRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === commandVerifiedRole; - }); + }) ?? null; if (verifiedRole == null) { return; } diff --git a/src/hooks/application.ts b/src/hooks/application.ts index e5e8c19..8a1cfc3 100644 --- a/src/hooks/application.ts +++ b/src/hooks/application.ts @@ -66,15 +66,15 @@ const applicationHook: Hook = { const [oldMember, newMember]: ClientEvents["guildMemberUpdate"] = (invocation.event as WebjobEvent<"guildMemberUpdate">).data; const {guild}: GuildMember = newMember; const {roles}: Guild = guild; - const applyingRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === hookApplyingRole; - }); + }) ?? null; if (applyingRole == null) { return; } - const verifiedRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const verifiedRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === hookVerifiedRole; - }); + }) ?? null; if (verifiedRole == null) { return; } diff --git a/src/hooks/approval.ts b/src/hooks/approval.ts index f659ebe..88d26fa 100644 --- a/src/hooks/approval.ts +++ b/src/hooks/approval.ts @@ -70,15 +70,15 @@ const approvalHook: Hook = { const [oldMember, newMember]: ClientEvents["guildMemberUpdate"] = (invocation.event as WebjobEvent<"guildMemberUpdate">).data; const {guild}: GuildMember = newMember; const {name, roles}: Guild = guild; - const applyingRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === hookApplyingRole; - }); + }) ?? null; if (applyingRole == null) { return; } - const verifiedRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const verifiedRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === hookVerifiedRole; - }); + }) ?? null; if (verifiedRole == null) { return; } diff --git a/src/hooks/record.ts b/src/hooks/record.ts index 036839c..120c22a 100644 --- a/src/hooks/record.ts +++ b/src/hooks/record.ts @@ -135,7 +135,7 @@ async function fetchData(start: number, end: number): Promise { continue; } const playerFlag: string | null = location?.country?.code?.slice?.(0, 2).split("").map((string: string): string => { - const character: number | undefined = string.codePointAt(0); + const character: number | null = string.codePointAt(0) ?? null; if (character == null) { return ""; } @@ -154,7 +154,7 @@ async function fetchData(start: number, end: number): Promise { return `: **${escapeMarkdown(time)}**`; })() : ""; const category: string = `${levelName}${categoryName}${leaderboardName}`; - const page: string | null = run?.weblink ?? null + const page: string | null = run?.weblink ?? null; const pageLink: string = page != null ?`[a new world record](<${page}>)` : "a new world record"; const videos: string[] = []; for (const {uri} of run?.videos?.links ?? []) { diff --git a/src/hooks/refusal.ts b/src/hooks/refusal.ts index f405421..ce3607a 100644 --- a/src/hooks/refusal.ts +++ b/src/hooks/refusal.ts @@ -70,15 +70,15 @@ const refusalHook: Hook = { const [oldMember, newMember]: ClientEvents["guildMemberUpdate"] = (invocation.event as WebjobEvent<"guildMemberUpdate">).data; const {guild}: GuildMember = newMember; const {name, roles}: Guild = guild; - const applyingRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === hookApplyingRole; - }); + }) ?? null; if (applyingRole == null) { return; } - const verifiedRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const verifiedRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === hookVerifiedRole; - }); + }) ?? null; if (verifiedRole == null) { return; } diff --git a/src/hooks/verification.ts b/src/hooks/verification.ts index fd6b8d6..8bac745 100644 --- a/src/hooks/verification.ts +++ b/src/hooks/verification.ts @@ -66,12 +66,12 @@ const verificationHook: Hook = { const [oldMember, newMember]: ClientEvents["guildMemberUpdate"] = (invocation.event as WebjobEvent<"guildMemberUpdate">).data; const {guild}: GuildMember = newMember; const {roles}: Guild = guild; - const applyingRole: Role | undefined = roles.cache.find((role: Role): boolean => { + const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === hookApplyingRole; - }); - const verifiedRole: Role | undefined = roles.cache.find((role: Role): boolean => { + }) ?? null; + const verifiedRole: Role | null = roles.cache.find((role: Role): boolean => { return role.name === hookVerifiedRole; - }); + }) ?? null; if (verifiedRole == null) { return; } diff --git a/src/rules/rule7.ts b/src/rules/rule7.ts index fd3f8ea..d7b68fc 100644 --- a/src/rules/rule7.ts +++ b/src/rules/rule7.ts @@ -81,46 +81,46 @@ const rule7Rule: Rule = { if (channel.isThread() && channel.id === messageId) { return; } - const message: Message | undefined = await (async (): Promise | undefined> => { + const message: Message | null = await (async (): Promise | null> => { try { if (channel.isThreadOnly()) { - const thread: ThreadChannel | undefined = channel.threads.cache.get(messageId); + const thread: ThreadChannel | null = await (async (): Promise | null> => { + try { + const thread: ThreadChannel | null = channel.threads.cache.get(messageId) ?? null; + if (thread == null) { + return await channel.threads.fetch(messageId); + } + return thread; + } catch { + return null; + } + })(); if (thread == null) { - return; + return null; } return await thread.messages.fetch(messageId); } return await channel.messages.fetch(messageId); - } catch {} + } catch { + return null; + } })(); if (message == null) { return; } const {guild}: AutoModerationActionExecution = execution; - const emoji: GuildEmoji | null = ((): GuildEmoji | null => { - const emoji: GuildEmoji | undefined = guild.emojis.cache.find((emoji: GuildEmoji): boolean => { - return (emoji.name ?? "") === ruleReactionEmoji; - }); - if (emoji == null || (emoji.animated ?? true)) { - return null; - } - return emoji; - })(); + const emoji: GuildEmoji | null = guild.emojis.cache.find((emoji: GuildEmoji): boolean => { + return !(emoji.animated ?? true) && (emoji.name ?? "") === ruleReactionEmoji; + }) ?? null; if (emoji != null) { await message.reply({ content: `<:${ruleReactionEmoji}:${emoji.id}>`, }); } const {rulesChannel}: Guild = guild; - const manualChannel: TextChannel | null = ruleRulesChannel != null ? ((): TextChannel | null => { - const channel: GuildBasedChannel | undefined = guild.channels.cache.find((channel: GuildBasedChannel): boolean => { - return channel.name === ruleRulesChannel; - }); - if (channel == null || channel.type !== ChannelType.GuildText) { - return null; - } - return channel; - })() : rulesChannel; + const manualChannel: TextChannel | null = ruleRulesChannel != null ? guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel => { + return channel.type !== ChannelType.GuildText && channel.name === ruleRulesChannel; + }) ?? null : rulesChannel; if (manualChannel != null) { await message.reply({ content: `Please read and respect the rules in <#${manualChannel.id}>!`, @@ -142,7 +142,7 @@ const rule7Rule: Rule = { if (channelId == null) { return null; } - const channel: GuildBasedChannel | undefined = autoModerationRule.guild.channels.cache.get(channelId); + const channel: GuildBasedChannel | null = autoModerationRule.guild.channels.cache.get(channelId) ?? null; if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { return null; } diff --git a/src/shicka.ts b/src/shicka.ts index 894a8c1..d249ff5 100644 --- a/src/shicka.ts +++ b/src/shicka.ts @@ -52,11 +52,12 @@ async function submitGuildCommands(guild: Guild, commandRegistry: ApplicationCom return true; } async function submitGuildHooks(guild: Guild, hookRegistry: WebhookCreateOptionsResolvable[]): Promise { - const guildWebhooks: Collection | undefined = await (async (): Promise | undefined> => { + const guildWebhooks: Collection | null = await (async (): Promise | null> => { try { return await guild.fetchWebhooks(); } catch (error: unknown) { console.warn(error); + return null; } })(); if (guildWebhooks == null) { @@ -95,15 +96,9 @@ async function submitGuildHooks(guild: Guild, hookRegistry: WebhookCreateOptions channel: channelResolvable, ...otherHookOptions }: WebhookCreateOptionsResolvable = hookOptionsResolvable; - const channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null = ((): TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null => { - const channel: GuildBasedChannel | undefined = guild.channels.cache.find((channel: GuildBasedChannel): boolean => { - return channel.name === channelResolvable; - }); - if (channel == null || channel.type === ChannelType.GuildCategory || channel.isThread()) { - return null; - } - return channel; - })(); + const channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null = guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { + return channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === channelResolvable; + }) ?? null; if (channel == null) { continue; } @@ -121,11 +116,12 @@ async function submitGuildHooks(guild: Guild, hookRegistry: WebhookCreateOptions return true; } async function submitGuildRules(guild: Guild, ruleRegistry: AutoModerationRuleCreateOptionsResolvable[]): Promise { - const guildAutoModerationRules: Collection | undefined = await (async (): Promise | undefined> => { + const guildAutoModerationRules: Collection | null = await (async (): Promise | null> => { try { return await guild.autoModerationRules.fetch(); } catch (error: unknown) { console.warn(error); + return null; } })(); if (guildAutoModerationRules == null) { @@ -166,24 +162,16 @@ async function submitGuildRules(guild: Guild, ruleRegistry: AutoModerationRuleCr ...otherRuleOptions }: AutoModerationRuleCreateOptionsResolvable = ruleOptionsResolvable; const exemptChannels: (TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel)[] | null = exemptChannelsResolvable != null ? exemptChannelsResolvable.map((exemptChannelResolvable: string): TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null => { - const channel: GuildBasedChannel | undefined = guild.channels.cache.find((channel: GuildBasedChannel): boolean => { - return channel.name === exemptChannelResolvable; - }); - if (channel == null || channel.type === ChannelType.GuildCategory || channel.isThread()) { - return null; - } - return channel; + return guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { + return channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === exemptChannelResolvable; + }) ?? null; }).filter((exemptChannel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null): exemptChannel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { return exemptChannel != null; }) : null; const exemptRoles: Role[] | null = exemptRolesResolvable != null ? exemptRolesResolvable.map((exemptRoleResolvable: string): Role | null => { - const role: Role | undefined = guild.roles.cache.find((role: Role): boolean => { + return guild.roles.cache.find((role: Role): boolean => { return role.name === exemptRoleResolvable; - }); - if (role == null) { - return null; - } - return role; + }) ?? null; }).filter((exemptRole: Role | null): exemptRole is Role => { return exemptRole != null; }) : null; @@ -197,15 +185,9 @@ async function submitGuildRules(guild: Guild, ruleRegistry: AutoModerationRuleCr channel: channelResolvable, ...otherMetadataOptions }: Omit & {channel?: string} = metadataResolvable; - const channel: TextChannel | NewsChannel | null = channelResolvable != null ? ((): TextChannel | NewsChannel | null => { - const channel: GuildBasedChannel | undefined = guild.channels.cache.find((channel: GuildBasedChannel): boolean => { - return channel.name === channelResolvable; - }); - if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { - return null; - } - return channel; - })() : null; + const channel: TextChannel | NewsChannel | null = channelResolvable != null ? guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel => { + return !channel.isThread() && !channel.isVoiceBased() && channel.isTextBased() && channel.name === channelResolvable; + }) ?? null : null; if (channelResolvable != null && channel == null) { return null; } @@ -311,10 +293,12 @@ client.once("ready", async (client: Client): Promise => { return; } const webhooks: Webhook[] = []; - const guildWebhooks: Collection | undefined = await (async (): Promise | undefined> => { + const guildWebhooks: Collection | null = await (async (): Promise | null> => { try { return await guild.fetchWebhooks(); - } catch {} + } catch { + return null; + } })(); if (guildWebhooks == null) { return; @@ -342,10 +326,12 @@ client.once("ready", async (client: Client): Promise => { const job: Job = schedule.scheduleJob(hookName, jobOptions, async (...data: [timestamp: Date]): Promise => { const webhooks: Webhook[] = []; for (const guild of client.guilds.cache.values()) { - const guildWebhooks: Collection | undefined = await (async (): Promise | undefined> => { + const guildWebhooks: Collection | null = await (async (): Promise | null> => { try { return await guild.fetchWebhooks(); - } catch {} + } catch { + return null; + } })(); if (guildWebhooks == null) { continue; From f5b06dde5007d561c32f39426cecb9a559dcbe21 Mon Sep 17 00:00:00 2001 From: TrisTOON <36267812+TrisTOON@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:56:32 +0100 Subject: [PATCH 2/2] Enables and handles partials when possible --- src/commands/approve.ts | 7 ++++--- src/commands/chat.ts | 2 +- src/commands/gate.ts | 2 +- src/commands/help.ts | 2 +- src/commands/refuse.ts | 7 ++++--- src/commands/roadmap.ts | 2 +- src/commands/tracker.ts | 2 +- src/hooks/application.ts | 3 +++ src/hooks/approval.ts | 3 +++ src/hooks/departure.ts | 3 +++ src/hooks/refusal.ts | 3 +++ src/hooks/verification.ts | 3 +++ src/rules/rule7.ts | 6 +++--- src/shicka.ts | 14 +++++++++++--- 14 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/commands/approve.ts b/src/commands/approve.ts index 6dd9d92..119bf0f 100644 --- a/src/commands/approve.ts +++ b/src/commands/approve.ts @@ -4,6 +4,7 @@ import type { Message, MessageContextMenuCommandInteraction, MessageReaction, + PartialMessageReaction, Role, } from "discord.js"; import type Command from "../commands.js"; @@ -69,7 +70,7 @@ const approveCommand: Command = { const {author}: Message = targetMessage; const memberId: string = author.id; const member: GuildMember | null = guild.members.cache.get(memberId) ?? null; - if (member == null) { + if (member == null || member.partial) { return await guild.members.fetch(memberId); } return member; @@ -95,8 +96,8 @@ const approveCommand: Command = { return; } await targetMessage.react("✅"); - const reaction: MessageReaction | null = targetMessage.reactions.cache.find((reaction: MessageReaction): boolean => { - return (reaction.emoji.name ?? "") === "❎"; + const reaction: MessageReaction | null = targetMessage.reactions.cache.find((reaction: MessageReaction | PartialMessageReaction): reaction is MessageReaction => { + return !reaction.partial && (reaction.emoji.name ?? "") === "❎"; }) ?? null; if (reaction != null) { await reaction.users.remove(); diff --git a/src/commands/chat.ts b/src/commands/chat.ts index baef2e5..c6b5e8e 100644 --- a/src/commands/chat.ts +++ b/src/commands/chat.ts @@ -328,7 +328,7 @@ const chatCommand: Command = { const thread: ThreadChannel | null = await (async (): Promise | null> => { try { const thread: ThreadChannel | null = channel.threads.cache.get(identifier) ?? null; - if (thread == null) { + if (thread == null || thread.partial) { return await channel.threads.fetch(identifier); } return thread; diff --git a/src/commands/gate.ts b/src/commands/gate.ts index a86cbee..0ec1e56 100644 --- a/src/commands/gate.ts +++ b/src/commands/gate.ts @@ -163,7 +163,7 @@ const gateCommand: Command = { const thread: ThreadChannel | null = await (async (): Promise | null> => { try { const thread: ThreadChannel | null = channel.threads.cache.get(identifier) ?? null; - if (thread == null) { + if (thread == null || thread.partial) { return await channel.threads.fetch(identifier); } return thread; diff --git a/src/commands/help.ts b/src/commands/help.ts index af77180..a310da5 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -302,7 +302,7 @@ const helpCommand: Command = { return null; } const channel: GuildBasedChannel | null = autoModerationRule.guild.channels.cache.get(channelId) ?? null; - if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { + if (channel == null || channel.partial || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { return null; } return channel; diff --git a/src/commands/refuse.ts b/src/commands/refuse.ts index 95a175f..4566fe3 100644 --- a/src/commands/refuse.ts +++ b/src/commands/refuse.ts @@ -4,6 +4,7 @@ import type { Message, MessageContextMenuCommandInteraction, MessageReaction, + PartialMessageReaction, Role, } from "discord.js"; import type Command from "../commands.js"; @@ -69,7 +70,7 @@ const refuseCommand: Command = { const {author}: Message = targetMessage; const memberId: string = author.id; const member: GuildMember | null = guild.members.cache.get(memberId) ?? null; - if (member == null) { + if (member == null || member.partial) { return await guild.members.fetch(memberId); } return member; @@ -95,8 +96,8 @@ const refuseCommand: Command = { return; } await targetMessage.react("❎"); - const reaction: MessageReaction | null = targetMessage.reactions.cache.find((reaction: MessageReaction): boolean => { - return (reaction.emoji.name ?? "") === "✅"; + const reaction: MessageReaction | null = targetMessage.reactions.cache.find((reaction: MessageReaction | PartialMessageReaction): reaction is MessageReaction => { + return !reaction.partial && (reaction.emoji.name ?? "") === "✅"; }) ?? null; if (reaction != null) { await reaction.users.remove(); diff --git a/src/commands/roadmap.ts b/src/commands/roadmap.ts index bace214..bf78ba6 100644 --- a/src/commands/roadmap.ts +++ b/src/commands/roadmap.ts @@ -51,7 +51,7 @@ const roadmapCommand: Command = { const {guild, locale}: ChatInputCommandInteraction<"cached"> = interaction; const resolvedLocale: Locale = resolve(locale); const channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null = guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { - return channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === commandIntentChannel; + return !channel.partial && channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === commandIntentChannel; }) ?? null; function formatMessage(locale: Locale): string { return replyLocalizations[locale]({ diff --git a/src/commands/tracker.ts b/src/commands/tracker.ts index 553d488..1001978 100644 --- a/src/commands/tracker.ts +++ b/src/commands/tracker.ts @@ -75,7 +75,7 @@ const trackerCommand: Command = { const {guild, locale}: ChatInputCommandInteraction<"cached"> = interaction; const resolvedLocale: Locale = resolve(locale); const channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null = guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { - return channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === commandIntentChannel; + return !channel.partial && channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === commandIntentChannel; }) ?? null; const links: Localized<(groups: {}) => string>[] = []; for (const item of data) { diff --git a/src/hooks/application.ts b/src/hooks/application.ts index 8a1cfc3..a680064 100644 --- a/src/hooks/application.ts +++ b/src/hooks/application.ts @@ -64,6 +64,9 @@ const applicationHook: Hook = { return; } const [oldMember, newMember]: ClientEvents["guildMemberUpdate"] = (invocation.event as WebjobEvent<"guildMemberUpdate">).data; + if (oldMember.partial) { + return; + } const {guild}: GuildMember = newMember; const {roles}: Guild = guild; const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { diff --git a/src/hooks/approval.ts b/src/hooks/approval.ts index 88d26fa..ffab861 100644 --- a/src/hooks/approval.ts +++ b/src/hooks/approval.ts @@ -68,6 +68,9 @@ const approvalHook: Hook = { return; } const [oldMember, newMember]: ClientEvents["guildMemberUpdate"] = (invocation.event as WebjobEvent<"guildMemberUpdate">).data; + if (oldMember.partial) { + return; + } const {guild}: GuildMember = newMember; const {name, roles}: Guild = guild; const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { diff --git a/src/hooks/departure.ts b/src/hooks/departure.ts index 353b7d7..fdb208b 100644 --- a/src/hooks/departure.ts +++ b/src/hooks/departure.ts @@ -62,6 +62,9 @@ const departureHook: Hook = { return; } const [member]: ClientEvents["guildMemberRemove"] = (invocation.event as WebjobEvent<"guildMemberRemove">).data; + if (member.partial) { + return; + } const greeting: string = greetings[Math.random() * greetings.length | 0]({ memberMention: (): string => { return `**${escapeMarkdown(member.user.username)}**${member.user.discriminator != null && member.user.discriminator !== "0" ? `#**${escapeMarkdown(member.user.discriminator)}**` : ""}${member.user.globalName != null ? ` (**${escapeMarkdown(member.user.globalName)}**)` : ""}`; diff --git a/src/hooks/refusal.ts b/src/hooks/refusal.ts index ce3607a..fcf483e 100644 --- a/src/hooks/refusal.ts +++ b/src/hooks/refusal.ts @@ -68,6 +68,9 @@ const refusalHook: Hook = { return; } const [oldMember, newMember]: ClientEvents["guildMemberUpdate"] = (invocation.event as WebjobEvent<"guildMemberUpdate">).data; + if (oldMember.partial) { + return; + } const {guild}: GuildMember = newMember; const {name, roles}: Guild = guild; const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { diff --git a/src/hooks/verification.ts b/src/hooks/verification.ts index 8bac745..fca6b11 100644 --- a/src/hooks/verification.ts +++ b/src/hooks/verification.ts @@ -64,6 +64,9 @@ const verificationHook: Hook = { return; } const [oldMember, newMember]: ClientEvents["guildMemberUpdate"] = (invocation.event as WebjobEvent<"guildMemberUpdate">).data; + if (oldMember.partial) { + return; + } const {guild}: GuildMember = newMember; const {roles}: Guild = guild; const applyingRole: Role | null = roles.cache.find((role: Role): boolean => { diff --git a/src/rules/rule7.ts b/src/rules/rule7.ts index d7b68fc..4adbed7 100644 --- a/src/rules/rule7.ts +++ b/src/rules/rule7.ts @@ -87,7 +87,7 @@ const rule7Rule: Rule = { const thread: ThreadChannel | null = await (async (): Promise | null> => { try { const thread: ThreadChannel | null = channel.threads.cache.get(messageId) ?? null; - if (thread == null) { + if (thread == null || thread.partial) { return await channel.threads.fetch(messageId); } return thread; @@ -119,7 +119,7 @@ const rule7Rule: Rule = { } const {rulesChannel}: Guild = guild; const manualChannel: TextChannel | null = ruleRulesChannel != null ? guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel => { - return channel.type !== ChannelType.GuildText && channel.name === ruleRulesChannel; + return !channel.partial && channel.type !== ChannelType.GuildText && channel.name === ruleRulesChannel; }) ?? null : rulesChannel; if (manualChannel != null) { await message.reply({ @@ -143,7 +143,7 @@ const rule7Rule: Rule = { return null; } const channel: GuildBasedChannel | null = autoModerationRule.guild.channels.cache.get(channelId) ?? null; - if (channel == null || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { + if (channel == null || channel.partial || channel.isThread() || channel.isVoiceBased() || !channel.isTextBased()) { return null; } return channel; diff --git a/src/shicka.ts b/src/shicka.ts index d249ff5..6616ec5 100644 --- a/src/shicka.ts +++ b/src/shicka.ts @@ -30,6 +30,7 @@ import { ChannelType, Client, GatewayIntentBits, + Partials, } from "discord.js"; import schedule from "node-schedule"; import * as commands from "./commands.js"; @@ -97,7 +98,7 @@ async function submitGuildHooks(guild: Guild, hookRegistry: WebhookCreateOptions ...otherHookOptions }: WebhookCreateOptionsResolvable = hookOptionsResolvable; const channel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null = guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { - return channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === channelResolvable; + return !channel.partial && channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === channelResolvable; }) ?? null; if (channel == null) { continue; @@ -163,7 +164,7 @@ async function submitGuildRules(guild: Guild, ruleRegistry: AutoModerationRuleCr }: AutoModerationRuleCreateOptionsResolvable = ruleOptionsResolvable; const exemptChannels: (TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel)[] | null = exemptChannelsResolvable != null ? exemptChannelsResolvable.map((exemptChannelResolvable: string): TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null => { return guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { - return channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === exemptChannelResolvable; + return !channel.partial && channel.type !== ChannelType.GuildCategory && !channel.isThread() && channel.name === exemptChannelResolvable; }) ?? null; }).filter((exemptChannel: TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel | null): exemptChannel is TextChannel | NewsChannel | VoiceChannel | StageChannel | ForumChannel | MediaChannel => { return exemptChannel != null; @@ -186,7 +187,7 @@ async function submitGuildRules(guild: Guild, ruleRegistry: AutoModerationRuleCr ...otherMetadataOptions }: Omit & {channel?: string} = metadataResolvable; const channel: TextChannel | NewsChannel | null = channelResolvable != null ? guild.channels.cache.find((channel: GuildBasedChannel): channel is TextChannel | NewsChannel => { - return !channel.isThread() && !channel.isVoiceBased() && channel.isTextBased() && channel.name === channelResolvable; + return !channel.partial && !channel.isThread() && !channel.isVoiceBased() && channel.isTextBased() && channel.name === channelResolvable; }) ?? null : null; if (channelResolvable != null && channel == null) { return null; @@ -234,6 +235,13 @@ const client: Client = new Client({ GatewayIntentBits.GuildMessages, GatewayIntentBits.Guilds, ], + partials: [ + Partials.User, + Partials.Channel, + Partials.GuildMember, + Partials.Message, + Partials.Reaction, + ], presence: { activities: [ {