From 6fa715fb8bd417c6c70230bad06997c31a8a0e4e Mon Sep 17 00:00:00 2001 From: "Navy.gif" Date: Fri, 8 Dec 2023 21:20:36 +0200 Subject: [PATCH] Check for channel type properly --- @types/Client.ts | 8 ++++---- @types/Moderation.d.ts | 8 ++++---- src/client/components/ModerationManager.ts | 16 ++++++++++------ src/client/components/RateLimiter.ts | 16 ++++++++-------- src/client/components/Resolver.ts | 2 +- src/client/components/commands/utility/Poll.ts | 8 ++++---- src/client/components/commands/utility/Remind.ts | 4 ++-- .../components/settings/utility/Selfrole.ts | 2 +- src/client/infractions/Prune.ts | 6 ++---- src/client/interfaces/Infraction.ts | 6 +++--- src/client/interfaces/commands/Command.ts | 1 + 11 files changed, 40 insertions(+), 37 deletions(-) diff --git a/@types/Client.ts b/@types/Client.ts index 109668f..24cec32 100644 --- a/@types/Client.ts +++ b/@types/Client.ts @@ -8,7 +8,6 @@ import { GuildMember, Role, BaseChannel, - TextChannel, PermissionsString, Channel, APIEmbed, @@ -20,7 +19,8 @@ import { ThreadChannel, APIEmbedField, VoiceState, - Invite + Invite, + GuildTextBasedChannel, } from 'discord.js'; import { InvokerWrapper, MemberWrapper, UserWrapper } from '../src/client/components/wrappers/index.js'; import GuildWrapper from '../src/client/components/wrappers/GuildWrapper.js'; @@ -384,9 +384,9 @@ export type BaseInfractionData = { arguments?: InfractionArguments, targetType?: InfractionTargetType, guild: GuildWrapper, - channel?: TextChannel, + channel?: GuildTextBasedChannel, invoker?: InvokerWrapper | null, - target?: MemberWrapper | UserWrapper | TextChannel, + target?: MemberWrapper | UserWrapper | GuildTextBasedChannel, executor?: MemberWrapper | UserWrapper duration?: number | null, reason?: string, diff --git a/@types/Moderation.d.ts b/@types/Moderation.d.ts index 75f8f09..1b668fa 100644 --- a/@types/Moderation.d.ts +++ b/@types/Moderation.d.ts @@ -1,9 +1,9 @@ -import { GuildBasedChannel, Message, TextChannel } from 'discord.js'; +import { GuildBasedChannel, Message } from 'discord.js'; import { GuildWrapper, InvokerWrapper, MemberWrapper, UserWrapper } from '../src/client/components/wrappers/index.ts'; import { CommandOption, Infraction } from '../src/client/interfaces/index.ts'; import { FormatParams, InfractionType } from './Client.ts'; -export type ModerationTarget = UserWrapper | MemberWrapper | TextChannel +export type ModerationTarget = UserWrapper | MemberWrapper | GuildTextBasedChannel export type ModerationTargets = ModerationTarget[] export type InfractionHandlerOptions = { @@ -14,7 +14,7 @@ export type InfractionHandlerOptions = { export type HandleAutomodOptions = { prune: boolean, - channel: TextChannel + channel: GuildTextBasedChannel } export type HandleTargetData = { @@ -25,7 +25,7 @@ export type HandleTargetData = { expiration?: number | null, invoker?: InvokerWrapper, arguments?: { [key: string]: CommandOption | undefined }, - channel: TextChannel, + channel: GuildTextBasedChannel, executor: MemberWrapper, duration?: number | null, data: object, diff --git a/src/client/components/ModerationManager.ts b/src/client/components/ModerationManager.ts index acb5e0d..0ba2c0d 100644 --- a/src/client/components/ModerationManager.ts +++ b/src/client/components/ModerationManager.ts @@ -1,7 +1,7 @@ import { inspect } from 'node:util'; import { stripIndents } from 'common-tags'; -import { Collection, GuildTextBasedChannel, Message, TextChannel } from 'discord.js'; +import { Collection, GuildTextBasedChannel, Message } from 'discord.js'; import { LoggerClient } from '@navy.gif/logger'; import { DiscordStruct, InfractionJSON, InfractionType, ModerationCallback } from '../../../@types/Client.js'; @@ -172,7 +172,7 @@ class ModerationManager implements Initialisable if (!executor) throw new Error('Missing executor'); - if (!(invoker.channel instanceof TextChannel)) + if (!invoker.channel?.isTextBased()) throw new Error('Invalid channel'); const responses = []; @@ -181,7 +181,7 @@ class ModerationManager implements Initialisable const response = await this._handleTarget(Infraction, target, { invoker, guild: invoker.guild!, - channel: invoker.channel!, + channel: invoker.channel, executor, arguments: args, points: args.points?.asNumber, @@ -338,7 +338,11 @@ class ModerationManager implements Initialisable * @memberof ModerationManager */ // eslint-disable-next-line max-lines-per-function - async _handleTarget (Infraction: typeof InfractionClass, target: UserWrapper | MemberWrapper | TextChannel, info: HandleTargetData) + async _handleTarget ( + Infraction: typeof InfractionClass, + target: UserWrapper | MemberWrapper | GuildTextBasedChannel, + info: HandleTargetData + ) { // wrapper: guildWrapper const { reason, force, guild } = info; @@ -542,7 +546,7 @@ class ModerationManager implements Initialisable else if (i.targetType === 'CHANNEL') { target = guild.channels.resolve(i.target!); - if (!(target instanceof TextChannel)) + if (!target?.isTextBased()) throw new Error('Invalid channel'); } @@ -550,7 +554,7 @@ class ModerationManager implements Initialisable { const executor = await guild.memberWrapper(i.executor!).catch(() => null) ?? await guild.memberWrapper(guild.me!); const channel = guild.channels.resolve(i.channel!); - if (!(channel instanceof TextChannel)) + if (!channel?.isTextBased()) throw new Error('Bad channel'); if (!executor) throw new Error('Missing executor'); diff --git a/src/client/components/RateLimiter.ts b/src/client/components/RateLimiter.ts index 75ad16d..f3fadb3 100644 --- a/src/client/components/RateLimiter.ts +++ b/src/client/components/RateLimiter.ts @@ -1,6 +1,6 @@ // const { TextChannel, Message } = require('discord.js'); -import { Message, MessageCreateOptions, TextChannel } from 'discord.js'; +import { GuildTextBasedChannel, Message, MessageCreateOptions, TextChannel } from 'discord.js'; import DiscordClient from '../DiscordClient.js'; type QueueEntry = { @@ -58,11 +58,11 @@ class RateLimiter * @returns {Promise} * @memberof RateLimiter */ - queueDelete (channel: TextChannel, message: Message) + queueDelete (channel: GuildTextBasedChannel, message: Message) { return new Promise((resolve, reject) => { - if (!channel || !(channel instanceof TextChannel)) + if (!channel || !(channel.isTextBased())) reject(new Error('Missing channel')); if (!message || !(message instanceof Message)) reject(new Error('Missing message')); @@ -78,7 +78,7 @@ class RateLimiter }); } - async #delete (channel: TextChannel) + async #delete (channel: GuildTextBasedChannel) { if (!this.#deleteQueue[channel.id] || !this.#deleteQueue[channel.id].length) return; @@ -142,11 +142,11 @@ class RateLimiter * @returns {Promise} Resolves when the message is sent, rejects if sending fails * @memberof RateLimiter */ - queueSend (channel: TextChannel, message: string) + queueSend (channel: GuildTextBasedChannel, message: string) { return new Promise((resolve, reject) => { - if (!channel || !(channel instanceof TextChannel)) + if (!channel?.isTextBased()) reject(new Error('Missing channel.')); if (!message) reject(new Error('Missing message.')); @@ -167,7 +167,7 @@ class RateLimiter }); } - async #send (channel: TextChannel) + async #send (channel: GuildTextBasedChannel) { if (!this.#sendQueue[channel.id] || !this.#sendQueue[channel.id].length) return; @@ -231,7 +231,7 @@ class RateLimiter { return new Promise((resolve, reject) => { - if (!channel || !(channel instanceof TextChannel)) + if (!channel?.isTextBased()) reject(new Error('Missing channel')); if (!this.#client.user || !channel.permissionsFor(this.#client.user)?.has('SendMessages')) reject(new Error('Missing permission SendMessages')); diff --git a/src/client/components/Resolver.ts b/src/client/components/Resolver.ts index e636b7a..8f9db12 100644 --- a/src/client/components/Resolver.ts +++ b/src/client/components/Resolver.ts @@ -418,7 +418,7 @@ class Resolver { const match = resolveable.toString().match(id) || []; const [ , ch ] = match; - const channel = await CM.fetch(ch).catch(null); + const channel = await CM.fetch(ch).catch(() => null); if (channel && filter(channel)) resolved.push(channel as unknown as T); } diff --git a/src/client/components/commands/utility/Poll.ts b/src/client/components/commands/utility/Poll.ts index e8447ad..ef35679 100644 --- a/src/client/components/commands/utility/Poll.ts +++ b/src/client/components/commands/utility/Poll.ts @@ -4,7 +4,7 @@ import InvokerWrapper from '../../wrappers/InvokerWrapper.js'; import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js'; import Util from '../../../../utilities/Util.js'; import { EmbedDefaultColor, PollReactions } from '../../../../constants/Constants.js'; -import { TextChannel } from 'discord.js'; +import { GuildTextBasedChannel, TextChannel } from 'discord.js'; import { CallbackData, PollData } from '../../../../../@types/Guild.js'; class PollCommand extends SlashCommand @@ -54,7 +54,7 @@ class PollCommand extends SlashCommand }); } - async execute (invoker: InvokerWrapper, { choices, channel, duration, multichoice, message }: CommandParams) + async execute (invoker: InvokerWrapper, { choices, channel, duration, multichoice, message }: CommandParams) { const { subcommand, author } = invoker; const guild = invoker.guild!; @@ -64,8 +64,8 @@ class PollCommand extends SlashCommand { // await invoker.deferReply(); const questions = []; - const _channel = channel?.asChannel || invoker.channel; - if (!(_channel instanceof TextChannel)) + const _channel = (channel?.asChannel || invoker.channel) as GuildTextBasedChannel; + if (!_channel?.isTextBased()) throw new CommandError(invoker, { index: 'ERR_INVALID_CHANNEL_TYPE' }); const botMissing = _channel.permissionsFor(this.client.user!)?.missing([ 'SendMessages', 'EmbedLinks' ]); const userMissing = _channel.permissionsFor(member).missing([ 'SendMessages' ]); diff --git a/src/client/components/commands/utility/Remind.ts b/src/client/components/commands/utility/Remind.ts index 19ceb30..c1d3f09 100644 --- a/src/client/components/commands/utility/Remind.ts +++ b/src/client/components/commands/utility/Remind.ts @@ -3,7 +3,7 @@ import DiscordClient from '../../../DiscordClient.js'; import InvokerWrapper from '../../wrappers/InvokerWrapper.js'; import { CommandOptionType, CommandParams } from '../../../../../@types/Client.js'; import Util from '../../../../utilities/Util.js'; -import { APIEmbed, TextChannel } from 'discord.js'; +import { APIEmbed } from 'discord.js'; import { CallbackData, ReminderData } from '../../../../../@types/Guild.js'; import GuildWrapper from '../../wrappers/GuildWrapper.js'; @@ -45,7 +45,7 @@ class RemindCommand extends SlashCommand { const { author, channel, guild, member } = invoker; const subcommand = invoker.subcommand!.name; - if (!channel || !(channel instanceof TextChannel)) + if (!channel?.isTextBased()) throw new Error('Missing channel?'); if (subcommand === 'create') diff --git a/src/client/components/settings/utility/Selfrole.ts b/src/client/components/settings/utility/Selfrole.ts index 7a52eb1..e887cb8 100644 --- a/src/client/components/settings/utility/Selfrole.ts +++ b/src/client/components/settings/utility/Selfrole.ts @@ -77,7 +77,7 @@ class SelfroleSetting extends Setting // old channel for deleting old message if one exists const oldChannel = await guild.resolveChannel(setting.channel); const newChannel = channel?.asChannel || oldChannel; - if (!(newChannel instanceof TextChannel)) + if (!newChannel?.isTextBased()) throw new CommandError(invoker, { index: 'ERR_INVALID_CHANNEL_TYPE' }); if (channel) setting.channel = channel.asChannel.id; // Set the new channel if one is given diff --git a/src/client/infractions/Prune.ts b/src/client/infractions/Prune.ts index 5467d3d..54b2011 100644 --- a/src/client/infractions/Prune.ts +++ b/src/client/infractions/Prune.ts @@ -4,10 +4,8 @@ import DiscordClient from '../DiscordClient.js'; import { LoggerClient } from '@navy.gif/logger'; import { InfractionTargetType, InfractionType } from '../../../@types/Client.js'; import { PruneData } from '../../../@types/Infractions.js'; -import { DMChannel, GuildTextBasedChannel, Message, PartialDMChannel, Snowflake, TextBasedChannel } from 'discord.js'; +import { BaseChannel, DMChannel, GuildTextBasedChannel, Message, PartialDMChannel, Snowflake, TextBasedChannel } from 'discord.js'; import { Collection } from '@discordjs/collection'; -import pkg from 'discord.js'; -const { TextBasedChannelMixin } = pkg; const Arguments = [ 'users', 'bots', 'humans', 'contains', 'startswith', 'endswith', 'emojis', 'reactions', 'text', 'invites', 'links', 'emojis', 'reactions', 'images', 'attachments' ]; @@ -48,7 +46,7 @@ class PruneInfraction extends Infraction hyperlink: opts.hyperlink }); - if (!(this.target instanceof TextBasedChannelMixin)) + if (this.target instanceof BaseChannel && !this.target.isTextBased()) throw new Error('Invalid channel type given'); } } diff --git a/src/client/interfaces/Infraction.ts b/src/client/interfaces/Infraction.ts index ae75627..d35cf49 100644 --- a/src/client/interfaces/Infraction.ts +++ b/src/client/interfaces/Infraction.ts @@ -1,7 +1,7 @@ import { inspect } from 'node:util'; import { LoggerClient } from '@navy.gif/logger'; -import { APIEmbed, GuildChannel, Message, Snowflake, TextChannel } from 'discord.js'; +import { APIEmbed, GuildChannel, GuildTextBasedChannel, Message, Snowflake, TextChannel } from 'discord.js'; import { ObjectId, WithId } from 'mongodb'; import { @@ -60,14 +60,14 @@ class Infraction #guild: GuildWrapper; #channelId: Snowflake | null; - #channel: TextChannel | null; + #channel: GuildTextBasedChannel | null; #messageId: Snowflake | null; #message: InvokerWrapper | null; #targetType: InfractionTargetType; #targetId: Snowflake | null; - #target: UserWrapper | MemberWrapper | TextChannel | null; + #target: UserWrapper | MemberWrapper | GuildTextBasedChannel | null; #executorId: Snowflake | null; #executor: UserWrapper | null; diff --git a/src/client/interfaces/commands/Command.ts b/src/client/interfaces/commands/Command.ts index b6f62d8..ea84074 100644 --- a/src/client/interfaces/commands/Command.ts +++ b/src/client/interfaces/commands/Command.ts @@ -12,6 +12,7 @@ type CommandUsageLimits = { usages: number, duration: number } + type CommandThrottle = { usages: number, start: number,