feat: initial prototype
Code Analysis / SonarQube (push) Failing after 19s
Node.js CI / Lint and Test (push) Has been cancelled

This commit is contained in:
2025-10-09 11:28:28 -07:00
parent 00cbbdab24
commit 68f7eabe2c
27 changed files with 6158 additions and 14 deletions
+15
View File
@@ -0,0 +1,15 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
// eslint-disable-next-line @typescript-eslint/naming-convention -- Importing a class.
import Anthropic from "@anthropic-ai/sdk";
/**
* The Anthropic AI instance.
*/
export const ai = new Anthropic({
apiKey: process.env.AI_TOKEN,
});
+30
View File
@@ -0,0 +1,30 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import type { Usage } from "@anthropic-ai/sdk/resources/index.js";
/**
* Calculates the cost of a command run by a user, and sends to
* our logging service.
* @param usage -- The usage payload from Anthropic.
* @returns A string containing the usage and cost information.
*/
export const calculateCost = (
usage: Usage,
): string => {
const inputCost = usage.input_tokens * ((usage.input_tokens > 200_000
? 6
: 3) / 1_000_000);
const outputCost = usage.output_tokens * ((usage.output_tokens > 200_000
? 22.5
: 15) / 1_000_000);
const totalCost = inputCost + outputCost;
return `-# Accepted ${usage.input_tokens.toString()} and generated ${usage.output_tokens.toString()}.
-# Total cost: ${totalCost.toLocaleString("en-GB", {
currency: "USD",
style: "currency",
})}`;
};
+80
View File
@@ -0,0 +1,80 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
type ChatInputCommandInteraction,
type Message,
} from "discord.js";
const naomiId = "465650873650118659";
/**
* Checks if a user has an active entitlement (subscription) for the bot.
* If they do not, it responds to the interaction with a button to subscribe.
* @param interaction -- The interaction payload from Discord.
* @returns A boolean indicating whether the user is subscribed.
*/
const isNaomiInteraction = async(
interaction: ChatInputCommandInteraction,
): Promise<boolean> => {
const isNaomi = interaction.user.id === naomiId;
if (!isNaomi) {
const subscribeButton = new ButtonBuilder().
setStyle(ButtonStyle.Premium).
setSKUId("1425905043244060762");
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
subscribeButton,
);
await interaction.editReply({
components: [ row ],
// eslint-disable-next-line stylistic/max-len -- Big boi string.
content: "Sorry, as Naomi's personal assistant I am unable to assist you with your request. However, consider donating to Naomi so she can create additional free tools you CAN use.",
});
return false;
}
return true;
};
/**
* Checks if a user has an active entitlement (subscription) for the bot.
* If they do not, it responds to the message with a button to subscribe.
* @param message -- The message payload from Discord.
* @returns A boolean indicating whether the user is subscribed.
*/
const isNaomiMessage = async(
message: Message,
): Promise<boolean> => {
if (message.author.id === "465650873650118659") {
return true;
}
const subscribeButton = new ButtonBuilder().
setStyle(ButtonStyle.Premium).
setSKUId("1425905043244060762");
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
subscribeButton,
);
await message.client.application.entitlements.fetch();
const isEntitled = message.client.application.entitlements.cache.find(
(entitlement) => {
return entitlement.userId === message.author.id && entitlement.isActive();
},
);
if (!isEntitled) {
await message.reply({
components: [ row ],
// eslint-disable-next-line stylistic/max-len -- Big boi string.
content: "Sorry, as Naomi's personal assistant I am unable to assist you with your request. However, consider donating to Naomi so she can create additional free tools you CAN use.",
});
return false;
}
return true;
};
export { isNaomiInteraction, isNaomiMessage };
+12
View File
@@ -0,0 +1,12 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Logger } from "@nhcarrigan/logger";
export const logger = new Logger(
"Maylin Taryne",
process.env.LOG_TOKEN ?? "",
);
+39
View File
@@ -0,0 +1,39 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
type ChatInputCommandInteraction,
type MessageContextMenuCommandInteraction,
} from "discord.js";
/**
* Responds to an interaction with a generic error message.
* @param interaction -- The interaction payload from Discord.
*/
export const replyToError = async(
interaction:
| ChatInputCommandInteraction
| MessageContextMenuCommandInteraction,
): Promise<void> => {
const button = new ButtonBuilder().
setLabel("Need help?").
setStyle(ButtonStyle.Link).
setURL("https://chat.nhcarrigan.com");
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(button);
if (interaction.deferred || interaction.replied) {
await interaction.editReply({
components: [ row ],
content: "Something went wrong with this command.",
});
return;
}
await interaction.reply({
components: [ row ],
content: "Something went wrong with this command.",
});
};