diff --git a/commands/explore-potatoes.ts b/commands/explore-potatoes.ts index 5b975827e..54f537888 100644 --- a/commands/explore-potatoes.ts +++ b/commands/explore-potatoes.ts @@ -7,7 +7,7 @@ import { APIInteractionDataResolvedChannel, GuildBasedChannel, Snowflake, - InteractionReplyOptions, + BaseMessageOptions, } from "discord.js"; import { @@ -55,7 +55,7 @@ async function textChannelMatches( } case ChannelType.GuildForum: case ChannelType.GuildText: - case ChannelType.GuildNews: { + case ChannelType.GuildAnnouncement: { // If channelFound is a matching non-thread it will have already returned at the start of the function, so only check for threads. const thread = await guild.channels.fetch(channelFound).catch(() => {}); return thread?.parent?.id === channelWanted.id; @@ -97,11 +97,11 @@ const info: ChatInputCommand = { ChannelType.GuildText, ChannelType.GuildVoice, ChannelType.GuildCategory, - ChannelType.GuildNews, - ChannelType.GuildNewsThread, - ChannelType.GuildPublicThread, - ChannelType.GuildPrivateThread, - // ChannelType.GuildForum, + ChannelType.GuildAnnouncement, + ChannelType.AnnouncementThread, + ChannelType.PublicThread, + ChannelType.PrivateThread, + ChannelType.GuildForum, ), ), @@ -129,10 +129,10 @@ const info: ChatInputCommand = { const nextId = generateHash("next"); const prevId = generateHash("prev"); - const messages: InteractionReplyOptions[] = []; + const messages: BaseMessageOptions[] = []; let index = 0; - async function getNextMessage(): Promise { + async function getNextMessage(): Promise { const info = (await fetchedMessages.next()).value; const reply = info diff --git a/commands/guess-addon.ts b/commands/guess-addon.ts index c5543e0ec..11b8396df 100644 --- a/commands/guess-addon.ts +++ b/commands/guess-addon.ts @@ -18,6 +18,7 @@ import { Snowflake, ActionRowBuilder, ModalActionRowComponentBuilder, + chatInputApplicationCommandMention, } from "discord.js"; import Fuse from "fuse.js"; import CONSTANTS from "../common/CONSTANTS.js"; @@ -43,11 +44,12 @@ const fuse = new Fuse(addons, { { name: "description", weight: 2 }, ], }); -const commandMarkdown = `\n\n*Run the command.name === "addon", - )?.id // TODO: chatInputApplicationCommandMention() (waiting on https://github.com/discordjs/discord.js/pull/8546) -}> command for more information about this addon!*`; + )?.id, +)} command for more information about this addon!*`; const GROUP_NAMES = ["Addon name", "Categorization", "Credits", "Misc"] as const; diff --git a/commands/suggestion.ts b/commands/suggestion.ts index 9780a8e4d..8e3730f5b 100644 --- a/commands/suggestion.ts +++ b/commands/suggestion.ts @@ -90,7 +90,7 @@ const COOLDOWN = 60_000; * * @returns The member who made the suggestion. */ -async function getUserFromSuggestion(message: Message): Promise { +async function getUserFromSuggestion(message: Message): Promise { const author = message.author.id === CONSTANTS.robotop ? message.embeds[0]?.footer?.text.split(": ")[1] diff --git a/common/CONSTANTS.js b/common/CONSTANTS.js index 0e6e1cc0c..3f25ab2da 100644 --- a/common/CONSTANTS.js +++ b/common/CONSTANTS.js @@ -88,7 +88,7 @@ export default /** @type {const} */ ({ ), board: enforceChannelType( channels.find((channel) => !!channel?.name.endsWith("board")), - [ChannelType.GuildText, ChannelType.GuildNews], + [ChannelType.GuildText, ChannelType.GuildAnnouncement], ), welcome: guild.systemChannel || diff --git a/common/board.js b/common/board.js index ac7532eea..7ac72365c 100644 --- a/common/board.js +++ b/common/board.js @@ -46,6 +46,8 @@ await boardDatabase.init(); /** * @param {import("./database").Databases["board"] | import("discord.js").Message} info * @param {{ pre?: ButtonBuilder[]; post?: ButtonBuilder[] }} [extraButtons] + * + * @returns {Promise} */ export async function generateBoardMessage(info, extraButtons = {}) { const count = @@ -177,7 +179,7 @@ export async function updateBoard(message) { allowedMentions: pings ? undefined : { users: [] }, }); - if (CONSTANTS.channels.board.type === ChannelType.GuildNews) + if (CONSTANTS.channels.board.type === ChannelType.GuildAnnouncement) promises.push(boardMessage.crosspost()); boardDatabase.data = info diff --git a/common/moderation/logging.js b/common/moderation/logging.js index 99231cb43..901a20a80 100644 --- a/common/moderation/logging.js +++ b/common/moderation/logging.js @@ -10,7 +10,7 @@ export const LOG_GROUPS = /** @type {const} */ ([ /** * @param {string} content * @param {typeof LOG_GROUPS[number]} group - * @param {Pick} [extra] + * @param {Pick} [extra] */ export default async function log(content, group, extra = {}) { const thread = await getLoggingThread(group); diff --git a/common/modmail.js b/common/modmail.js index 4ae15e551..26533d7bc 100644 --- a/common/modmail.js +++ b/common/modmail.js @@ -32,7 +32,7 @@ export const MODMAIL_UNSUPPORTED = * * @param {import("discord.js").Message} message - Message sent by a user. * - * @returns Webhook message. + * @returns {Promise} - Webhook message. */ export async function generateModmailMessage(message) { const { files, embeds } = await extractMessageExtremities(message); @@ -185,7 +185,7 @@ export async function sendOpenedMessage(user) { /** * @param {EmbedBuilder} confirmEmbed * @param {(buttonInteraction: import("discord.js").MessageComponentInteraction) => Promise} onConfirm - * @param {(options: { components: [MessageActionRowBuilder]; embeds: [EmbedBuilder] }) => Promise} reply + * @param {(options: import("discord.js").BaseMessageOptions) => Promise} reply */ export async function generateModmailConfirm(confirmEmbed, onConfirm, reply) { const confirmId = generateHash("confirm"); diff --git a/events/channel/create.js b/events/channel/create.js index 2efa2e91c..77012fcb9 100644 --- a/events/channel/create.js +++ b/events/channel/create.js @@ -10,7 +10,7 @@ export default async function event(channel) { [ChannelType.GuildText]: "Text", [ChannelType.GuildVoice]: "Voice", [ChannelType.GuildCategory]: "Category", - [ChannelType.GuildNews]: "Announcement", + [ChannelType.GuildAnnouncement]: "Announcement", [ChannelType.GuildStageVoice]: "Stage", [ChannelType.GuildForum]: "Forum", }[channel.type] diff --git a/events/channel/delete.js b/events/channel/delete.js index 282d7bc07..9655134a6 100644 --- a/events/channel/delete.js +++ b/events/channel/delete.js @@ -10,7 +10,7 @@ export default async function event(channel) { [ChannelType.GuildText]: "Text", [ChannelType.GuildVoice]: "Voice", [ChannelType.GuildCategory]: "Category", - [ChannelType.GuildNews]: "Announcement", + [ChannelType.GuildAnnouncement]: "Announcement", [ChannelType.GuildStageVoice]: "Stage", [ChannelType.GuildForum]: "Forum", }[channel.type] diff --git a/events/channel/update.js b/events/channel/update.js index c15c2189a..5e1dd93f4 100644 --- a/events/channel/update.js +++ b/events/channel/update.js @@ -26,7 +26,7 @@ export default async function event(oldChannel, newChannel) { [ChannelType.GuildText]: " text", [ChannelType.GuildVoice]: " voice", [ChannelType.GuildCategory]: " category", - [ChannelType.GuildNews]: "n announcement", + [ChannelType.GuildAnnouncement]: "n announcement", [ChannelType.GuildStageVoice]: " stage", [ChannelType.GuildForum]: " forum", }[newChannel.type] + @@ -60,8 +60,12 @@ export default async function event(oldChannel, newChannel) { } if ( - (oldChannel.type === ChannelType.GuildText || oldChannel.type === ChannelType.GuildNews) && - (newChannel.type === ChannelType.GuildText || newChannel.type === ChannelType.GuildNews) + (oldChannel.type === ChannelType.GuildText || + oldChannel.type === ChannelType.GuildForum || + oldChannel.type === ChannelType.GuildAnnouncement) && + (newChannel.type === ChannelType.GuildText || + newChannel.type === ChannelType.GuildForum || + newChannel.type === ChannelType.GuildAnnouncement) ) { if (oldChannel.topic !== newChannel.topic) { log(`✏ Channel ${newChannel.toString()}’s topic was changed!`, "channels", { @@ -96,6 +100,13 @@ export default async function event(oldChannel, newChannel) { ); } + if (oldChannel.type === ChannelType.GuildForum && newChannel.type === ChannelType.GuildForum) { + // TODO + oldChannel.availableTags; + oldChannel.defaultReactionEmoji; + oldChannel.defaultThreadRateLimitPerUser; + } + if (oldChannel.type === ChannelType.GuildVoice && newChannel.type === ChannelType.GuildVoice) oldChannel.videoQualityMode !== newChannel.videoQualityMode && edits.push( diff --git a/events/guild/update.js b/events/guild/update.js index 48addffd2..0372f6444 100644 --- a/events/guild/update.js +++ b/events/guild/update.js @@ -97,10 +97,16 @@ export default async function event(oldGuild, newGuild) { if ( oldGuild.features.includes("HAS_DIRECTORY_ENTRY") !== newGuild.features.includes("HAS_DIRECTORY_ENTRY") + ) { + logs.push(`➕ Invites ${newGuild.features.includes("HAS_DIRECTORY_ENTRY") ? "" : "un"}paused`); + } + if ( + oldGuild.features.includes("INVITES_DISABLED") !== + newGuild.features.includes("INVITES_DISABLED") ) { logs.push( `Server ${ - newGuild.features.includes("HAS_DIRECTORY_ENTRY") ? "add" : "remov" + newGuild.features.includes("INVITES_DISABLED") ? "add" : "remov" }ed from a directory channel`, ); } diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 218f91660..337526ed8 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -107,9 +107,9 @@ export default async function event(interaction) { await command.interaction(interaction); } catch (error) { logError(error, interaction.toString()); - await interaction[ - interaction.replied ? "followUp" : interaction.deferred ? "editReply" : "reply" - ]({ + await (interaction.replied + ? interaction.followUp + : interaction[interaction.deferred ? "editReply" : "reply"])({ ephemeral: true, content: `${CONSTANTS.emojis.statuses.no} An error occurred.`, embeds: [], diff --git a/events/message/create.js b/events/message/create.js index a6f9d11c3..f94a76bd1 100644 --- a/events/message/create.js +++ b/events/message/create.js @@ -139,7 +139,7 @@ export default async function event(message) { } if ( - message.channel.type === ChannelType.GuildPublicThread && + message.channel.type === ChannelType.PublicThread && message.channel.parent?.id === CONSTANTS.channels.modmail?.id && !message.content.startsWith("=") && (message.webhookId && message.author.id !== client.user?.id @@ -209,7 +209,7 @@ export default async function event(message) { if (CONSTANTS.channels.modlogs?.id !== getBaseChannel(message.channel)?.id) { // eslint-disable-next-line no-irregular-whitespace -- This is intended. - const spoilerHack = "||​||".repeat(200); + const spoilerHack = "||​||".repeat(199); if (message.content.includes(spoilerHack)) { const array = message.cleanContent.split(spoilerHack); diff --git a/events/thread/update.js b/events/thread/update.js index 4b0710616..d4ed9d98e 100644 --- a/events/thread/update.js +++ b/events/thread/update.js @@ -1,4 +1,4 @@ -import { EmbedBuilder, ThreadAutoArchiveDuration } from "discord.js"; +import { EmbedBuilder, ThreadAutoArchiveDuration, ButtonBuilder, ButtonStyle } from "discord.js"; import { MODMAIL_COLORS, getUserFromModmail, @@ -11,6 +11,7 @@ import { badWordsAllowed, censor } from "../../common/moderation/automod.js"; import log, { LOG_GROUPS } from "../../common/moderation/logging.js"; import { DATABASE_THREAD } from "../../common/database.js"; import CONSTANTS from "../../common/CONSTANTS.js"; +import { MessageActionRowBuilder } from "../../common/types/ActionRowBuilder.js"; /** @type {import("../../common/types/event").default<"threadUpdate">} */ export default async function event(oldThread, newThread) { @@ -43,7 +44,25 @@ export default async function event(oldThread, newThread) { ` second${newThread.rateLimitPerUser === 1 ? "" : "s"}`, ); } - + newThread.appliedTags; // TODO + if (oldThread.flags.has("Pinned") !== newThread.flags.has("Pinned")) { + await log( + `📌 Post ${ + newThread.flags.has("Pinned") ? "" : "un" + }pinned in ${newThread.parent?.toString()}!`, + "messages", + { + components: [ + new MessageActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel("View Post") + .setStyle(ButtonStyle.Link) + .setURL(newThread.url), + ), + ], + }, + ); + } if ( newThread.archived && (newThread.name === DATABASE_THREAD || diff --git a/package-lock.json b/package-lock.json index 60dbf78b2..3503a277d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scradd", - "version": "2.0.3", + "version": "2.0.4-pre", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "scradd", - "version": "2.0.3", + "version": "2.0.4-pre", "license": "MIT", "dependencies": { "async-exit-hook": "2.0.1", diff --git a/package.json b/package.json index e7c17ad92..50d829e23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scradd", - "version": "2.0.3", + "version": "2.0.4-pre", "private": true, "description": "Custom Discord bot for the Scratch Addons server.", "homepage": "https://discord.gg/FPv957V6SD", diff --git a/tsconfig.json b/tsconfig.json index ef145d29b..6de51ffdc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "maxNodeModuleJsDepth": 0, "module": "ESNext", "moduleResolution": "Node", - "noEmitOnError": true, + "noEmitOnError": false, "noErrorTruncation": true, "noImplicitAny": true, "noImplicitOverride": true, diff --git a/util/discord.js b/util/discord.js index a7882470d..8566a1711 100644 --- a/util/discord.js +++ b/util/discord.js @@ -8,6 +8,7 @@ import { Message, MessageType, SelectMenuBuilder, + ChannelType, } from "discord.js"; import CONSTANTS from "../common/CONSTANTS.js"; import { escapeMessage, escapeLinks } from "./markdown.js"; @@ -18,7 +19,7 @@ import { MessageActionRowBuilder } from "../common/types/ActionRowBuilder.js"; /** * @param {import("discord.js").Message | import("discord.js").PartialMessage} message * - * @returns {Promise>>} + * @returns {Promise>>} */ export async function extractMessageExtremities(message, allowLanguage = true) { const embeds = [ @@ -182,8 +183,10 @@ export async function messageToText(message, replies = true) { return ( CONSTANTS.emojis.discord.edit + ` ${message.author.toString()} changed the ${ - // message.channel.type === ChannelType.GuildForum ? "post title" : - "channel name" + message.channel.isThread() && + message.channel.parent?.type === ChannelType.GuildForum + ? "post title" + : "channel name" }: **${escapeMessage(message.content)}**` ); } @@ -358,9 +361,7 @@ export async function reactAll(message, reactions) { * @param {(value: T, index: number, array: (T | undefined)[]) => string} toString * @param {string} failMessage * @param {string} title - * @param {( - * options: import("discord.js").WebhookEditMessageOptions, - * ) => Promise} reply + * @param {(options: import("discord.js").BaseMessageOptions) => Promise} reply */ export async function paginate(array, toString, failMessage, title, reply) { diff --git a/util/logError.js b/util/logError.js index 436381796..6ccf66274 100644 --- a/util/logError.js +++ b/util/logError.js @@ -18,7 +18,7 @@ export default async function logError(error, event) { "server", { files: [ - new AttachmentBuilder(Buffer.from(generateError(error), "utf-8"), { + new AttachmentBuilder(Buffer.from(generateError(error).toString(), "utf-8"), { name: "error.json", }), ], @@ -33,24 +33,28 @@ export default async function logError(error, event) { } /** + * - + * * @param {any} error + * @param {boolean} [returnObject] * - * @returns {string} + * @returns {Record | string} */ -const generateError = (error) => { +const generateError = (error, returnObject = false) => { if (typeof error === "object" || error.toString !== "function") { const serialized = serializeError(error); - return typeof serialized === "string" - ? serialized - : JSON.stringify( - { - ...serialized, - stack: sanitizePath(error.stack).split("\n"), - errors: error.errors?.map(generateError), - }, - undefined, - " ", - ); + + if (typeof serialized === "string") return serialized; + /** @type {unknown[]} */ + const subErrors = + "errors" in error && error.errors instanceof Array ? error.errors : undefined; + + const object = { + ...serialized, + stack: sanitizePath(error.stack).split("\n"), + errors: subErrors?.map((sub) => generateError(sub, true)), + }; + return returnObject ? object : JSON.stringify(object, null, " "); } return error.toString(); };