diff --git a/dev.env b/dev.env new file mode 100644 index 0000000..55a4abf --- /dev/null +++ b/dev.env @@ -0,0 +1,3 @@ +DISCORD_TOKEN="op://Environment Variables - Development/Naomi Dev Bot/token" +AI_TOKEN="op://Environment Variables - Development/Naomi Dev Bot/ai token" +LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth" \ No newline at end of file diff --git a/src/commands/clear.ts b/src/commands/clear.ts new file mode 100644 index 0000000..58df1fe --- /dev/null +++ b/src/commands/clear.ts @@ -0,0 +1,24 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { + ApplicationIntegrationType, + SlashCommandBuilder, + InteractionContextType, +} from "discord.js"; + +const command = new SlashCommandBuilder(). + setContexts( + InteractionContextType.BotDM, + InteractionContextType.Guild, + InteractionContextType.PrivateChannel, + ). + setIntegrationTypes(ApplicationIntegrationType.UserInstall). + setName("clear"). + setDescription("Clear your current adventure so you can start a new one!"); + +// eslint-disable-next-line no-console -- We don't need our logger here as this never runs in production. +console.log(JSON.stringify(command.toJSON())); diff --git a/src/events/message.ts b/src/events/message.ts index ab64bd7..4432a0d 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -20,7 +20,7 @@ import type { MessageParam } from "@anthropic-ai/sdk/resources/index.js"; * Handles the Discord message event. * @param message - The message payload from Discord. */ -// eslint-disable-next-line max-lines-per-function -- We're off by one bloody line. +// eslint-disable-next-line max-lines-per-function, max-statements -- We're off by one bloody line. export const onMessage = async(message: Message): Promise => { try { if (message.channel.type !== ChannelType.DM) { @@ -33,20 +33,32 @@ export const onMessage = async(message: Message): Promise => { if (!subbed) { return; } - const history = await message.channel.messages.fetch({ limit: 6 }); - const context: Array - = history.reverse().map((messageInner) => { - return { - content: messageInner.content, - role: - messageInner.author.id === message.client.user.id - ? "assistant" - : "user", - }; + const historyRequest = await message.channel.messages.fetch({ limit: 6 }); + const history = [ ...historyRequest.values() ]; + const clearMessageIndex = history.findIndex((messageInner) => { + return ( + messageInner.content === "" + && messageInner.author.id === message.client.user.id + ); }); + if (clearMessageIndex !== -1) { + // Remove the clear message and everything sent before it, which means everything after in the array because the array is backwards + history.splice(0, clearMessageIndex + 1); + } + const context: Array = history. + reverse(). + map((messageInner) => { + return { + content: messageInner.content, + role: + messageInner.author.id === message.client.user.id + ? "assistant" + : "user", + }; + }); const messages = await ai.messages.create({ // eslint-disable-next-line @typescript-eslint/naming-convention -- Required key format for SDK. - max_tokens: 3000, + max_tokens: 5000, messages: context, model: "claude-3-5-sonnet-latest", system: `${personality} Provide a response to the user that continues the story. The user's name is ${message.author.displayName}`, diff --git a/src/index.ts b/src/index.ts index 5a2b047..183381d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { Client, Events, GatewayIntentBits, Partials } from "discord.js"; import { onMessage } from "./events/message.js"; import { about } from "./modules/about.js"; +import { clear } from "./modules/clear.js"; import { start } from "./modules/start.js"; import { instantiateServer } from "./server/serve.js"; import { logger } from "./utils/logger.js"; @@ -41,6 +42,9 @@ client.on(Events.InteractionCreate, (interaction) => { case "start": void start(interaction); break; + case "clear": + void clear(interaction); + break; default: void interaction.reply({ content: `I'm sorry, I don't know the ${interaction.commandName} command.`, diff --git a/src/modules/clear.ts b/src/modules/clear.ts new file mode 100644 index 0000000..8e7eb8b --- /dev/null +++ b/src/modules/clear.ts @@ -0,0 +1,47 @@ +/** + * @copyright nhcarrigan + * @license Naomi's Public License + * @author Naomi Carrigan + */ + +import { + MessageFlags, + type ChatInputCommandInteraction, +} from "discord.js"; +import { isSubscribedInteraction } from "../utils/isSubscribed.js"; +import { logger } from "../utils/logger.js"; +import { replyToError } from "../utils/replyToError.js"; + +/** + * Sends a clear message in the DMs. + * @param interaction -- The interaction payload from Discord. + */ +export const clear = async( + interaction: ChatInputCommandInteraction, +): Promise => { + try { + await interaction.deferReply({ flags: [ MessageFlags.Ephemeral ] }); + + const subbed = await isSubscribedInteraction(interaction); + if (!subbed) { + return; + } + const sent = await interaction.user.send({ + content: "", + }).catch(() => { + return null; + }); + + await interaction.editReply({ + content: sent + ? "I have added a clear history marker to your DMs." + // eslint-disable-next-line stylistic/max-len -- This is a long string. + : "I was unable to send you a DM. Please ensure your privacy settings allow direct messages.", + }); + } catch (error) { + await replyToError(interaction); + if (error instanceof Error) { + await logger.error("about command", error); + } + } +};