From 784317135d45c4d7cc6b11b52c82fc0b8a676bec Mon Sep 17 00:00:00 2001 From: Sandu Bogdan Date: Wed, 14 Jan 2026 11:16:51 +0200 Subject: [PATCH 1/8] Tried adding ReportToMods.ts --- src/app.ts | 21 ++++---- src/commands/ReportToMods.ts | 100 +++++++++++++++++++++++++++++++++++ src/config.json | 8 +-- 3 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 src/commands/ReportToMods.ts diff --git a/src/app.ts b/src/app.ts index 3681ffa..661e808 100644 --- a/src/app.ts +++ b/src/app.ts @@ -40,24 +40,15 @@ class App { @Schedule("*/5 * * * *") async reportHealth(): Promise { await axios.get(process.env.HEALTH_CHECK_URL!); + return; } async init(): Promise { - this.client.once("ready", async () => { - await this.client.initApplicationCommands(); - }); - await DirectoryUtils.getFilesInDirectory( `${__dirname}/${getConfigValue("commands_directory")}`, DirectoryUtils.appendFileExtension("Command") ); - this.client.on("interactionCreate", interaction => { - this.client.executeInteraction(interaction); - }); - - await this.client.login(process.env.DISCORD_TOKEN!); - const handlerFiles = await DirectoryUtils.getFilesInDirectory( `${__dirname}/${getConfigValue("handlers_directory")}`, DirectoryUtils.appendFileExtension("Handler") @@ -71,6 +62,16 @@ class App { this.client.on(handlerInstance.getEvent(), handlerInstance.handle); }); + this.client.once("ready", async () => { + await this.client.initApplicationCommands(); + }); + + this.client.on("interactionCreate", interaction => { + this.client.executeInteraction(interaction); + }); + + await this.client.login(process.env.DISCORD_TOKEN!); + if (process.env.NODE_ENV === getConfigValue("PRODUCTION_ENV")) { const channelSnowflake = getConfigValue("AUTHENTICATION_MESSAGE_CHANNEL"); const messageSnowflake = getConfigValue("AUTHENTICATION_MESSAGE_ID"); diff --git a/src/commands/ReportToMods.ts b/src/commands/ReportToMods.ts new file mode 100644 index 0000000..6e7886a --- /dev/null +++ b/src/commands/ReportToMods.ts @@ -0,0 +1,100 @@ +import {ApplicationCommandType, EmbedBuilder, MessageContextMenuCommandInteraction, ChannelType, TextChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction } from "discord.js"; +import { ContextMenu, Discord, ButtonComponent } from "discordx"; +import getConfigValue from "../utils/getConfigValue"; +//import GenericObject from "../interfaces/GenericObject"; +//import App from "../app"; +//import { log } from "console"; +//import { channel } from "diagnostics_channel"; + +@Discord() +class ReportToMods { + @ContextMenu({name: "Flag to moderators", type: ApplicationCommandType.Message, guilds: ["1213170850015084594"]}) + async onContext(interaction: MessageContextMenuCommandInteraction): Promise { + const logChannelId: string = getConfigValue("LOG_CHANNEL_ID"); + + const logChannelRaw = await interaction.client.channels.fetch(logChannelId); + + if (!logChannelRaw || logChannelRaw.type !== ChannelType.GuildText) { + console.error("Invalid logChannel instance"); + } + + const logChannel = logChannelRaw as TextChannel; + + const reportedMessage = interaction.targetMessage; + const messageLink = `https://discord.com/channels/${reportedMessage.guildId}/${reportedMessage.channelId}/${reportedMessage.id}`; + + const embed = new EmbedBuilder() + .setTitle("Message flagged to moderators") + .setDescription(reportedMessage.content.replace('*', '\\*') || "[*No message content*]") + .addFields([ + { name: "Author", value: `<@${reportedMessage.author.id}>`, inline: true }, + { name: "Channel", value: `<#${reportedMessage.channelId}>`, inline: true }, + { name: "Message Link", value: messageLink, inline: true } + ]); + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`report:approve:${reportedMessage.channelId}:${reportedMessage.id}`) + .setLabel("Approve") + .setStyle(ButtonStyle.Success), + + new ButtonBuilder() + .setCustomId(`report:reject:${reportedMessage.channelId}:${reportedMessage.id}`) + .setLabel("Reject") + .setStyle(ButtonStyle.Danger) + ); + + await logChannel.send({ embeds: [embed], components: [row] }); + + await interaction.reply({ + content: "Message flagged to moderators.", + ephemeral: true + }); + } + + @ButtonComponent({ id: /^report:(approve|reject):\d+$/ }) + async onButton(interaction: ButtonInteraction): Promise { + const modRoleId = getConfigValue("MOD_ROLE"); + + const roles = interaction.member?.roles; + + const hasRole = Array.isArray(roles) + ? roles?.includes(modRoleId) + : roles?.cache.has(modRoleId); + + if (!hasRole) { + await interaction.reply({ + content: "You do not have permission to perform this action.", + ephemeral: true + }); + return; + } + + const [, action, channelId, messageId] = interaction.customId.split(":"); + + if (action === "approve") { + const channel = await interaction.client.channels.fetch(channelId); + + if (!channel || !channel.isTextBased()) { + await interaction.reply({ + content: "Cannot resolve channel.", + ephemeral: true + }); + return; + } + + const message = await channel.messages.fetch(messageId); + await message.delete(); + } + + await interaction.update({ + content: + action === "approve" + ? `✅ Report approved by <@${interaction.user.id}>` + : `❌ Report rejected by <@${interaction.user.id}>`, + components: [] // disable buttons + }); + } +} + +export default ReportToMods; diff --git a/src/config.json b/src/config.json index 2c52ee0..61e1b33 100644 --- a/src/config.json +++ b/src/config.json @@ -1,10 +1,10 @@ { - "GUILD_ID": "240880736851329024", - "BOT_ID": "545281816026677258", + "GUILD_ID": "1213170850015084594", + "BOT_ID": "1097888956244316242", "COMMAND_PREFIX": "?", "MEMBER_ROLE": "592088198746996768", "REGULAR_ROLE": "700614448846733402", - "MOD_ROLE": "490594428549857281", + "MOD_ROLE": "1255503137851179008", "REGULAR_ROLE_CHANGE_CHANNEL": "1241698863664988182", "SHOWCASE_CHANNEL_ID": "240892912186032129", "AUTHENTICATION_MESSAGE_ID": "592316062796873738", @@ -21,7 +21,7 @@ "DEVELOPMENT_ENV": "dev", "commands_directory": "commands", "handlers_directory": "event/handlers", - "LOG_CHANNEL_ID": "405068878151024640", + "LOG_CHANNEL_ID": "1213170850740834336", "MOD_CHANNEL_ID": "495713774205141022", "GENERAL_CHANNEL_ID": "518817917438001152", "ANTISPAM_CHANNEL_ID": "1416375771727138929", From db001d53756ab7e111ff85f2c9d2c7cde441e724 Mon Sep 17 00:00:00 2001 From: Sandu Bogdan Date: Tue, 20 Jan 2026 13:23:40 +0200 Subject: [PATCH 2/8] Fixed Context Menu Command and added tests --- ...ReportToMods.ts => ReportToModsCommand.ts} | 17 +- test/commands/ReportToModsCommandTest.ts | 165 ++++++++++++++++++ 2 files changed, 176 insertions(+), 6 deletions(-) rename src/commands/{ReportToMods.ts => ReportToModsCommand.ts} (85%) create mode 100644 test/commands/ReportToModsCommandTest.ts diff --git a/src/commands/ReportToMods.ts b/src/commands/ReportToModsCommand.ts similarity index 85% rename from src/commands/ReportToMods.ts rename to src/commands/ReportToModsCommand.ts index 6e7886a..99a94c9 100644 --- a/src/commands/ReportToMods.ts +++ b/src/commands/ReportToModsCommand.ts @@ -1,6 +1,7 @@ -import {ApplicationCommandType, EmbedBuilder, MessageContextMenuCommandInteraction, ChannelType, TextChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction } from "discord.js"; +import {ApplicationCommandType, EmbedBuilder, MessageContextMenuCommandInteraction, ChannelType, TextChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonInteraction, ColorResolvable } from "discord.js"; import { ContextMenu, Discord, ButtonComponent } from "discordx"; import getConfigValue from "../utils/getConfigValue"; +import GenericObject from "../interfaces/GenericObject"; //import GenericObject from "../interfaces/GenericObject"; //import App from "../app"; //import { log } from "console"; @@ -16,6 +17,7 @@ class ReportToMods { if (!logChannelRaw || logChannelRaw.type !== ChannelType.GuildText) { console.error("Invalid logChannel instance"); + return; } const logChannel = logChannelRaw as TextChannel; @@ -25,12 +27,15 @@ class ReportToMods { const embed = new EmbedBuilder() .setTitle("Message flagged to moderators") - .setDescription(reportedMessage.content.replace('*', '\\*') || "[*No message content*]") + .setDescription(reportedMessage.content.replace(/[*_~`]/g, "\\$&") || "[*No message content*]") .addFields([ { name: "Author", value: `<@${reportedMessage.author.id}>`, inline: true }, { name: "Channel", value: `<#${reportedMessage.channelId}>`, inline: true }, { name: "Message Link", value: messageLink, inline: true } - ]); + ] + ); + + embed.setColor(getConfigValue>("EMBED_COLOURS").DEFAULT) const row = new ActionRowBuilder().addComponents( new ButtonBuilder() @@ -47,12 +52,12 @@ class ReportToMods { await logChannel.send({ embeds: [embed], components: [row] }); await interaction.reply({ - content: "Message flagged to moderators.", + content: "Message flagged to moderators. They will review the reported content as soon as possible.", ephemeral: true }); } - @ButtonComponent({ id: /^report:(approve|reject):\d+$/ }) + @ButtonComponent({ id: /^report:(approve|reject):\d+:\d+$/ }) async onButton(interaction: ButtonInteraction): Promise { const modRoleId = getConfigValue("MOD_ROLE"); @@ -92,7 +97,7 @@ class ReportToMods { action === "approve" ? `✅ Report approved by <@${interaction.user.id}>` : `❌ Report rejected by <@${interaction.user.id}>`, - components: [] // disable buttons + components: [] }); } } diff --git a/test/commands/ReportToModsCommandTest.ts b/test/commands/ReportToModsCommandTest.ts new file mode 100644 index 0000000..17d77f1 --- /dev/null +++ b/test/commands/ReportToModsCommandTest.ts @@ -0,0 +1,165 @@ +import { expect } from "chai"; +import { createSandbox, SinonSandbox } from "sinon"; +import ReportToMods from "../../src/commands/ReportToModsCommand"; +import { ChannelType } from "discord.js"; + +describe("ReportToMods", () => { + let sandbox: SinonSandbox; + let command: ReportToMods; + let getConfigStub: sinon.SinonStub; + + beforeEach(() => { + sandbox = createSandbox(); + command = new ReportToMods(); + + // ✅ SINGLE stub for getConfigValue + getConfigStub = sandbox.stub( + require("../../src/utils/getConfigValue"), + "default" + ); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe("onContext()", () => { + it("sends an embed to the log channel and replies ephemerally", async () => { + const sendStub = sandbox.stub().resolves(); + const replyStub = sandbox.stub().resolves(); + + getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); + getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); + + const interaction: any = { + targetMessage: { + id: "456", + channelId: "123", + guildId: "789", + content: "Test *message*", + author: { id: "111" } + }, + client: { + channels: { + fetch: sandbox.stub().resolves({ + type: ChannelType.GuildText, + send: sendStub + }) + } + }, + reply: replyStub + }; + + await command.onContext(interaction); + + expect(sendStub.calledOnce).to.be.true; + expect(replyStub.calledOnce).to.be.true; + + const embed = sendStub.getCall(0).args[0].embeds[0]; + expect(embed.data.title).to.equal("Message flagged to moderators"); + expect(embed.data.description).to.equal("Test \\*message\\*"); + }); + + it("returns early if log channel is invalid", async () => { + getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); + getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); + + const fetchStub = sandbox.stub().resolves(null); + + const interaction: any = { + targetMessage: {}, + client: { + channels: { + fetch: fetchStub + } + }, + reply: sandbox.stub() + }; + + await command.onContext(interaction); + + expect(fetchStub.calledOnce).to.be.true; + expect(interaction.reply.called).to.be.false; + }); + }); + + describe("onButton()", () => { + it("rejects users without the mod role", async () => { + getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); + + const replyStub = sandbox.stub().resolves(); + + const interaction: any = { + customId: "report:approve:123:456", + user: { id: "999" }, + member: { + roles: { + cache: new Map() + } + }, + reply: replyStub + }; + + await command.onButton(interaction); + + expect(replyStub.calledOnce).to.be.true; + expect(replyStub.getCall(0).args[0].ephemeral).to.be.true; + }); + + it("deletes the message when approved by a mod", async () => { + getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); + + const deleteStub = sandbox.stub().resolves(); + const fetchMessageStub = sandbox.stub().resolves({ delete: deleteStub }); + const updateStub = sandbox.stub().resolves(); + + const interaction: any = { + customId: "report:approve:123:456", + user: { id: "999" }, + member: { + roles: { + cache: new Map([["MOD_ROLE_ID", true]]) + } + }, + client: { + channels: { + fetch: sandbox.stub().resolves({ + isTextBased: () => true, + messages: { + fetch: fetchMessageStub + } + }) + } + }, + update: updateStub + }; + + await command.onButton(interaction); + + expect(fetchMessageStub.calledOnceWith("456")).to.be.true; + expect(deleteStub.calledOnce).to.be.true; + expect(updateStub.calledOnce).to.be.true; + }); + + it("does not delete the message when rejected", async () => { + getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); + + const updateStub = sandbox.stub().resolves(); + + const interaction: any = { + customId: "report:reject:123:456", + user: { id: "999" }, + member: { + roles: { + cache: new Map([["MOD_ROLE_ID", true]]) + } + }, + update: updateStub + }; + + await command.onButton(interaction); + + expect(updateStub.calledOnce).to.be.true; + }); + }); +}); From cc37551180cd3f04e96ad9a2c669d1103e26bdf3 Mon Sep 17 00:00:00 2001 From: Sandu Bogdan Date: Tue, 20 Jan 2026 13:31:42 +0200 Subject: [PATCH 3/8] Made Flag to Mods ping @Moderator + Updated Tests --- src/commands/ReportToModsCommand.ts | 3 + src/config.json | 10 +- test/commands/ReportToModsCommandTest.ts | 118 ++++++++++++----------- 3 files changed, 71 insertions(+), 60 deletions(-) diff --git a/src/commands/ReportToModsCommand.ts b/src/commands/ReportToModsCommand.ts index 99a94c9..2b112e5 100644 --- a/src/commands/ReportToModsCommand.ts +++ b/src/commands/ReportToModsCommand.ts @@ -2,6 +2,7 @@ import {ApplicationCommandType, EmbedBuilder, MessageContextMenuCommandInteracti import { ContextMenu, Discord, ButtonComponent } from "discordx"; import getConfigValue from "../utils/getConfigValue"; import GenericObject from "../interfaces/GenericObject"; +import { log } from "console"; //import GenericObject from "../interfaces/GenericObject"; //import App from "../app"; //import { log } from "console"; @@ -35,6 +36,8 @@ class ReportToMods { ] ); + await logChannel.send("<@" + getConfigValue("MOD_ROLE") + ">"); + embed.setColor(getConfigValue>("EMBED_COLOURS").DEFAULT) const row = new ActionRowBuilder().addComponents( diff --git a/src/config.json b/src/config.json index 61e1b33..d1a2af9 100644 --- a/src/config.json +++ b/src/config.json @@ -1,10 +1,10 @@ { - "GUILD_ID": "1213170850015084594", - "BOT_ID": "1097888956244316242", + "GUILD_ID": "240880736851329024", + "BOT_ID": "545281816026677258", "COMMAND_PREFIX": "?", "MEMBER_ROLE": "592088198746996768", "REGULAR_ROLE": "700614448846733402", - "MOD_ROLE": "1255503137851179008", + "MOD_ROLE": "490594428549857281", "REGULAR_ROLE_CHANGE_CHANNEL": "1241698863664988182", "SHOWCASE_CHANNEL_ID": "240892912186032129", "AUTHENTICATION_MESSAGE_ID": "592316062796873738", @@ -21,7 +21,7 @@ "DEVELOPMENT_ENV": "dev", "commands_directory": "commands", "handlers_directory": "event/handlers", - "LOG_CHANNEL_ID": "1213170850740834336", + "LOG_CHANNEL_ID": "405068878151024640", "MOD_CHANNEL_ID": "495713774205141022", "GENERAL_CHANNEL_ID": "518817917438001152", "ANTISPAM_CHANNEL_ID": "1416375771727138929", @@ -106,4 +106,4 @@ "description": "Keep it appropriate, some people use this at school or at work." } ] -} +} \ No newline at end of file diff --git a/test/commands/ReportToModsCommandTest.ts b/test/commands/ReportToModsCommandTest.ts index 17d77f1..0916f36 100644 --- a/test/commands/ReportToModsCommandTest.ts +++ b/test/commands/ReportToModsCommandTest.ts @@ -24,63 +24,71 @@ describe("ReportToMods", () => { }); describe("onContext()", () => { - it("sends an embed to the log channel and replies ephemerally", async () => { - const sendStub = sandbox.stub().resolves(); - const replyStub = sandbox.stub().resolves(); - - getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); - getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); - - const interaction: any = { - targetMessage: { - id: "456", - channelId: "123", - guildId: "789", - content: "Test *message*", - author: { id: "111" } - }, - client: { - channels: { - fetch: sandbox.stub().resolves({ - type: ChannelType.GuildText, - send: sendStub - }) - } - }, - reply: replyStub - }; - - await command.onContext(interaction); - - expect(sendStub.calledOnce).to.be.true; - expect(replyStub.calledOnce).to.be.true; - - const embed = sendStub.getCall(0).args[0].embeds[0]; - expect(embed.data.title).to.equal("Message flagged to moderators"); - expect(embed.data.description).to.equal("Test \\*message\\*"); - }); + it("sends a role ping, an embed to the log channel, and replies ephemerally", async () => { + const sendStub = sandbox.stub().resolves(); + const replyStub = sandbox.stub().resolves(); + + getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); + getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); + getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); + + const interaction: any = { + targetMessage: { + id: "456", + channelId: "123", + guildId: "789", + content: "Test *message*", + author: { id: "111" } + }, + client: { + channels: { + fetch: sandbox.stub().resolves({ + type: ChannelType.GuildText, + send: sendStub + }) + } + }, + reply: replyStub + }; + + await command.onContext(interaction); + + // 🔹 send called twice now + expect(sendStub.callCount).to.equal(2); + + // 🔹 first call = role ping + expect(sendStub.getCall(0).args[0]).to.equal("<@MOD_ROLE_ID>"); + + // 🔹 second call = embed payload + const embed = sendStub.getCall(1).args[0].embeds[0]; + expect(embed.data.title).to.equal("Message flagged to moderators"); + expect(embed.data.description).to.equal("Test \\*message\\*"); + + expect(replyStub.calledOnce).to.be.true; + }); it("returns early if log channel is invalid", async () => { - getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); - getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); - - const fetchStub = sandbox.stub().resolves(null); - - const interaction: any = { - targetMessage: {}, - client: { - channels: { - fetch: fetchStub - } - }, - reply: sandbox.stub() - }; - - await command.onContext(interaction); - - expect(fetchStub.calledOnce).to.be.true; - expect(interaction.reply.called).to.be.false; - }); + getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); + getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); + getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); + + const fetchStub = sandbox.stub().resolves(null); + + const interaction: any = { + targetMessage: {}, + client: { + channels: { + fetch: fetchStub + } + }, + reply: sandbox.stub() + }; + + await command.onContext(interaction); + + expect(fetchStub.calledOnce).to.be.true; + expect(interaction.reply.called).to.be.false; + }); }); describe("onButton()", () => { From 74c349462d896c7bc1a4dce8e764daa0bf877ddf Mon Sep 17 00:00:00 2001 From: Sandu Bogdan Date: Tue, 20 Jan 2026 13:42:50 +0200 Subject: [PATCH 4/8] Linting fixes --- src/commands/ReportToModsCommand.ts | 56 +++++----- test/appTest.ts | 6 +- test/commands/ReportToModsCommandTest.ts | 131 +++++++++++------------ 3 files changed, 97 insertions(+), 96 deletions(-) diff --git a/src/commands/ReportToModsCommand.ts b/src/commands/ReportToModsCommand.ts index 2b112e5..acf16a3 100644 --- a/src/commands/ReportToModsCommand.ts +++ b/src/commands/ReportToModsCommand.ts @@ -2,11 +2,10 @@ import {ApplicationCommandType, EmbedBuilder, MessageContextMenuCommandInteracti import { ContextMenu, Discord, ButtonComponent } from "discordx"; import getConfigValue from "../utils/getConfigValue"; import GenericObject from "../interfaces/GenericObject"; -import { log } from "console"; -//import GenericObject from "../interfaces/GenericObject"; -//import App from "../app"; -//import { log } from "console"; -//import { channel } from "diagnostics_channel"; +// Import GenericObject from "../interfaces/GenericObject"; +// Import App from "../app"; +// Import { log } from "console"; +// Import { channel } from "diagnostics_channel"; @Discord() class ReportToMods { @@ -27,18 +26,18 @@ class ReportToMods { const messageLink = `https://discord.com/channels/${reportedMessage.guildId}/${reportedMessage.channelId}/${reportedMessage.id}`; const embed = new EmbedBuilder() - .setTitle("Message flagged to moderators") - .setDescription(reportedMessage.content.replace(/[*_~`]/g, "\\$&") || "[*No message content*]") - .addFields([ - { name: "Author", value: `<@${reportedMessage.author.id}>`, inline: true }, - { name: "Channel", value: `<#${reportedMessage.channelId}>`, inline: true }, - { name: "Message Link", value: messageLink, inline: true } - ] - ); + .setTitle("Message flagged to moderators") + .setDescription(reportedMessage.content.replace(/[*_~`]/g, "\\$&") || "[*No message content*]") + .addFields([ + { name: "Author", value: `<@${reportedMessage.author.id}>`, inline: true }, + { name: "Channel", value: `<#${reportedMessage.channelId}>`, inline: true }, + { name: "Message Link", value: messageLink, inline: true } + ] + ); - await logChannel.send("<@" + getConfigValue("MOD_ROLE") + ">"); + await logChannel.send(`<@${getConfigValue("MOD_ROLE")}>`); - embed.setColor(getConfigValue>("EMBED_COLOURS").DEFAULT) + embed.setColor(getConfigValue>("EMBED_COLOURS").DEFAULT); const row = new ActionRowBuilder().addComponents( new ButtonBuilder() @@ -63,21 +62,21 @@ class ReportToMods { @ButtonComponent({ id: /^report:(approve|reject):\d+:\d+$/ }) async onButton(interaction: ButtonInteraction): Promise { const modRoleId = getConfigValue("MOD_ROLE"); - + const roles = interaction.member?.roles; - + const hasRole = Array.isArray(roles) ? roles?.includes(modRoleId) : roles?.cache.has(modRoleId); if (!hasRole) { - await interaction.reply({ - content: "You do not have permission to perform this action.", - ephemeral: true - }); - return; + await interaction.reply({ + content: "You do not have permission to perform this action.", + ephemeral: true + }); + return; } - + const [, action, channelId, messageId] = interaction.customId.split(":"); if (action === "approve") { @@ -92,15 +91,16 @@ class ReportToMods { } const message = await channel.messages.fetch(messageId); + await message.delete(); } - + await interaction.update({ - content: + content: action === "approve" - ? `✅ Report approved by <@${interaction.user.id}>` - : `❌ Report rejected by <@${interaction.user.id}>`, - components: [] + ? `✅ Report approved by <@${interaction.user.id}>` + : `❌ Report rejected by <@${interaction.user.id}>`, + components: [] }); } } diff --git a/test/appTest.ts b/test/appTest.ts index e8d007d..0d03dcb 100644 --- a/test/appTest.ts +++ b/test/appTest.ts @@ -21,7 +21,9 @@ describe("App", () => { sandbox = createSandbox(); // @ts-ignore - (axios as unknown as AxiosCacheInstance).defaults.cache = undefined; + const axiosCache = axios as unknown as AxiosCacheInstance; + + axiosCache.defaults.cache = undefined; loginStub = sandbox.stub(Client.prototype, "login"); getStub = sandbox.stub(axios, "get").resolves(); @@ -115,4 +117,4 @@ describe("App", () => { afterEach(() => { sandbox.restore(); }); -}); +}); \ No newline at end of file diff --git a/test/commands/ReportToModsCommandTest.ts b/test/commands/ReportToModsCommandTest.ts index 0916f36..ac8074f 100644 --- a/test/commands/ReportToModsCommandTest.ts +++ b/test/commands/ReportToModsCommandTest.ts @@ -2,6 +2,7 @@ import { expect } from "chai"; import { createSandbox, SinonSandbox } from "sinon"; import ReportToMods from "../../src/commands/ReportToModsCommand"; import { ChannelType } from "discord.js"; +import * as config from "../../src/utils/getConfigValue"; describe("ReportToMods", () => { let sandbox: SinonSandbox; @@ -13,10 +14,7 @@ describe("ReportToMods", () => { command = new ReportToMods(); // ✅ SINGLE stub for getConfigValue - getConfigStub = sandbox.stub( - require("../../src/utils/getConfigValue"), - "default" - ); + getConfigStub = sandbox.stub(config, "default"); }); afterEach(() => { @@ -25,70 +23,71 @@ describe("ReportToMods", () => { describe("onContext()", () => { it("sends a role ping, an embed to the log channel, and replies ephemerally", async () => { - const sendStub = sandbox.stub().resolves(); - const replyStub = sandbox.stub().resolves(); - - getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); - getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); - getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); - - const interaction: any = { - targetMessage: { - id: "456", - channelId: "123", - guildId: "789", - content: "Test *message*", - author: { id: "111" } - }, - client: { - channels: { - fetch: sandbox.stub().resolves({ - type: ChannelType.GuildText, - send: sendStub - }) - } - }, - reply: replyStub - }; - - await command.onContext(interaction); - - // 🔹 send called twice now - expect(sendStub.callCount).to.equal(2); - - // 🔹 first call = role ping - expect(sendStub.getCall(0).args[0]).to.equal("<@MOD_ROLE_ID>"); - - // 🔹 second call = embed payload - const embed = sendStub.getCall(1).args[0].embeds[0]; - expect(embed.data.title).to.equal("Message flagged to moderators"); - expect(embed.data.description).to.equal("Test \\*message\\*"); - - expect(replyStub.calledOnce).to.be.true; - }); + const sendStub = sandbox.stub().resolves(); + const replyStub = sandbox.stub().resolves(); + + getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); + getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); + getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); + + const interaction: any = { + targetMessage: { + id: "456", + channelId: "123", + guildId: "789", + content: "Test *message*", + author: { id: "111" } + }, + client: { + channels: { + fetch: sandbox.stub().resolves({ + type: ChannelType.GuildText, + send: sendStub + }) + } + }, + reply: replyStub + }; + + await command.onContext(interaction); + + // 🔹 send called twice now + expect(sendStub.callCount).to.equal(2); + + // 🔹 first call = role ping + expect(sendStub.getCall(0).args[0]).to.equal("<@MOD_ROLE_ID>"); + + // 🔹 second call = embed payload + const embed = sendStub.getCall(1).args[0].embeds[0]; + + expect(embed.data.title).to.equal("Message flagged to moderators"); + expect(embed.data.description).to.equal("Test \\*message\\*"); + + expect(replyStub.calledOnce).to.be.true; + }); it("returns early if log channel is invalid", async () => { - getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); - getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); - getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); - - const fetchStub = sandbox.stub().resolves(null); - - const interaction: any = { - targetMessage: {}, - client: { - channels: { - fetch: fetchStub - } - }, - reply: sandbox.stub() - }; - - await command.onContext(interaction); - - expect(fetchStub.calledOnce).to.be.true; - expect(interaction.reply.called).to.be.false; - }); + getConfigStub.withArgs("LOG_CHANNEL_ID").returns("LOG_CHANNEL_ID"); + getConfigStub.withArgs("MOD_ROLE").returns("MOD_ROLE_ID"); + getConfigStub.withArgs("EMBED_COLOURS").returns({ DEFAULT: "#ffffff" }); + + const fetchStub = sandbox.stub().resolves(null); + + const interaction: any = { + targetMessage: {}, + client: { + channels: { + fetch: fetchStub + } + }, + reply: sandbox.stub() + }; + + await command.onContext(interaction); + + expect(fetchStub.calledOnce).to.be.true; + expect(interaction.reply.called).to.be.false; + }); }); describe("onButton()", () => { From 7373dbd587d7d9464b8ee52dc586e510c5725f11 Mon Sep 17 00:00:00 2001 From: Sandu Bogdan Date: Tue, 20 Jan 2026 13:50:14 +0200 Subject: [PATCH 5/8] . --- src/commands/ReportToModsCommand.ts | 3 ++- src/config.json | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/commands/ReportToModsCommand.ts b/src/commands/ReportToModsCommand.ts index acf16a3..2c22c9d 100644 --- a/src/commands/ReportToModsCommand.ts +++ b/src/commands/ReportToModsCommand.ts @@ -31,7 +31,8 @@ class ReportToMods { .addFields([ { name: "Author", value: `<@${reportedMessage.author.id}>`, inline: true }, { name: "Channel", value: `<#${reportedMessage.channelId}>`, inline: true }, - { name: "Message Link", value: messageLink, inline: true } + { name: "Message Link", value: messageLink, inline: true }, + { name: "The person reporting", value: `<@${interaction.user.id}>`} ] ); diff --git a/src/config.json b/src/config.json index d1a2af9..61e1b33 100644 --- a/src/config.json +++ b/src/config.json @@ -1,10 +1,10 @@ { - "GUILD_ID": "240880736851329024", - "BOT_ID": "545281816026677258", + "GUILD_ID": "1213170850015084594", + "BOT_ID": "1097888956244316242", "COMMAND_PREFIX": "?", "MEMBER_ROLE": "592088198746996768", "REGULAR_ROLE": "700614448846733402", - "MOD_ROLE": "490594428549857281", + "MOD_ROLE": "1255503137851179008", "REGULAR_ROLE_CHANGE_CHANNEL": "1241698863664988182", "SHOWCASE_CHANNEL_ID": "240892912186032129", "AUTHENTICATION_MESSAGE_ID": "592316062796873738", @@ -21,7 +21,7 @@ "DEVELOPMENT_ENV": "dev", "commands_directory": "commands", "handlers_directory": "event/handlers", - "LOG_CHANNEL_ID": "405068878151024640", + "LOG_CHANNEL_ID": "1213170850740834336", "MOD_CHANNEL_ID": "495713774205141022", "GENERAL_CHANNEL_ID": "518817917438001152", "ANTISPAM_CHANNEL_ID": "1416375771727138929", @@ -106,4 +106,4 @@ "description": "Keep it appropriate, some people use this at school or at work." } ] -} \ No newline at end of file +} From 4a01ef066dd114fc97d24294365e7fba8b6bba35 Mon Sep 17 00:00:00 2001 From: Sandu Bogdan Date: Tue, 20 Jan 2026 14:00:59 +0200 Subject: [PATCH 6/8] Fixed mod role pings --- src/commands/ReportToModsCommand.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/ReportToModsCommand.ts b/src/commands/ReportToModsCommand.ts index 2c22c9d..ce42554 100644 --- a/src/commands/ReportToModsCommand.ts +++ b/src/commands/ReportToModsCommand.ts @@ -36,7 +36,7 @@ class ReportToMods { ] ); - await logChannel.send(`<@${getConfigValue("MOD_ROLE")}>`); + await logChannel.send(`<@&${getConfigValue("MOD_ROLE")}>`); embed.setColor(getConfigValue>("EMBED_COLOURS").DEFAULT); From 9de9eaea503b67fdaddd1aa198e40bd48637a8de Mon Sep 17 00:00:00 2001 From: Sandu Bogdan Date: Tue, 20 Jan 2026 14:01:29 +0200 Subject: [PATCH 7/8] Reverted config --- src/config.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config.json b/src/config.json index 61e1b33..d1a2af9 100644 --- a/src/config.json +++ b/src/config.json @@ -1,10 +1,10 @@ { - "GUILD_ID": "1213170850015084594", - "BOT_ID": "1097888956244316242", + "GUILD_ID": "240880736851329024", + "BOT_ID": "545281816026677258", "COMMAND_PREFIX": "?", "MEMBER_ROLE": "592088198746996768", "REGULAR_ROLE": "700614448846733402", - "MOD_ROLE": "1255503137851179008", + "MOD_ROLE": "490594428549857281", "REGULAR_ROLE_CHANGE_CHANNEL": "1241698863664988182", "SHOWCASE_CHANNEL_ID": "240892912186032129", "AUTHENTICATION_MESSAGE_ID": "592316062796873738", @@ -21,7 +21,7 @@ "DEVELOPMENT_ENV": "dev", "commands_directory": "commands", "handlers_directory": "event/handlers", - "LOG_CHANNEL_ID": "1213170850740834336", + "LOG_CHANNEL_ID": "405068878151024640", "MOD_CHANNEL_ID": "495713774205141022", "GENERAL_CHANNEL_ID": "518817917438001152", "ANTISPAM_CHANNEL_ID": "1416375771727138929", @@ -106,4 +106,4 @@ "description": "Keep it appropriate, some people use this at school or at work." } ] -} +} \ No newline at end of file From 00ab357e5d773a4d195d40e34aabf8ba5eb038b1 Mon Sep 17 00:00:00 2001 From: Sandu Bogdan Date: Tue, 20 Jan 2026 14:31:51 +0200 Subject: [PATCH 8/8] Added content truncation to 100 characters max. --- src/commands/ReportToModsCommand.ts | 9 ++++++++- src/config.json | 10 +++++----- test/commands/ReportToModsCommandTest.ts | 8 +++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/commands/ReportToModsCommand.ts b/src/commands/ReportToModsCommand.ts index ce42554..284569d 100644 --- a/src/commands/ReportToModsCommand.ts +++ b/src/commands/ReportToModsCommand.ts @@ -25,9 +25,16 @@ class ReportToMods { const reportedMessage = interaction.targetMessage; const messageLink = `https://discord.com/channels/${reportedMessage.guildId}/${reportedMessage.channelId}/${reportedMessage.id}`; + const rawContent = reportedMessage.content.replace(/[*_~`]/g, "\\$&") || "[*No message content*]"; + + const truncatedContent = + rawContent.length > 100 + ? rawContent.slice(0, 100) + "…" + : rawContent; + const embed = new EmbedBuilder() .setTitle("Message flagged to moderators") - .setDescription(reportedMessage.content.replace(/[*_~`]/g, "\\$&") || "[*No message content*]") + .setDescription(truncatedContent) .addFields([ { name: "Author", value: `<@${reportedMessage.author.id}>`, inline: true }, { name: "Channel", value: `<#${reportedMessage.channelId}>`, inline: true }, diff --git a/src/config.json b/src/config.json index d1a2af9..61e1b33 100644 --- a/src/config.json +++ b/src/config.json @@ -1,10 +1,10 @@ { - "GUILD_ID": "240880736851329024", - "BOT_ID": "545281816026677258", + "GUILD_ID": "1213170850015084594", + "BOT_ID": "1097888956244316242", "COMMAND_PREFIX": "?", "MEMBER_ROLE": "592088198746996768", "REGULAR_ROLE": "700614448846733402", - "MOD_ROLE": "490594428549857281", + "MOD_ROLE": "1255503137851179008", "REGULAR_ROLE_CHANGE_CHANNEL": "1241698863664988182", "SHOWCASE_CHANNEL_ID": "240892912186032129", "AUTHENTICATION_MESSAGE_ID": "592316062796873738", @@ -21,7 +21,7 @@ "DEVELOPMENT_ENV": "dev", "commands_directory": "commands", "handlers_directory": "event/handlers", - "LOG_CHANNEL_ID": "405068878151024640", + "LOG_CHANNEL_ID": "1213170850740834336", "MOD_CHANNEL_ID": "495713774205141022", "GENERAL_CHANNEL_ID": "518817917438001152", "ANTISPAM_CHANNEL_ID": "1416375771727138929", @@ -106,4 +106,4 @@ "description": "Keep it appropriate, some people use this at school or at work." } ] -} \ No newline at end of file +} diff --git a/test/commands/ReportToModsCommandTest.ts b/test/commands/ReportToModsCommandTest.ts index ac8074f..53130ff 100644 --- a/test/commands/ReportToModsCommandTest.ts +++ b/test/commands/ReportToModsCommandTest.ts @@ -46,6 +46,9 @@ describe("ReportToMods", () => { }) } }, + user: { + id: "190" + }, reply: replyStub }; @@ -55,7 +58,7 @@ describe("ReportToMods", () => { expect(sendStub.callCount).to.equal(2); // 🔹 first call = role ping - expect(sendStub.getCall(0).args[0]).to.equal("<@MOD_ROLE_ID>"); + expect(sendStub.getCall(0).args[0]).to.equal("<@&MOD_ROLE_ID>"); // 🔹 second call = embed payload const embed = sendStub.getCall(1).args[0].embeds[0]; @@ -80,6 +83,9 @@ describe("ReportToMods", () => { fetch: fetchStub } }, + user: { + id: "190" + }, reply: sandbox.stub() };