feat: build out discord support agent
All checks were successful
Node.js CI / Lint and Test (pull_request) Successful in 1m17s

This commit is contained in:
2025-07-05 23:06:45 -07:00
parent af33e704a4
commit cd5c3761f4
33 changed files with 1300 additions and 0 deletions

View File

@ -0,0 +1,29 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { about } from "../commands/about.js";
import { dm } from "../commands/dm.js";
const handlers = {
_default: async (_, interaction) => {
await interaction.reply({
content: `Unknown command: ${interaction.commandName}`,
ephemeral: true,
});
},
about: about,
dm: dm,
};
/**
* Processes a slash command.
* @param hikari - Hikari's Discord instance.
* @param interaction - The command interaction payload from Discord.
*/
const chatInputInteractionCreate = async (hikari, interaction) => {
const name = interaction.commandName;
// eslint-disable-next-line no-underscore-dangle -- We use _default as a fallback handler.
const handler = handlers[name] ?? handlers._default;
await handler(hikari, interaction);
};
export { chatInputInteractionCreate };

View File

@ -0,0 +1,95 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { ai } from "../modules/ai.js";
import { checkGuildEntitlement, checkUserEntitlement, } from "../utils/checkEntitlement.js";
import { errorHandler } from "../utils/errorHandler.js";
/**
* Handles the creation of a message in Discord. If Hikari is mentioned in the message,
* trigger a response.
* @param hikari - Hikari's Discord instance.
* @param message - The message payload from Discord.
*/
// eslint-disable-next-line max-lines-per-function -- This function is large, but it handles a lot of logic.
const guildMessageCreate = async (hikari, message) => {
try {
if (!hikari.user || !message.mentions.has(hikari.user.id)) {
return;
}
await message.channel.sendTyping();
const hasSubscription = await checkGuildEntitlement(hikari, message.guild);
if (!hasSubscription) {
await message.reply({
content:
// eslint-disable-next-line stylistic/max-len -- Big boi string.
"Your server is not currently subscribed to use this service. Unfortunately, due to Discord restrictions, we cannot offer server subscriptions just yet. We are hard at work on our own payment system, so stay tuned! Until then, you can subscribe as a user and ask questions by DMing me directly!",
});
return;
}
if (!message.channel.isThread()) {
const thread = await message.startThread({
autoArchiveDuration: 60,
name: `Thread for ${message.author.username}`,
});
// Wait five seconds for the thread to be created
await new Promise((resolve) => {
// eslint-disable-next-line no-promise-executor-return -- We want to wait for a bit.
return setTimeout(resolve, 5000);
});
await ai(hikari, [message], message.member?.nickname ?? message.author.displayName, thread);
return;
}
const previousMessages = await message.channel.messages.fetch({
limit: 100,
});
await ai(hikari, [...previousMessages.values()], message.member?.nickname ?? message.author.displayName, message.channel);
}
catch (error) {
const id = await errorHandler(error, "message create event");
await message.reply({
content: `Something went wrong while processing your request. Please try again later, or [reach out in our support channel](<https://discord.com/channels/1354624415861833870/1385797209706201198>).\n-# ${id}`,
});
}
};
/**
* Processes the creation of a direct message in Discord.
* @param hikari - Hikari's Discord instance.
* @param message - The message payload from Discord.
*/
const directMessageCreate = async (hikari, message) => {
try {
if (message.author.bot || message.content === "<Clear History>") {
// Ignore bot messages and the clear history message
return;
}
await message.channel.sendTyping();
const hasSubscription = await checkUserEntitlement(hikari, message.author);
if (!hasSubscription) {
await message.reply({
content:
// eslint-disable-next-line stylistic/max-len -- Big boi string.
"You are not currently subscribed to use this service. Please note that a user subscription is NOT the same as a server subscription.",
});
return;
}
const historyRequest = await message.channel.messages.fetch({ limit: 100 });
const history = [...historyRequest.values()];
const clearMessageIndex = history.findIndex((messageInner) => {
return messageInner.content === "<Clear History>";
});
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(clearMessageIndex, history.length - clearMessageIndex);
}
await ai(hikari, history.reverse(), message.member?.nickname ?? message.author.displayName, message.channel);
}
catch (error) {
const id = await errorHandler(error, "message create event");
await message.reply({
content: `Something went wrong while processing your request. Please try again later, or [reach out in our support channel](<https://discord.com/channels/1354624415861833870/1385797209706201198>).\n-# ${id}`,
});
}
};
export { guildMessageCreate, directMessageCreate };