diff --git a/src/commands/forwardToOwner.ts b/src/commands/forwardToOwner.ts new file mode 100644 index 0000000..b638c28 --- /dev/null +++ b/src/commands/forwardToOwner.ts @@ -0,0 +1,95 @@ +/** + * @copyright NHCarrigan + * @license Naomi's Public License + * @author Teklu Abayneh + */ + +import { + ContextMenuCommandBuilder, + ApplicationCommandType, + type MessageContextMenuCommandInteraction, + DiscordAPIError, + ButtonBuilder, + EmbedBuilder, + ActionRowBuilder, + ButtonStyle, + type Message, +} from "discord.js"; +import { ids } from "../config/ids.js"; + +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") + + (message.embeds.length > 0 + ? `**Embeds:** ${String(message.embeds.length)}\n\n` + : "")} + \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); + } +}; + +export const forwardOwnerDM = { + data, + execute, +}; diff --git a/src/config/ids.ts b/src/config/ids.ts index c2c42ce..174ae5f 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", }, }; diff --git a/src/index.ts b/src/index.ts index 1a2d6bb..69d3d51 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ import { scheduleJob } from "node-schedule"; import { App } from "octokit"; import { createIssue } from "./commands/createIssue.js"; import { createTask } from "./commands/createTask.js"; +import { forwardOwnerDM } from "./commands/forwardToOwner.js"; import { onboardMentee } from "./commands/onboardMentee.js"; import { ids } from "./config/ids.js"; import { handleMessageCreate } from "./events/handleMessageCreate.js"; @@ -44,6 +45,7 @@ const githubApp = new App({ 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( @@ -91,12 +93,18 @@ amari.discord.once(Events.ClientReady, () => { 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); + setInterval( + () => { + amari.recentlyActiveChannels = new Set(); + }, + 10 * 60 * 1000, + ); + setInterval( + () => { + void checkRetroAchievements(amari); + }, + 10 * 60 * 1000, + ); }); amari.discord.on(Events.MessageCreate, (message) => { @@ -109,14 +117,26 @@ amari.discord.on(Events.MessageCreate, (message) => { amari.discord.on(Events.InteractionCreate, (interaction) => { void analytics.logGatewayEvent(Events.InteractionCreate, { ...interaction }); + + 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({ + void interaction.reply({ content: "Who are you????", flags: [ MessageFlags.Ephemeral ], }); + return; } - return void interaction.message.delete(); + + void interaction.message.delete(); + return; } if (interaction.isChatInputCommand()) { const { commandName } = interaction; @@ -131,9 +151,10 @@ amari.discord.on(Events.InteractionCreate, (interaction) => { } } if (interaction.isAutocomplete()) { - return void interaction; + void interaction; + return; } - return void interaction.reply({ + void interaction.reply({ content: "What?", flags: [ MessageFlags.Ephemeral ], }); diff --git a/src/scripts/deployGlobal.ts b/src/scripts/deployGlobal.ts new file mode 100644 index 0000000..deebd9f --- /dev/null +++ b/src/scripts/deployGlobal.ts @@ -0,0 +1,32 @@ +/** + * @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 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); +const requestCommand = async(): Promise => { + try { + await rest.put(Routes.applicationCommands(clientId), { body: commands }); + } catch (error) { + if (error instanceof Error) { + await logger.error("operation", error); + } + } +}; +void requestCommand();