From 0394d0336144b3139012fe2cf52cf5b3b4e664bb Mon Sep 17 00:00:00 2001 From: Teklu Date: Mon, 16 Feb 2026 19:48:27 +0900 Subject: [PATCH 1/4] feat: add owner-only message context menu to forward messages to DMs --- src/commands/forwardToOwner.ts | 72 ++++++++++++ src/config/ids.ts | 3 +- src/index.ts | 205 ++++++++++++++++++--------------- src/scripts/deploy-global.ts | 32 +++++ 4 files changed, 215 insertions(+), 97 deletions(-) create mode 100644 src/commands/forwardToOwner.ts create mode 100644 src/scripts/deploy-global.ts diff --git a/src/commands/forwardToOwner.ts b/src/commands/forwardToOwner.ts new file mode 100644 index 0000000..2166985 --- /dev/null +++ b/src/commands/forwardToOwner.ts @@ -0,0 +1,72 @@ +import { + ContextMenuCommandBuilder, + ApplicationCommandType, + type MessageContextMenuCommandInteraction, + DiscordAPIError, + ButtonBuilder, + EmbedBuilder, + ActionRowBuilder, + ButtonStyle, +} from "discord.js"; +import { ids } from "../config/ids.js"; + +export const forwardOwnerDM = { + data: new ContextMenuCommandBuilder() + .setName("Forward to Naomi") + .setType(ApplicationCommandType.Message), + + async execute(interaction: MessageContextMenuCommandInteraction) { + await interaction.deferReply({ ephemeral: true }); + + if (interaction.user.id !== ids.users.naomi) { + await interaction.editReply("❌ Only Naomi can use this command."); + return; + } + + const message = interaction.targetMessage; + + if (message.author.id === ids.users.naomi) { + await interaction.editReply("No need to forward your own message to yourself 😄"); + return; + } + + try { + const naomi = await interaction.client.users.fetch(ids.users.naomi); + + const forwardedEmbed = new EmbedBuilder() + .setColor(0x5865F2) + .setTitle(`Message from ${message.author.tag}!`) + .setDescription( + (message.attachments.size > 0 ? `**Attachments:** ${message.attachments.size} file(s)\n\n` : "\n") + + (message.embeds.length > 0 ? `**Embeds:** ${message.embeds.length}\n\n` : "") + "\n" + + (message.content || "*[No text content]*") + "\n\n"); + +const viewButton = new ButtonBuilder() + .setLabel("View Message") + .setURL(message.url) + .setStyle(ButtonStyle.Link); + +const row = new ActionRowBuilder() + .addComponents(viewButton); + await naomi.send({ + embeds: [forwardedEmbed], + files: message.attachments.map((att) => att.url), + components: [row], + }); + + await interaction.editReply({ + content: "✅ Forwarded to your DMs!", + }); + } catch (error) { + console.error("Failed to forward:", error); + + let replyText = "❌ Failed to forward message."; + + if (error instanceof DiscordAPIError && error.code === 50007) { + replyText += " (Naomi's DMs might be closed)"; + } + + await interaction.editReply(replyText); + } + }, +}; \ No newline at end of file diff --git a/src/config/ids.ts b/src/config/ids.ts index c2c42ce..35fca11 100644 --- a/src/config/ids.ts +++ b/src/config/ids.ts @@ -70,5 +70,6 @@ export const ids = { amari: "1406431359345496255", naomi: "465650873650118659", nhcarrigan: "1382837581649150104", + teklu:"1381735115163570198", }, -}; +}; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 312ffeb..84cac00 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,14 +6,15 @@ import { DiscordAnalytics } from "@nhcarrigan/discord-analytics"; import { - Client, - GatewayIntentBits, - Events, - Partials, - MessageFlags, + Client, + GatewayIntentBits, + Events, + Partials, + MessageFlags, } from "discord.js"; import { scheduleJob } from "node-schedule"; import { App } from "octokit"; +import { forwardOwnerDM } from "./commands/forwardToOwner.js"; import { ids } from "./config/ids.js"; import { handleMessageCreate } from "./events/handleMessageCreate.js"; import { cacheData } from "./modules/cacheData.js"; @@ -31,136 +32,148 @@ import { logger } from "./utils/logger.js"; import type { Amari } from "./interfaces/amari.js"; if ( - process.env.GH_CLIENT_ID === undefined - || process.env.GH_PRIVATE_KEY === undefined + process.env.GH_CLIENT_ID === undefined + || process.env.GH_PRIVATE_KEY === undefined ) { - throw new Error("Cannot initialise GitHub!"); + throw new Error("Cannot initialise GitHub!"); } const githubApp = new App({ - appId: process.env.GH_CLIENT_ID, - privateKey: process.env.GH_PRIVATE_KEY.replaceAll("\\n", "\n"), + appId: process.env.GH_CLIENT_ID, + privateKey: process.env.GH_PRIVATE_KEY.replaceAll("\\n", "\n"), }); + const octokit = await githubApp.getInstallationOctokit(83_119_105); const { data } = await octokit.rest.apps.getAuthenticated(); await logger.log( - "debug", - `Authenticated to GitHub as ${data?.name ?? "unknown"}`, + "debug", + `Authenticated to GitHub as ${data?.name ?? "unknown"}`, ); const amari: Amari = { - discord: new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - GatewayIntentBits.GuildMembers, - GatewayIntentBits.DirectMessages, - ], - partials: [ Partials.Channel ], - }), - github: octokit, - lastRssItems: { - freeCodeCamp: null, - hackerNews: null, - }, - recentlyActiveChannels: new Set(), + discord: new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.DirectMessages, + ], + partials: [Partials.Channel], + }), + github: octokit, + lastRssItems: { + freeCodeCamp: null, + hackerNews: null, + }, + recentlyActiveChannels: new Set(), }; const analytics = new DiscordAnalytics(amari.discord, logger); amari.discord.once(Events.ClientReady, () => { - void logger.log( - "debug", - `Authenticated to Discord as ${amari.discord.user?.username ?? "unknown"}`, - ); - void cacheData(amari); - analytics.startCron(); - scheduleJob("post news", "0 * * * *", async() => { - await postFreeCodeCampNews(amari); - await postHackerNews(amari); - }); - scheduleJob("check guild tags", "0 0 * * *", async() => { - await logger.log("debug", "Auditing guild tags."); - await cacheData(amari); - }); - scheduleJob("post progress reminders", "0 9 * * 1-5", async() => { - await postProgressReminders(amari); - }); - setInterval(() => { - amari.recentlyActiveChannels = new Set(); - }, 10 * 60 * 1000); - setInterval(() => { - void checkRetroAchievements(amari); - }, 10 * 60 * 1000); + void logger.log( + "debug", + `Authenticated to Discord as ${amari.discord.user?.username ?? "unknown"}`, + ); + void cacheData(amari); + analytics.startCron(); + scheduleJob("post news", "0 * * * *", async () => { + await postFreeCodeCampNews(amari); + await postHackerNews(amari); + }); + scheduleJob("check guild tags", "0 0 * * *", async () => { + await logger.log("debug", "Auditing guild tags."); + await cacheData(amari); + }); + scheduleJob("post progress reminders", "0 9 * * 1-5", async () => { + await postProgressReminders(amari); + }); + setInterval(() => { + amari.recentlyActiveChannels = new Set(); + }, 10 * 60 * 1000); + setInterval(() => { + void checkRetroAchievements(amari); + }, 10 * 60 * 1000); }); amari.discord.on(Events.MessageCreate, (message) => { - if (!message.inGuild()) { - void respondToDm(amari, message); - return; - } - void handleMessageCreate(amari, message); + if (!message.inGuild()) { + void respondToDm(amari, message); + return; + } + void handleMessageCreate(amari, message); }); amari.discord.on(Events.InteractionCreate, (interaction) => { - void analytics.logGatewayEvent(Events.InteractionCreate, { ...interaction }); - if (interaction.isButton() && interaction.customId === "resolve") { - if (interaction.user.id !== ids.users.naomi) { - return void interaction.reply({ - content: "Who are you????", - flags: [ MessageFlags.Ephemeral ], - }); + void analytics.logGatewayEvent(Events.InteractionCreate, { ...interaction }); + + if (interaction.isMessageContextMenuCommand() && interaction.commandName === "Forward to Naomi") { + void forwardOwnerDM.execute(interaction); + return } - return void interaction.message.delete(); - } - if (interaction.isAutocomplete()) { - return void interaction; - } - return void interaction.reply({ - content: "What?", - flags: [ MessageFlags.Ephemeral ], - }); + + if (interaction.isButton() && interaction.customId === "resolve") { + if (interaction.user.id !== ids.users.naomi) { + return void interaction.reply({ + content: "Who are you????", + flags: [MessageFlags.Ephemeral], + }); + } + + return void interaction.message.delete(); + } + if (interaction.isAutocomplete()) { + return void interaction; + } + return void interaction.reply({ + content: "What?", + flags: [MessageFlags.Ephemeral], + }); }); amari.discord.on(Events.ThreadCreate, (thread) => { - if (thread.parent?.isThreadOnly() !== true) { - return; - } - const { bugReports, communityFeedback, featureRequests, policyIdeation } - = ids.channels; - if ( - ![ bugReports, - communityFeedback, - featureRequests, - policyIdeation ].includes( - thread.parent.id, - ) - ) { - return; - } - const tagId = getForumTagId(thread.parent.id); - if (tagId === null) { - return; - } - void thread.setAppliedTags([ tagId ]); + if (thread.parent?.isThreadOnly() !== true) { + return; + } + const { bugReports, communityFeedback, featureRequests, policyIdeation } + = ids.channels; + if ( + ![bugReports, + communityFeedback, + featureRequests, + policyIdeation].includes( + thread.parent.id, + ) + ) { + return; + } + const tagId = getForumTagId(thread.parent.id); + if (tagId === null) { + return; + } + void thread.setAppliedTags([tagId]); }); amari.discord.on(Events.UserUpdate, (_oldUser, updatedUser) => { - void processUserGuildTag(amari, updatedUser); + void processUserGuildTag(amari, updatedUser); }); amari.discord.on(Events.GuildMemberUpdate, (oldMember, updatedMember) => { - void processMentorshipRole(amari, oldMember, updatedMember); + void processMentorshipRole(amari, oldMember, updatedMember); }); amari.discord.on(Events.GuildMemberAdd, (member) => { - void logMenteeJoin(amari, member); + void logMenteeJoin(amari, member); }); amari.discord.on(Events.GuildMemberRemove, (member) => { - void logMenteeLeave(amari, member); + void logMenteeLeave(amari, member); }); await amari.discord.login(process.env.BOT_TOKEN); +amari.discord.on(Events.MessageCreate, (message) => { + const channelType = message.channel.type; + console.log(`📩 [${channelType}] ${message.author.tag}: ${message.content}`); +}); instantiateServer(amari); diff --git a/src/scripts/deploy-global.ts b/src/scripts/deploy-global.ts new file mode 100644 index 0000000..0a4de4e --- /dev/null +++ b/src/scripts/deploy-global.ts @@ -0,0 +1,32 @@ +import { REST, Routes } from "discord.js"; +import { forwardOwnerDM } from "../commands/forwardToOwner.js"; + +const commands = [ + forwardOwnerDM.data.toJSON(), +]; + +const token = process.env.BOT_TOKEN; +const clientId = process.env.GH_CLIENT_ID; + +if (token === undefined) { + throw new Error("BOT_TOKEN is missing from environment variables!"); +} +if (clientId === undefined) { + throw new Error("CLIENT_ID is missing from environment variables!"); +} + +const rest = new REST({ version: "10" }).setToken(token); +(async() => { + try { + console.log("Registering GLOBAL context menu command... wait till appear everywhere."); + + await rest.put( + Routes.applicationCommands(clientId), + { body: commands }, + ); + + console.log("Global registration sent! then check right-click on a message → Apps."); + } catch (error) { + console.error("Registration failed:", error); + } +})(); From 0a3c000add6a14ced2703d9e162fee9c7e9c2462 Mon Sep 17 00:00:00 2001 From: Teklu Date: Tue, 17 Feb 2026 01:13:41 +0900 Subject: [PATCH 2/4] fix: fix lint erorr --- src/commands/forwardToOwner.ts | 78 ++++--- src/config/ids.ts | 4 +- src/index.ts | 212 +++++++++--------- .../{deploy-global.ts => deployGlobal.ts} | 30 +-- 4 files changed, 172 insertions(+), 152 deletions(-) rename src/scripts/{deploy-global.ts => deployGlobal.ts} (53%) diff --git a/src/commands/forwardToOwner.ts b/src/commands/forwardToOwner.ts index 2166985..5c8e73d 100644 --- a/src/commands/forwardToOwner.ts +++ b/src/commands/forwardToOwner.ts @@ -1,3 +1,9 @@ +/** + * @copyright NHCarrigan + * @license Naomi's Public License + * @author Teklu Abayneh + */ + import { ContextMenuCommandBuilder, ApplicationCommandType, @@ -9,13 +15,14 @@ import { ButtonStyle, } from "discord.js"; import { ids } from "../config/ids.js"; +import { logger } from "../utils/logger.js"; export const forwardOwnerDM = { - data: new ContextMenuCommandBuilder() - .setName("Forward to Naomi") - .setType(ApplicationCommandType.Message), + data: new ContextMenuCommandBuilder().setName("Forward to Naomi"). + setType(ApplicationCommandType.Message), - async execute(interaction: MessageContextMenuCommandInteraction) { + async execute(interaction: MessageContextMenuCommandInteraction): + Promise { await interaction.deferReply({ ephemeral: true }); if (interaction.user.id !== ids.users.naomi) { @@ -26,47 +33,58 @@ export const forwardOwnerDM = { const message = interaction.targetMessage; if (message.author.id === ids.users.naomi) { - await interaction.editReply("No need to forward your own message to yourself 😄"); + await interaction.editReply( + "No need to forward your own message to yourself 😄", + ); return; } try { const naomi = await interaction.client.users.fetch(ids.users.naomi); + const forwardedEmbed = new EmbedBuilder(). + setColor(0x58_65_F2). + setTitle(`Message from ${String(message.author.tag)}!`). + setDescription( + `${(message.attachments.size > 0 + ? `**Attachments:** ${String(message.attachments.size)} + file(s)\n\n` + : "\n") + + (message.embeds.length > 0 + ? `**Embeds:** ${String(message.embeds.length)}\n\n` + : "")} + \n${message.content !== "" || message.content !== null + ? message.content + : "*[No text content]*"}\n\n`, + ); - const forwardedEmbed = new EmbedBuilder() - .setColor(0x5865F2) - .setTitle(`Message from ${message.author.tag}!`) - .setDescription( - (message.attachments.size > 0 ? `**Attachments:** ${message.attachments.size} file(s)\n\n` : "\n") + - (message.embeds.length > 0 ? `**Embeds:** ${message.embeds.length}\n\n` : "") + "\n" + - (message.content || "*[No text content]*") + "\n\n"); + const viewButton = new ButtonBuilder(). + setLabel("View Message"). + setURL(message.url). + setStyle(ButtonStyle.Link); -const viewButton = new ButtonBuilder() - .setLabel("View Message") - .setURL(message.url) - .setStyle(ButtonStyle.Link); - -const row = new ActionRowBuilder() - .addComponents(viewButton); - await naomi.send({ - embeds: [forwardedEmbed], - files: message.attachments.map((att) => att.url), - components: [row], + const row = new ActionRowBuilder().addComponents( + viewButton, + ); + await naomi.send({ + components: [ row ], + embeds: [ forwardedEmbed ], + files: message.attachments.map((att) => { + return att.url; + }), }); await interaction.editReply({ content: "✅ Forwarded to your DMs!", }); } catch (error) { - console.error("Failed to forward:", error); - let replyText = "❌ Failed to forward message."; - - if (error instanceof DiscordAPIError && error.code === 50007) { - replyText += " (Naomi's DMs might be closed)"; + if (error instanceof DiscordAPIError && error.code === 50_007) { + replyText = `${replyText} (Naomi's DMs might be closed)`; } - await interaction.editReply(replyText); + if (error instanceof Error) { + await logger.error("operation", error); + } } }, -}; \ No newline at end of file +}; diff --git a/src/config/ids.ts b/src/config/ids.ts index 35fca11..174ae5f 100644 --- a/src/config/ids.ts +++ b/src/config/ids.ts @@ -70,6 +70,6 @@ export const ids = { amari: "1406431359345496255", naomi: "465650873650118659", nhcarrigan: "1382837581649150104", - teklu:"1381735115163570198", + teklu: "1381735115163570198", }, -}; \ No newline at end of file +}; diff --git a/src/index.ts b/src/index.ts index 84cac00..03243d9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,11 +6,11 @@ import { DiscordAnalytics } from "@nhcarrigan/discord-analytics"; import { - Client, - GatewayIntentBits, - Events, - Partials, - MessageFlags, + Client, + GatewayIntentBits, + Events, + Partials, + MessageFlags, } from "discord.js"; import { scheduleJob } from "node-schedule"; import { App } from "octokit"; @@ -32,148 +32,150 @@ import { logger } from "./utils/logger.js"; import type { Amari } from "./interfaces/amari.js"; if ( - process.env.GH_CLIENT_ID === undefined - || process.env.GH_PRIVATE_KEY === undefined + process.env.GH_CLIENT_ID === undefined + || process.env.GH_PRIVATE_KEY === undefined ) { - throw new Error("Cannot initialise GitHub!"); + throw new Error("Cannot initialise GitHub!"); } const githubApp = new App({ - appId: process.env.GH_CLIENT_ID, - privateKey: process.env.GH_PRIVATE_KEY.replaceAll("\\n", "\n"), + appId: process.env.GH_CLIENT_ID, + privateKey: process.env.GH_PRIVATE_KEY.replaceAll("\\n", "\n"), }); const octokit = await githubApp.getInstallationOctokit(83_119_105); const { data } = await octokit.rest.apps.getAuthenticated(); await logger.log( - "debug", - `Authenticated to GitHub as ${data?.name ?? "unknown"}`, + "debug", + `Authenticated to GitHub as ${data?.name ?? "unknown"}`, ); const amari: Amari = { - discord: new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - GatewayIntentBits.GuildMembers, - GatewayIntentBits.DirectMessages, - ], - partials: [Partials.Channel], - }), - github: octokit, - lastRssItems: { - freeCodeCamp: null, - hackerNews: null, - }, - recentlyActiveChannels: new Set(), + discord: new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.DirectMessages, + ], + partials: [ Partials.Channel ], + }), + github: octokit, + lastRssItems: { + freeCodeCamp: null, + hackerNews: null, + }, + recentlyActiveChannels: new Set(), }; const analytics = new DiscordAnalytics(amari.discord, logger); amari.discord.once(Events.ClientReady, () => { - void logger.log( - "debug", - `Authenticated to Discord as ${amari.discord.user?.username ?? "unknown"}`, - ); - void cacheData(amari); - analytics.startCron(); - scheduleJob("post news", "0 * * * *", async () => { - await postFreeCodeCampNews(amari); - await postHackerNews(amari); - }); - scheduleJob("check guild tags", "0 0 * * *", async () => { - await logger.log("debug", "Auditing guild tags."); - await cacheData(amari); - }); - scheduleJob("post progress reminders", "0 9 * * 1-5", async () => { - await postProgressReminders(amari); - }); - setInterval(() => { - amari.recentlyActiveChannels = new Set(); - }, 10 * 60 * 1000); - setInterval(() => { - void checkRetroAchievements(amari); - }, 10 * 60 * 1000); + void logger.log( + "debug", + `Authenticated to Discord as ${amari.discord.user?.username ?? "unknown"}`, + ); + void cacheData(amari); + analytics.startCron(); + scheduleJob("post news", "0 * * * *", async() => { + await postFreeCodeCampNews(amari); + await postHackerNews(amari); + }); + scheduleJob("check guild tags", "0 0 * * *", async() => { + await logger.log("debug", "Auditing guild tags."); + await cacheData(amari); + }); + scheduleJob("post progress reminders", "0 9 * * 1-5", async() => { + await postProgressReminders(amari); + }); + setInterval( + () => { + amari.recentlyActiveChannels = new Set(); + }, + 10 * 60 * 1000, + ); + setInterval( + () => { + void checkRetroAchievements(amari); + }, + 10 * 60 * 1000, + ); }); amari.discord.on(Events.MessageCreate, (message) => { - if (!message.inGuild()) { - void respondToDm(amari, message); - return; - } - void handleMessageCreate(amari, message); + if (!message.inGuild()) { + void respondToDm(amari, message); + return; + } + void handleMessageCreate(amari, message); }); amari.discord.on(Events.InteractionCreate, (interaction) => { - void analytics.logGatewayEvent(Events.InteractionCreate, { ...interaction }); + void analytics.logGatewayEvent(Events.InteractionCreate, { ...interaction }); - if (interaction.isMessageContextMenuCommand() && interaction.commandName === "Forward to Naomi") { - void forwardOwnerDM.execute(interaction); - return + if ( + interaction.isMessageContextMenuCommand() + && interaction.commandName === "Forward to Naomi" + ) { + void forwardOwnerDM.execute(interaction); + return; + } + + if (interaction.isButton() && interaction.customId === "resolve") { + if (interaction.user.id !== ids.users.naomi) { + return void interaction.reply({ + content: "Who are you????", + flags: [ MessageFlags.Ephemeral ], + }); } - if (interaction.isButton() && interaction.customId === "resolve") { - if (interaction.user.id !== ids.users.naomi) { - return void interaction.reply({ - content: "Who are you????", - flags: [MessageFlags.Ephemeral], - }); - } - - return void interaction.message.delete(); - } - if (interaction.isAutocomplete()) { - return void interaction; - } - return void interaction.reply({ - content: "What?", - flags: [MessageFlags.Ephemeral], - }); + return void interaction.message.delete(); + } + if (interaction.isAutocomplete()) { + return void interaction; + } + return void interaction.reply({ + content: "What?", + flags: [ MessageFlags.Ephemeral ], + }); }); amari.discord.on(Events.ThreadCreate, (thread) => { - if (thread.parent?.isThreadOnly() !== true) { - return; - } - const { bugReports, communityFeedback, featureRequests, policyIdeation } - = ids.channels; - if ( - ![bugReports, - communityFeedback, - featureRequests, - policyIdeation].includes( - thread.parent.id, - ) - ) { - return; - } - const tagId = getForumTagId(thread.parent.id); - if (tagId === null) { - return; - } - void thread.setAppliedTags([tagId]); + if (thread.parent?.isThreadOnly() !== true) { + return; + } + const { bugReports, communityFeedback, featureRequests, policyIdeation } + = ids.channels; + if ( + ![ bugReports, communityFeedback, featureRequests, policyIdeation ].includes( + thread.parent.id, + ) + ) { + return; + } + const tagId = getForumTagId(thread.parent.id); + if (tagId === null) { + return; + } + void thread.setAppliedTags([ tagId ]); }); amari.discord.on(Events.UserUpdate, (_oldUser, updatedUser) => { - void processUserGuildTag(amari, updatedUser); + void processUserGuildTag(amari, updatedUser); }); amari.discord.on(Events.GuildMemberUpdate, (oldMember, updatedMember) => { - void processMentorshipRole(amari, oldMember, updatedMember); + void processMentorshipRole(amari, oldMember, updatedMember); }); amari.discord.on(Events.GuildMemberAdd, (member) => { - void logMenteeJoin(amari, member); + void logMenteeJoin(amari, member); }); amari.discord.on(Events.GuildMemberRemove, (member) => { - void logMenteeLeave(amari, member); + void logMenteeLeave(amari, member); }); await amari.discord.login(process.env.BOT_TOKEN); -amari.discord.on(Events.MessageCreate, (message) => { - const channelType = message.channel.type; - console.log(`📩 [${channelType}] ${message.author.tag}: ${message.content}`); -}); instantiateServer(amari); diff --git a/src/scripts/deploy-global.ts b/src/scripts/deployGlobal.ts similarity index 53% rename from src/scripts/deploy-global.ts rename to src/scripts/deployGlobal.ts index 0a4de4e..4445cfb 100644 --- a/src/scripts/deploy-global.ts +++ b/src/scripts/deployGlobal.ts @@ -1,10 +1,14 @@ +/** + * @copyright NHCarrigan + * @license Naomi's Public License + * @author Teklu Abayneh + */ + import { REST, Routes } from "discord.js"; import { forwardOwnerDM } from "../commands/forwardToOwner.js"; +import { logger } from "../utils/logger.js"; -const commands = [ - forwardOwnerDM.data.toJSON(), -]; - +const commands = [ forwardOwnerDM.data.toJSON() ]; const token = process.env.BOT_TOKEN; const clientId = process.env.GH_CLIENT_ID; @@ -16,17 +20,13 @@ if (clientId === undefined) { } const rest = new REST({ version: "10" }).setToken(token); -(async() => { +const requestCommand = async(): Promise => { try { - console.log("Registering GLOBAL context menu command... wait till appear everywhere."); - - await rest.put( - Routes.applicationCommands(clientId), - { body: commands }, - ); - - console.log("Global registration sent! then check right-click on a message → Apps."); + await rest.put(Routes.applicationCommands(clientId), { body: commands }); } catch (error) { - console.error("Registration failed:", error); + if (error instanceof Error) { + await logger.error("operation", error); + } } -})(); +}; +requestCommand(); From 60fec0f2d85c99a0eda4965de621cf289d159810 Mon Sep 17 00:00:00 2001 From: Teklu Date: Sat, 21 Feb 2026 23:36:20 +0900 Subject: [PATCH 3/4] fix: solve linter error --- src/commands/forwardToOwner.ts | 139 +++++++++++++++++---------------- src/index.ts | 5 +- src/scripts/deployGlobal.ts | 2 +- 3 files changed, 78 insertions(+), 68 deletions(-) diff --git a/src/commands/forwardToOwner.ts b/src/commands/forwardToOwner.ts index 5c8e73d..207eab7 100644 --- a/src/commands/forwardToOwner.ts +++ b/src/commands/forwardToOwner.ts @@ -13,78 +13,85 @@ import { EmbedBuilder, ActionRowBuilder, ButtonStyle, + type Message, } from "discord.js"; import { ids } from "../config/ids.js"; import { logger } from "../utils/logger.js"; -export const forwardOwnerDM = { - data: new ContextMenuCommandBuilder().setName("Forward to Naomi"). - setType(ApplicationCommandType.Message), - - async execute(interaction: MessageContextMenuCommandInteraction): - Promise { - await interaction.deferReply({ ephemeral: true }); - - if (interaction.user.id !== ids.users.naomi) { - await interaction.editReply("❌ Only Naomi can use this command."); - return; - } - - const message = interaction.targetMessage; - - if (message.author.id === ids.users.naomi) { - await interaction.editReply( - "No need to forward your own message to yourself 😄", - ); - return; - } - - try { - const naomi = await interaction.client.users.fetch(ids.users.naomi); - const forwardedEmbed = new EmbedBuilder(). - setColor(0x58_65_F2). - setTitle(`Message from ${String(message.author.tag)}!`). - setDescription( - `${(message.attachments.size > 0 - ? `**Attachments:** ${String(message.attachments.size)} +const buildForwardedEmbed = (message: Message): EmbedBuilder => { + const forwardedEmbed = new EmbedBuilder(). + setColor(0x58_65_F2). + setTitle(`Message from ${String(message.author.tag)}!`). + setDescription( + `${(message.attachments.size > 0 + ? `**Attachments:** ${String(message.attachments.size)} file(s)\n\n` - : "\n") + : "\n") + (message.embeds.length > 0 ? `**Embeds:** ${String(message.embeds.length)}\n\n` : "")} - \n${message.content !== "" || message.content !== null - ? message.content - : "*[No text content]*"}\n\n`, - ); - - const viewButton = new ButtonBuilder(). - setLabel("View Message"). - setURL(message.url). - setStyle(ButtonStyle.Link); - - const row = new ActionRowBuilder().addComponents( - viewButton, - ); - await naomi.send({ - components: [ row ], - embeds: [ forwardedEmbed ], - files: message.attachments.map((att) => { - return att.url; - }), - }); - - await interaction.editReply({ - content: "✅ Forwarded to your DMs!", - }); - } catch (error) { - let replyText = "❌ Failed to forward message."; - if (error instanceof DiscordAPIError && error.code === 50_007) { - replyText = `${replyText} (Naomi's DMs might be closed)`; - } - await interaction.editReply(replyText); - if (error instanceof Error) { - await logger.error("operation", error); - } - } - }, + \n${message.content}\n\n`, + ); + return forwardedEmbed; +}; +const buildViewButtonFunction = (message: Message): ButtonBuilder => { + const viewButton = new ButtonBuilder(). + setLabel("View Message"). + setURL(message.url). + setStyle(ButtonStyle.Link); + return viewButton; +}; + +const data = new ContextMenuCommandBuilder().setName("Forward to Naomi"). + setType(ApplicationCommandType.Message); + +const execute = async(interaction: MessageContextMenuCommandInteraction): +Promise => { + await interaction.deferReply({ ephemeral: true }); + + if (interaction.user.id !== ids.users.naomi) { + await interaction.editReply("❌ Only Naomi can use this command."); + return; + } + + const message = interaction.targetMessage; + if (message.author.id === ids.users.naomi) { + await interaction.editReply( + "No need to forward your own message to yourself 😄", + ); + return; + } + + try { + const naomi = await interaction.client.users.fetch(ids.users.naomi); + const forwardedEmbed = buildForwardedEmbed(message); + const viewButton = buildViewButtonFunction(message); + + const row = new ActionRowBuilder().addComponents( + viewButton, + ); + await naomi.send({ + components: [ row ], + embeds: [ forwardedEmbed ], + files: message.attachments.map((att) => { + return att.url; + }), + }); + + await interaction.editReply({ + content: "✅ Forwarded to your DMs!", + }); + } catch (error) { + let replyText = "❌ Failed to forward message."; + if (error instanceof DiscordAPIError && error.code === 50_007) { + replyText = `${replyText} (Naomi's DMs might be closed)`; + } + await interaction.editReply(replyText); + await logger.error("operation", error); + } +}; + +export const forwardOwnerDM = { + data, + execute, }; diff --git a/src/index.ts b/src/index.ts index 03243d9..47634b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -148,7 +148,10 @@ amari.discord.on(Events.ThreadCreate, (thread) => { const { bugReports, communityFeedback, featureRequests, policyIdeation } = ids.channels; if ( - ![ bugReports, communityFeedback, featureRequests, policyIdeation ].includes( + ![ bugReports, + communityFeedback, + featureRequests, + policyIdeation ].includes( thread.parent.id, ) ) { diff --git a/src/scripts/deployGlobal.ts b/src/scripts/deployGlobal.ts index 4445cfb..deebd9f 100644 --- a/src/scripts/deployGlobal.ts +++ b/src/scripts/deployGlobal.ts @@ -29,4 +29,4 @@ const requestCommand = async(): Promise => { } } }; -requestCommand(); +void requestCommand(); From 10c7d337a5b93542e8f69eeb665fa0d5c870ce48 Mon Sep 17 00:00:00 2001 From: Teklu Date: Mon, 23 Feb 2026 22:40:53 +0900 Subject: [PATCH 4/4] fix: solve linter error --- src/commands/forwardToOwner.ts | 2 -- src/index.ts | 11 +++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/commands/forwardToOwner.ts b/src/commands/forwardToOwner.ts index 207eab7..b638c28 100644 --- a/src/commands/forwardToOwner.ts +++ b/src/commands/forwardToOwner.ts @@ -16,7 +16,6 @@ import { type Message, } from "discord.js"; import { ids } from "../config/ids.js"; -import { logger } from "../utils/logger.js"; const buildForwardedEmbed = (message: Message): EmbedBuilder => { const forwardedEmbed = new EmbedBuilder(). @@ -87,7 +86,6 @@ Promise => { replyText = `${replyText} (Naomi's DMs might be closed)`; } await interaction.editReply(replyText); - await logger.error("operation", error); } }; diff --git a/src/index.ts b/src/index.ts index 47634b1..8736373 100644 --- a/src/index.ts +++ b/src/index.ts @@ -124,18 +124,21 @@ amari.discord.on(Events.InteractionCreate, (interaction) => { if (interaction.isButton() && interaction.customId === "resolve") { if (interaction.user.id !== ids.users.naomi) { - return void interaction.reply({ + void interaction.reply({ content: "Who are you????", flags: [ MessageFlags.Ephemeral ], }); + return; } - return void interaction.message.delete(); + void interaction.message.delete(); + return; } if (interaction.isAutocomplete()) { - return void interaction; + void interaction; + return; } - return void interaction.reply({ + void interaction.reply({ content: "What?", flags: [ MessageFlags.Ephemeral ], });