diff --git a/backend/src/api/docs.ts b/backend/src/api/docs.ts index 29a3dba9..1ce9f28d 100644 --- a/backend/src/api/docs.ts +++ b/backend/src/api/docs.ts @@ -56,7 +56,7 @@ export function initDocs(app: express.Express) { const name = plugin.name; const info = plugin.info || {}; - const commands = (plugin.commands || []).map((cmd) => ({ + const commands = (plugin.messageCommands || []).map((cmd) => ({ trigger: cmd.trigger, permission: cmd.permission, signature: cmd.signature, diff --git a/backend/src/commandTypes.ts b/backend/src/commandTypes.ts index 75c6225c..2ab56fd7 100644 --- a/backend/src/commandTypes.ts +++ b/backend/src/commandTypes.ts @@ -1,5 +1,18 @@ -import { GuildChannel, GuildMember, Snowflake, Util, User, GuildTextBasedChannel } from "discord.js"; -import { baseCommandParameterTypeHelpers, baseTypeConverters, CommandContext, TypeConversionError } from "knub"; +import { + GuildChannel, + GuildMember, + Snowflake, + User, + GuildTextBasedChannel, + escapeCodeBlock, + escapeInlineCode, +} from "discord.js"; +import { + baseCommandParameterTypeHelpers, + messageCommandBaseTypeConverters, + CommandContext, + TypeConversionError, +} from "knub"; import { createTypeHelper } from "knub-command-manager"; import { channelMentionRegex, @@ -14,11 +27,9 @@ import { import { isValidTimezone } from "./utils/isValidTimezone"; import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget"; import { inputPatternToRegExp } from "./validatorUtils"; -import { getChannelId } from "knub/dist/utils"; -import { disableCodeBlocks } from "knub/dist/helpers"; export const commandTypes = { - ...baseTypeConverters, + ...messageCommandBaseTypeConverters, delay(value) { const result = convertDelayStringToMS(value); @@ -32,7 +43,7 @@ export const commandTypes = { async resolvedUser(value, context: CommandContext) { const result = await resolveUser(context.pluginData.client, value); if (result == null || result instanceof UnknownUser) { - throw new TypeConversionError(`User \`${Util.escapeCodeBlock(value)}\` was not found`); + throw new TypeConversionError(`User \`${escapeCodeBlock(value)}\` was not found`); } return result; }, @@ -40,7 +51,7 @@ export const commandTypes = { async resolvedUserLoose(value, context: CommandContext) { const result = await resolveUser(context.pluginData.client, value); if (result == null) { - throw new TypeConversionError(`Invalid user: \`${Util.escapeCodeBlock(value)}\``); + throw new TypeConversionError(`Invalid user: \`${escapeCodeBlock(value)}\``); } return result; }, @@ -52,9 +63,7 @@ export const commandTypes = { const result = await resolveMember(context.pluginData.client, context.message.channel.guild, value); if (result == null) { - throw new TypeConversionError( - `Member \`${Util.escapeCodeBlock(value)}\` was not found or they have left the server`, - ); + throw new TypeConversionError(`Member \`${escapeCodeBlock(value)}\` was not found or they have left the server`); } return result; }, @@ -64,7 +73,7 @@ export const commandTypes = { const result = await resolveMessageTarget(context.pluginData, value); if (!result) { - throw new TypeConversionError(`Unknown message \`${Util.escapeInlineCode(value)}\``); + throw new TypeConversionError(`Unknown message \`${escapeInlineCode(value)}\``); } return result; @@ -84,20 +93,20 @@ export const commandTypes = { return value as Snowflake; } - throw new TypeConversionError(`Could not parse ID: \`${Util.escapeInlineCode(value)}\``); + throw new TypeConversionError(`Could not parse ID: \`${escapeInlineCode(value)}\``); }, regex(value: string, context: CommandContext): RegExp { try { return inputPatternToRegExp(value); } catch (e) { - throw new TypeConversionError(`Could not parse RegExp: \`${Util.escapeInlineCode(e.message)}\``); + throw new TypeConversionError(`Could not parse RegExp: \`${escapeInlineCode(e.message)}\``); } }, timezone(value: string) { if (!isValidTimezone(value)) { - throw new TypeConversionError(`Invalid timezone: ${Util.escapeInlineCode(value)}`); + throw new TypeConversionError(`Invalid timezone: ${escapeInlineCode(value)}`); } return value; @@ -105,7 +114,7 @@ export const commandTypes = { guildTextBasedChannel(value: string, context: CommandContext) { // FIXME: Remove once Knub's types have been fixed - return baseTypeConverters.textChannel(value, context) as GuildTextBasedChannel; + return messageCommandBaseTypeConverters.textChannel(value, context) as GuildTextBasedChannel; }, }; diff --git a/backend/src/data/GuildSavedMessages.ts b/backend/src/data/GuildSavedMessages.ts index e6c51e91..a8724938 100644 --- a/backend/src/data/GuildSavedMessages.ts +++ b/backend/src/data/GuildSavedMessages.ts @@ -53,13 +53,13 @@ export class GuildSavedMessages extends BaseGuildRepository { title: embed.title, description: embed.description, url: embed.url, - timestamp: embed.timestamp, + timestamp: embed.timestamp ? Date.parse(embed.timestamp) : null, color: embed.color, fields: embed.fields.map((field) => ({ name: field.name, value: field.value, - inline: field.inline, + inline: field.inline ?? false, })), author: embed.author diff --git a/backend/src/data/entities/SavedMessage.ts b/backend/src/data/entities/SavedMessage.ts index 5456970b..67ac729b 100644 --- a/backend/src/data/entities/SavedMessage.ts +++ b/backend/src/data/entities/SavedMessage.ts @@ -1,4 +1,4 @@ -import { Snowflake } from "discord.js"; +import { Snowflake, StickerFormatType, StickerType } from "discord.js"; import { Column, Entity, PrimaryColumn } from "typeorm"; export interface ISavedMessageAttachmentData { @@ -55,13 +55,13 @@ export interface ISavedMessageEmbedData { } export interface ISavedMessageStickerData { - format: string; + format: StickerFormatType; guildId: Snowflake | null; id: Snowflake; name: string; description: string | null; available: boolean | null; - type: string | null; + type: StickerType | null; } export interface ISavedMessageData { diff --git a/backend/src/plugins/Automod/actions/alert.ts b/backend/src/plugins/Automod/actions/alert.ts index 8bfce04d..a1defe71 100644 --- a/backend/src/plugins/Automod/actions/alert.ts +++ b/backend/src/plugins/Automod/actions/alert.ts @@ -38,7 +38,7 @@ export const AlertAction = automodAction({ const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake); const logs = pluginData.getPlugin(LogsPlugin); - if (channel?.isText()) { + if (channel?.isTextBased()) { const text = actionConfig.text; const theMessageLink = contexts[0].message && messageLink(pluginData.guild.id, contexts[0].message.channel_id, contexts[0].message.id); diff --git a/backend/src/plugins/Automod/actions/reply.ts b/backend/src/plugins/Automod/actions/reply.ts index 865ef127..b40e744b 100644 --- a/backend/src/plugins/Automod/actions/reply.ts +++ b/backend/src/plugins/Automod/actions/reply.ts @@ -36,7 +36,7 @@ export const ReplyAction = automodAction({ .filter((c) => c.message?.channel_id) .filter((c) => { const channel = pluginData.guild.channels.cache.get(c.message!.channel_id as Snowflake); - return channel?.isText(); + return channel?.isTextBased(); }); const contextsByChannelId = contextsWithTextChannels.reduce((map: Map, context) => { diff --git a/backend/src/plugins/Automod/actions/startThread.ts b/backend/src/plugins/Automod/actions/startThread.ts index 943f9be6..130e7035 100644 --- a/backend/src/plugins/Automod/actions/startThread.ts +++ b/backend/src/plugins/Automod/actions/startThread.ts @@ -32,7 +32,7 @@ export const StartThreadAction = automodAction({ const threads = contexts.filter((c) => { if (!c.message || !c.user) return false; const channel = pluginData.guild.channels.cache.get(c.message.channel_id); - if (channel?.type !== ChannelTypeStrings.TEXT || !channel.isText()) return false; // for some reason the typing here for channel.type defaults to ThreadChannelTypes (?) + if (channel?.type !== ChannelTypeStrings.TEXT || !channel.isTextBased()) return false; // for some reason the typing here for channel.type defaults to ThreadChannelTypes (?) // check against max threads per channel if (actionConfig.limit_per_channel && actionConfig.limit_per_channel > 0) { const threadCount = channel.threads.cache.filter( diff --git a/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts b/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts index 9576d149..36a31ec1 100644 --- a/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts +++ b/backend/src/plugins/Automod/functions/resolveActionContactMethods.ts @@ -19,7 +19,7 @@ export function resolveActionContactMethods( } const channel = pluginData.guild.channels.cache.get(actionConfig.notifyChannel as Snowflake); - if (!channel?.isText()) { + if (!channel?.isTextBased()) { throw new RecoverablePluginError(ERRORS.INVALID_USER_NOTIFICATION_CHANNEL); } diff --git a/backend/src/plugins/Counters/commands/ViewCounterCmd.ts b/backend/src/plugins/Counters/commands/ViewCounterCmd.ts index d4d95b76..01116873 100644 --- a/backend/src/plugins/Counters/commands/ViewCounterCmd.ts +++ b/backend/src/plugins/Counters/commands/ViewCounterCmd.ts @@ -68,7 +68,7 @@ export const ViewCounterCmd = guildPluginMessageCommand()({ } const potentialChannel = pluginData.guild.channels.resolve(reply.content as Snowflake); - if (!potentialChannel?.isText()) { + if (!potentialChannel?.isTextBased()) { sendErrorMessage(pluginData, message.channel, "Channel is not a text channel, cancelling"); return; } diff --git a/backend/src/plugins/Logs/util/log.ts b/backend/src/plugins/Logs/util/log.ts index 26be9585..ab6a29e4 100644 --- a/backend/src/plugins/Logs/util/log.ts +++ b/backend/src/plugins/Logs/util/log.ts @@ -88,7 +88,7 @@ export async function log( logChannelLoop: for (const [channelId, opts] of Object.entries(logChannels)) { const channel = pluginData.guild.channels.cache.get(channelId as Snowflake); - if (!channel?.isText()) continue; + if (!channel?.isTextBased()) continue; if (pluginData.state.channelCooldowns.isOnCooldown(channelId)) continue; if (opts.include?.length && !opts.include.includes(typeStr)) continue; if (opts.exclude && opts.exclude.includes(typeStr)) continue; diff --git a/backend/src/plugins/Mutes/functions/muteUser.ts b/backend/src/plugins/Mutes/functions/muteUser.ts index 3956be8a..a6293db1 100644 --- a/backend/src/plugins/Mutes/functions/muteUser.ts +++ b/backend/src/plugins/Mutes/functions/muteUser.ts @@ -186,7 +186,7 @@ export async function muteUser( const channel = config.message_channel ? pluginData.guild.channels.cache.get(config.message_channel as Snowflake) : null; - if (useChannel && channel?.isText()) { + if (useChannel && channel?.isTextBased()) { contactMethods.push({ type: "channel", channel }); } } diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts index a63b1312..ba6e668b 100644 --- a/backend/src/plugins/Post/util/actualPostCmd.ts +++ b/backend/src/plugins/Post/util/actualPostCmd.ts @@ -30,7 +30,7 @@ export async function actualPostCmd( "repeat-times"?: number; } = {}, ) { - if (!targetChannel.isText()) { + if (!targetChannel.isTextBased()) { msg.channel.send(errorMessage("Specified channel is not a text-based channel")); return; } diff --git a/backend/src/plugins/Post/util/postScheduledPost.ts b/backend/src/plugins/Post/util/postScheduledPost.ts index 824f5021..94959043 100644 --- a/backend/src/plugins/Post/util/postScheduledPost.ts +++ b/backend/src/plugins/Post/util/postScheduledPost.ts @@ -45,7 +45,7 @@ export async function postScheduledPost(pluginData: GuildPluginData, reminder: Reminder) { const channel = pluginData.guild.channels.cache.get(reminder.channel_id as Snowflake); - if (channel && (channel.isText() || channel.isThread())) { + if (channel && (channel.isTextBased() || channel.isThread())) { try { // Only show created at date if one exists if (moment.utc(reminder.created_at).isValid()) { diff --git a/backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts b/backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts index 66e9666e..e8c86f2f 100644 --- a/backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts +++ b/backend/src/plugins/RoleButtons/functions/applyRoleButtons.ts @@ -19,7 +19,7 @@ export async function applyRoleButtons( // Remove existing role buttons, if any if (existingSavedButtons?.channel_id) { const existingChannel = await pluginData.guild.channels.fetch(configItem.message.channel_id).catch(() => null); - const existingMessage = await (existingChannel?.isText() && + const existingMessage = await (existingChannel?.isTextBased() && existingChannel.messages.fetch(existingSavedButtons.message_id).catch(() => null)); if (existingMessage && existingMessage.components.length) { await existingMessage.edit({ @@ -32,7 +32,7 @@ export async function applyRoleButtons( if ("message_id" in configItem.message) { // channel id + message id: apply role buttons to existing message const channel = await pluginData.guild.channels.fetch(configItem.message.channel_id).catch(() => null); - const messageCandidate = await (channel?.isText() && + const messageCandidate = await (channel?.isTextBased() && channel.messages.fetch(configItem.message.message_id).catch(() => null)); if (!messageCandidate) { pluginData.getPlugin(LogsPlugin).logBotAlert({ @@ -55,10 +55,10 @@ export async function applyRoleButtons( } const channel = await pluginData.guild.channels.fetch(configItem.message.channel_id).catch(() => null); - if (channel && (!channel.isText || typeof channel.isText !== "function")) { + if (channel && (!channel.isTextBased || typeof channel.isTextBased !== "function")) { console.log("wtf", pluginData.guild?.id, configItem.message.channel_id); } - if (!channel || !channel?.isText?.()) { + if (!channel || !channel?.isTextBased()) { pluginData.getPlugin(LogsPlugin).logBotAlert({ body: `Text channel not found for role_buttons/${configItem.name}`, }); diff --git a/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts b/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts index 6535852f..cb62432e 100644 --- a/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts +++ b/backend/src/plugins/Starboard/util/removeMessageFromStarboard.ts @@ -1,3 +1,4 @@ +import { ChannelType } from "discord.js"; import { GuildPluginData } from "knub"; import { StarboardMessage } from "../../../data/entities/StarboardMessage"; import { noop } from "../../../utils"; @@ -12,7 +13,7 @@ export async function removeMessageFromStarboard( // this code is now Almeida-certified and no longer ugly :ok_hand: :cake: const channel = pluginData.client.channels.cache.find((c) => c.id === msg.starboard_channel_id); - if (!channel?.isText()) return; + if (channel?.type !== ChannelType.GuildText) return; const message = await channel.messages.fetch(msg.starboard_message_id).catch(noop); if (!message?.deletable) return; await message.delete().catch(noop); diff --git a/backend/src/plugins/Utility/commands/CleanCmd.ts b/backend/src/plugins/Utility/commands/CleanCmd.ts index 6abbb481..5e99cecc 100644 --- a/backend/src/plugins/Utility/commands/CleanCmd.ts +++ b/backend/src/plugins/Utility/commands/CleanCmd.ts @@ -81,7 +81,7 @@ export async function cleanCmd(pluginData: GuildPluginData, a } const targetChannel = args.channel ? pluginData.guild.channels.cache.get(args.channel as Snowflake) : msg.channel; - if (!targetChannel?.isText()) { + if (!targetChannel?.isTextBased()) { sendErrorMessage(pluginData, msg.channel, `Invalid channel specified`); return; } diff --git a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts index c4922bed..dd75d807 100644 --- a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts +++ b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts @@ -1,4 +1,4 @@ -import { MessageEmbedOptions, Role } from "discord.js"; +import { MessageEmbedOptions } from "discord.js"; import humanizeDuration from "humanize-duration"; import { GuildPluginData } from "knub"; import moment from "moment-timezone"; diff --git a/backend/src/types.ts b/backend/src/types.ts index 6f24a7fd..fd068ef8 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; import { BaseConfig, Knub } from "knub"; -export interface ZeppelinGuildConfig extends BaseConfig { +export interface ZeppelinGuildConfig extends BaseConfig { success_emoji?: string; error_emoji?: string; @@ -26,7 +26,7 @@ export const ZeppelinGuildConfigSchema = t.type({ }); export const PartialZeppelinGuildConfigSchema = t.partial(ZeppelinGuildConfigSchema.props); -export interface ZeppelinGlobalConfig extends BaseConfig { +export interface ZeppelinGlobalConfig extends BaseConfig { url: string; owners?: string[]; } @@ -37,7 +37,7 @@ export const ZeppelinGlobalConfigSchema = t.type({ plugins: t.record(t.string, t.unknown), }); -export type TZeppelinKnub = Knub; +export type TZeppelinKnub = Knub; /** * Wrapper for the string type that indicates the text will be parsed as Markdown later diff --git a/backend/src/utils/isThreadChannel.ts b/backend/src/utils/isThreadChannel.ts index 6a1060df..aff2c0eb 100644 --- a/backend/src/utils/isThreadChannel.ts +++ b/backend/src/utils/isThreadChannel.ts @@ -1,10 +1,10 @@ -import { Channel, ThreadChannel } from "discord.js"; +import { AnyThreadChannel, Channel, ChannelType } from "discord.js"; import { ChannelTypeStrings } from "src/types"; -export function isThreadChannel(channel: Channel): channel is ThreadChannel { +export function isThreadChannel(channel: Channel): channel is AnyThreadChannel { return ( - channel.type === ChannelTypeStrings.NEWS_THREAD || - channel.type === ChannelTypeStrings.PUBLIC_THREAD || - channel.type === ChannelTypeStrings.PRIVATE_THREAD + channel.type === ChannelType.PublicThread || + channel.type === ChannelType.PrivateThread || + channel.type === ChannelType.AnnouncementThread ); } diff --git a/backend/src/utils/resolveMessageTarget.ts b/backend/src/utils/resolveMessageTarget.ts index 4b24b779..2d39873a 100644 --- a/backend/src/utils/resolveMessageTarget.ts +++ b/backend/src/utils/resolveMessageTarget.ts @@ -47,7 +47,7 @@ export async function resolveMessageTarget(pluginData: GuildPluginData, val } const channel = pluginData.guild.channels.resolve(result.channelId as Snowflake); - if (!channel?.isText()) { + if (!channel?.isTextBased()) { return null; } diff --git a/backend/src/utils/templateSafeObjects.ts b/backend/src/utils/templateSafeObjects.ts index d57e1fca..6ce23a99 100644 --- a/backend/src/utils/templateSafeObjects.ts +++ b/backend/src/utils/templateSafeObjects.ts @@ -9,6 +9,7 @@ import { Snowflake, StageInstance, Sticker, + StickerFormatType, ThreadChannel, User, } from "discord.js"; @@ -239,7 +240,7 @@ export function userToTemplateSafeUser(user: User | UnknownUser): TemplateSafeUs discriminator: user.discriminator, mention: `<@${user.id}>`, tag: user.tag, - avatarURL: user.displayAvatarURL?.({ dynamic: true }), + avatarURL: user.displayAvatarURL?.(), bot: user.bot, createdAt: user.createdTimestamp, }); @@ -305,9 +306,9 @@ export function stickerToTemplateSafeSticker(sticker: Sticker): TemplateSafeStic packId: sticker.packId ?? undefined, name: sticker.name, description: sticker.description ?? "", - tags: sticker.tags?.join(", ") ?? "", + tags: sticker.tags ?? "", format: sticker.format, - animated: sticker.format === "PNG" ? false : true, + animated: sticker.format === StickerFormatType.PNG ? false : true, url: sticker.url, }); } diff --git a/backend/src/utils/waitForInteraction.ts b/backend/src/utils/waitForInteraction.ts index ece841ca..1b9cc756 100644 --- a/backend/src/utils/waitForInteraction.ts +++ b/backend/src/utils/waitForInteraction.ts @@ -1,23 +1,35 @@ -import { MessageActionRow, MessageButton, MessageComponentInteraction, MessageOptions, TextChannel } from "discord.js"; +import { + ActionRow, + ActionRowBuilder, + ButtonBuilder, + ButtonComponent, + ButtonStyle, + Message, + MessageActionRowComponentBuilder, + MessageComponentInteraction, + MessageCreateOptions, + MessagePayloadOption, + TextChannel, +} from "discord.js"; import { noop } from "knub/dist/utils"; import moment from "moment"; import uuidv4 from "uuid/v4"; export async function waitForButtonConfirm( channel: TextChannel, - toPost: MessageOptions, + toPost: MessageCreateOptions, options?: WaitForOptions, ): Promise { return new Promise(async (resolve) => { const idMod = `${channel.guild.id}-${moment.utc().valueOf()}`; - const row = new MessageActionRow().addComponents([ - new MessageButton() - .setStyle("SUCCESS") + const row = new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setStyle(ButtonStyle.Success) .setLabel(options?.confirmText || "Confirm") .setCustomId(`confirmButton:${idMod}:${uuidv4()}`), - new MessageButton() - .setStyle("DANGER") + new ButtonBuilder() + .setStyle(ButtonStyle.Danger) .setLabel(options?.cancelText || "Cancel") .setCustomId(`cancelButton:${idMod}:${uuidv4()}`), ]); @@ -41,7 +53,7 @@ export async function waitForButtonConfirm( } }); collector.on("end", () => { - if (!message.deleted) message.delete().catch(noop); + if (message.deletable) message.delete().catch(noop); resolve(false); }); });