feat: initial commit

This commit is contained in:
2025-11-02 09:05:06 -08:00
parent 7f0d908ea6
commit 17facb07fc
9 changed files with 5180 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
node_modules
prod
+7
View File
@@ -0,0 +1,7 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["typescript"],
}
+3
View File
@@ -0,0 +1,3 @@
import NaomisConfig from '@nhcarrigan/eslint-config';
export default NaomisConfig;
+31
View File
@@ -0,0 +1,31 @@
{
"name": "vivicrea",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"build": "tsc",
"lint": "eslint src --max-warnings 0",
"start": "op run --env-file=prod.env -- node prod/index.js",
"test": "echo \"Error: no test specified\" && exit 0"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.19.0",
"devDependencies": {
"@nhcarrigan/eslint-config": "5.2.0",
"@nhcarrigan/typescript-config": "4.0.0",
"@types/node": "24.9.2",
"eslint": "9.38.0",
"typescript": "5.9.3"
},
"dependencies": {
"@google/genai": "1.28.0",
"@nhcarrigan/discord-analytics": "0.0.6",
"@nhcarrigan/logger": "1.1.1",
"discord.js": "14.24.2",
"fastify": "5.6.1"
}
}
+5020
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
DISCORD_TOKEN="op://Environment Variables - Naomi/Vivicrea/Discord token"
GEMINI_API_KEY="op://Environment Variables - Naomi/Vivicrea/gemini token"
LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth"
+56
View File
@@ -0,0 +1,56 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import {
GoogleGenAI,
} from "@google/genai";
import { AttachmentBuilder, type MessageCreateOptions } from "discord.js";
/**
* Utility class for generating project information and images.
*/
export class Ai {
private readonly gemini: GoogleGenAI;
/**
* Creates a new instance of the Ai class.
* @param geminiKey - The API key for the Gemini API.
*/
public constructor(geminiKey: string) {
this.gemini = new GoogleGenAI({
apiKey: geminiKey,
});
}
/**
* Generates an image of Naomi in the style of the prompt.
* @returns A message create options object containing the image.
*/
public async generateImage(): Promise<MessageCreateOptions> {
const response = await this.gemini.models.generateContent({
config: {
imageConfig: { aspectRatio: "16:9" },
},
contents: `Anime style full body art of "Naomi", a woman with shoulder-length wavy light dusty brown hair, light blue eyes, very pale white skin, pink glasses, small vampire fangs, a gentle smile. She is barefoot, with neatly painted pink fingernails and matching toenails. Her accessories include pink teardrop earrings and a pearl bracelet. She should be depicted in various outfits and settings, but always maintaining these core features. The art style should be clean, detailed, and vibrant. You may refer to her reference sheet at https://cdn.nhcarrigan.com/ref.png. NEVER include text in the image, no text anywhere at all.`,
model: "gemini-2.5-flash-image",
});
const image = response.candidates?.[0]?.content?.parts?.find((p) => {
return Boolean(p.inlineData);
});
const base64 = image?.inlineData?.data;
if (base64 === undefined) {
return { content: "Sorry, I was unable to generate an image for you." };
}
return {
files: [
new AttachmentBuilder(
Buffer.from(base64, "base64"),
{ name: "naomi.png" },
),
],
};
}
}
+51
View File
@@ -0,0 +1,51 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Logger } from "@nhcarrigan/logger";
import { Client, Events, GatewayIntentBits } from "discord.js";
import { Ai } from "./classes/ai.js";
if (
process.env.GEMINI_API_KEY === undefined
|| process.env.LOG_TOKEN === undefined
|| process.env.DISCORD_TOKEN === undefined) {
throw new Error(
`GEMINI_API_KEY, LOG_TOKEN, and DISCORD_TOKEN must be set.`,
);
}
const ai = new Ai(process.env.GEMINI_API_KEY);
const logger = new Logger("Nomena", process.env.LOG_TOKEN);
const bot = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
],
});
bot.once("ready", () => {
void logger.log("debug", "Nomena is online!");
});
// eslint-disable-next-line @typescript-eslint/no-misused-promises -- Lazy.
bot.on(Events.MessageCreate, async(message) => {
if (!message.mentions.has("1434585327552430081", {
ignoreEveryone: true,
ignoreRepliedUser: true,
ignoreRoles: true,
})) {
return;
}
if (message.author.id !== "465650873650118659") {
await message.reply("Sorry, I can only generate images for Naomi.");
}
await message.channel.sendTyping();
const image = await ai.generateImage();
await message.reply(image);
});
await bot.login(process.env.DISCORD_TOKEN);
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "@nhcarrigan/typescript-config",
"compilerOptions": {
"outDir": "./prod",
"rootDir": "./src",
}
}