From 49ccedfdcc696e774eb212049a7953b218886807 Mon Sep 17 00:00:00 2001 From: artie Date: Mon, 10 Feb 2025 19:35:40 +0100 Subject: [PATCH] add pm2 config, license, merge dev commands into a subcommand, add message triggers --- LICENSE | 19 ++++++ ecosystem.config.cjs | 14 ++++ eslint.config.mjs | 3 + src/commands/owner/dev.ts | 116 ++++++++++++++++++++++++++++++++++ src/commands/owner/restart.ts | 19 ------ src/commands/owner/sync.ts | 26 -------- src/commands/owner/update.ts | 65 ------------------- src/events/messageCreate.ts | 13 +++- src/types/command.ts | 3 + src/utils/constants.ts | 16 +++++ 10 files changed, 183 insertions(+), 111 deletions(-) create mode 100644 LICENSE create mode 100644 ecosystem.config.cjs create mode 100644 src/commands/owner/dev.ts delete mode 100644 src/commands/owner/restart.ts delete mode 100644 src/commands/owner/sync.ts delete mode 100644 src/commands/owner/update.ts diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cb41893 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2025 artie + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs new file mode 100644 index 0000000..e78cf57 --- /dev/null +++ b/ecosystem.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + apps: [ + { + name: "artemis", + interpreter: "bun", + script: "src/index.ts", + time: true, + env: { + NODE_ENV: "production", + PATH: `${process.env.HOME}/.bun/bin:${process.env.PATH}`, + }, + }, + ], +}; diff --git a/eslint.config.mjs b/eslint.config.mjs index de8d500..07d7957 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -15,5 +15,8 @@ export default tseslint.config( "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-explicit-any": "off", }, + }, + { + ignores: ["ecosystem.config.cjs"], } ); diff --git a/src/commands/owner/dev.ts b/src/commands/owner/dev.ts new file mode 100644 index 0000000..bf85d2f --- /dev/null +++ b/src/commands/owner/dev.ts @@ -0,0 +1,116 @@ +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ChatInputCommandInteraction, + codeBlock, + ComponentType, + MessageFlags, + SlashCommandBuilder, +} from "discord.js"; +import { defineCommand } from ".."; +import { client } from "../../client"; +import { abort } from "../../utils/error"; +import { restart as restartBot } from "../../utils/restart"; +import { shell } from "../../utils/functions"; + +export async function sync(interaction: ChatInputCommandInteraction) { + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); + const counts = await client.syncCommands(); + + if (!counts) { + abort("No commands to sync"); + } + + const { guildCount, globalCount } = counts; + + await interaction.followUp( + `Successfully synced ${guildCount} guild and ${globalCount} global application commands` + ); +} + +export async function restart(interaction: ChatInputCommandInteraction) { + await interaction.reply({ + content: "Restarting...", + flags: MessageFlags.Ephemeral, + }); + + await restartBot({ token: interaction.token }); +} + +export async function update(interaction: ChatInputCommandInteraction) { + const response = await interaction.deferReply({ withResponse: true }); + const result = await shell`git pull`; + const output = result.stdout + result.stderr; + + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("restart") + .setLabel("Restart") + .setStyle(ButtonStyle.Success) + ); + + const isUpToDate = output.trim() === "Already up to date."; + + await interaction.editReply({ + components: isUpToDate || result.failed ? [] : [row], + embeds: [ + { + description: codeBlock(output), + color: isUpToDate ? 0x00ff00 : 0xff0000, + }, + ], + }); + + if (!isUpToDate && !result.failed) { + response.resource?.message + ?.awaitMessageComponent({ + componentType: ComponentType.Button, + time: 30000, + filter: (i) => i.user.id === interaction.user.id, + dispose: true, + }) + .then(async (interaction) => { + await interaction.update({ + components: [], + }); + await interaction.message.react("🔄"); + await restartBot({ + message: { + id: interaction.message.id, + channelId: interaction.message.channelId, + }, + }); + }); + } +} + +export default defineCommand({ + data: new SlashCommandBuilder() + .setName("dev") + .setDescription("Owner commands") + .addSubcommand((subcommand) => + subcommand.setName("sync").setDescription("Sync application commands") + ) + .addSubcommand((subcommand) => + subcommand.setName("restart").setDescription("Restarts the bot") + ) + .addSubcommand((subcommand) => + subcommand.setName("update").setDescription("Updates the bot") + ), + isOwnerOnly: true, + + async execute(interaction) { + switch (interaction.options.getSubcommand()) { + case "sync": + await sync(interaction); + break; + case "restart": + await restart(interaction); + break; + case "update": + await update(interaction); + break; + } + }, +}); diff --git a/src/commands/owner/restart.ts b/src/commands/owner/restart.ts deleted file mode 100644 index a7b8c26..0000000 --- a/src/commands/owner/restart.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { MessageFlags, SlashCommandBuilder } from "discord.js"; -import { defineCommand } from ".."; -import { restart } from "../../utils/restart"; - -export default defineCommand({ - data: new SlashCommandBuilder() - .setName("restart") - .setDescription("Restarts the bot"), - isOwnerOnly: true, - - async execute(interaction) { - await interaction.reply({ - content: "Restarting...", - flags: MessageFlags.Ephemeral, - }); - - await restart({ token: interaction.token }); - }, -}); diff --git a/src/commands/owner/sync.ts b/src/commands/owner/sync.ts deleted file mode 100644 index f94427e..0000000 --- a/src/commands/owner/sync.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { MessageFlags, SlashCommandBuilder } from "discord.js"; -import { client } from "../../client"; -import { defineCommand } from ".."; -import { abort } from "../../utils/error"; - -export default defineCommand({ - data: new SlashCommandBuilder() - .setName("sync") - .setDescription("Sync application commands"), - isOwnerOnly: true, - - async execute(interaction) { - await interaction.deferReply({ flags: MessageFlags.Ephemeral }); - const counts = await client.syncCommands(); - - if (!counts) { - abort("No commands to sync"); - } - - const { guildCount, globalCount } = counts; - - await interaction.followUp( - `Successfully synced ${guildCount} guild and ${globalCount} global application commands` - ); - }, -}); diff --git a/src/commands/owner/update.ts b/src/commands/owner/update.ts deleted file mode 100644 index 2df6cad..0000000 --- a/src/commands/owner/update.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - codeBlock, - ComponentType, - SlashCommandBuilder, -} from "discord.js"; -import { defineCommand } from ".."; -import { restart } from "../../utils/restart"; -import { shell } from "../../utils/functions"; - -export default defineCommand({ - data: new SlashCommandBuilder() - .setName("update") - .setDescription("Updates the bot"), - isOwnerOnly: true, - - async execute(interaction) { - const response = await interaction.deferReply({ withResponse: true }); - const result = await shell`git pull`; - const output = result.stdout + result.stderr; - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId("restart") - .setLabel("Restart") - .setStyle(ButtonStyle.Success) - ); - - const isUpToDate = output.trim() === "Already up to date."; - - await interaction.editReply({ - components: isUpToDate || result.failed ? [] : [row], - embeds: [ - { - description: codeBlock(output), - color: isUpToDate ? 0x00ff00 : 0xff0000, - }, - ], - }); - - if (!isUpToDate && !result.failed) { - response.resource?.message - ?.awaitMessageComponent({ - componentType: ComponentType.Button, - time: 30000, - filter: (i) => i.user.id === interaction.user.id, - dispose: true, - }) - .then(async (interaction) => { - await interaction.update({ - components: [], - }); - await interaction.message.react("🔄"); - await restart({ - message: { - id: interaction.message.id, - channelId: interaction.message.channelId, - }, - }); - }); - } - }, -}); diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index c64c389..f239b17 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -1,6 +1,7 @@ import { Events } from "discord.js"; import { defineEvent } from "."; -import { dedent } from "../utils/functions"; +import { dedent, pickRandom } from "../utils/functions"; +import { BAD_BOT_EMOJIS, GOOD_BOT_EMOJIS } from "../utils/constants"; export default defineEvent({ name: Events.MessageCreate, @@ -17,5 +18,15 @@ export default defineEvent({ ); return; } + + const trigger = message.content.match(/^(?good|bad)\s+bot\b/i); + if (trigger) { + const emojis = + trigger.groups?.side.toLowerCase() === "good" + ? GOOD_BOT_EMOJIS + : BAD_BOT_EMOJIS; + await message.reply(pickRandom(emojis)); + return; + } }, }); diff --git a/src/types/command.ts b/src/types/command.ts index a6b6a0c..0459fa8 100644 --- a/src/types/command.ts +++ b/src/types/command.ts @@ -5,17 +5,20 @@ import type { MessageContextMenuCommandInteraction, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, + SlashCommandSubcommandsOnlyBuilder, UserContextMenuCommandInteraction, } from "discord.js"; export type CommandBuilder = | SlashCommandBuilder + | SlashCommandSubcommandsOnlyBuilder | SlashCommandOptionsOnlyBuilder | ContextMenuCommandBuilder; type InferInteraction = B extends | SlashCommandBuilder | SlashCommandOptionsOnlyBuilder + | SlashCommandSubcommandsOnlyBuilder ? ChatInputCommandInteraction : B extends ContextMenuCommandBuilder ? MessageContextMenuCommandInteraction | UserContextMenuCommandInteraction diff --git a/src/utils/constants.ts b/src/utils/constants.ts index fdd21be..642b11e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -7,3 +7,19 @@ export const PROD = env.NODE_ENV === "production"; export const USER_AGENT = `artemis (discord.js ${version})`; export const FAKE_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0"; + +export const GOOD_BOT_EMOJIS = [ + "<:teehee:825098257742299136>", + "<:teehee2:825098258741067787>", + "<:teehee3:825098263820632066>", + "<:teehee4:825098262884778026>", + "<:teehee5:825098263437901825>", +]; + +export const BAD_BOT_EMOJIS = [ + "<:rip:825101664939147285>", + "<:rip2:825101666373206086>", + "<:rip3:825101667434889236>", + "<:rip4:825101668428546058>", + "<:rip5:825101671436255243>", +];