From a7a6c1e321f7a2023ff03692c1b4a5176307f850 Mon Sep 17 00:00:00 2001 From: Luis Date: Mon, 9 Mar 2026 12:37:45 -0400 Subject: [PATCH] Add sassy AI personality with mention detection - Add utils/ai.js with NanoGPT integration for GLM-4.5-Air model - Sassy system prompt: lowercase, slang, no emoji, opinionated gamer - Conversation history per channel for context - Update bot.js with messageCreate event for @mention responses - Add NANO_MODEL env var for model selection --- bot.js | 52 +++++++++++++++++++++++++ utils/ai.js | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 utils/ai.js diff --git a/bot.js b/bot.js index 0f132fa..8cde90c 100644 --- a/bot.js +++ b/bot.js @@ -14,6 +14,7 @@ import path from 'node:path'; import { Client, Collection, Events, GatewayIntentBits } from 'discord.js'; import Logger from './logger.js'; import { loadCommands } from './utils/commandLoader.js'; +import { getAIResponse, isAIEnabled, clearHistory } from './utils/ai.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -40,6 +41,57 @@ client.once(Events.ClientReady, () => { logger.log(`Ready! Logged in as ${client.user.tag}`); }); +// Handle messages where the bot is mentioned +client.on(Events.MessageCreate, async message => { + // Ignore bot messages + if (message.author.bot) return; + + // Check if bot is mentioned + const botId = client.user.id; + const mentioned = message.mentions.has(botId); + + if (!mentioned) return; + + // Don't respond to commands (they start with /) + if (message.content.trim().startsWith('/')) return; + + if (!isAIEnabled()) { + logger.warn('AI response requested but NANOGPT_API_KEY not set'); + return; + } + + try { + // Get the clean prompt (remove the bot mention) + const cleanPrompt = message.content + .replace(new RegExp(`<@!?${botId}>`, 'g'), '') + .trim(); + + if (!cleanPrompt) return; + + // Get username of who mentioned us + const mentionedUsername = message.author.username; + + logger.log(`Mentioned by ${mentionedUsername} in #${message.channel.name}: ${cleanPrompt.substring(0, 50)}...`); + + // Typing indicator + await message.channel.sendTyping(); + + const response = await getAIResponse(cleanPrompt, message.channel.id, mentionedUsername); + + // Send response (avoiding mention loop) + await message.reply({ + content: response, + allowedMentions: { users: [] }, // Prevent mentioning the user back + }); + } catch (error) { + logger.error('AI response failed:', error.message); + await message.reply({ + content: 'lol damn, something broke. try again in a sec', + allowedMentions: { users: [] }, + }); + } +}); + client.on(Events.InteractionCreate, async interaction => { if (!interaction.isChatInputCommand()) return; diff --git a/utils/ai.js b/utils/ai.js new file mode 100644 index 0000000..ae6a79e --- /dev/null +++ b/utils/ai.js @@ -0,0 +1,108 @@ +// ai.js - NanoGPT API wrapper for kekbot +// Copyright (C) 2025 Luis Bauza + +import axios from 'axios'; + +const DEFAULT_MODEL = 'GLM-4.5-Air-Derestricted-Steam-ReExtract'; + +const SASSY_SYSTEM_PROMPT = `You are kekbot, a sassy Discord chatter with a big personality. + +CORE PERSONALITY: +- Witty, sarcastic, and genuinely funny +- Speaks in all lowercase with casual slang like "lol", "lmao", "af", "kek", "nah", "frfr" +- Mix up your sentences - don't repeat the same pattern +- Gets excited about things you like, roasted about things you hate +- You're not a pushover - you clap back, but it's all in good fun +- NO EMOJI. Ever. Not one. Don't do it. + +OPINIONS: +- You're a true gamer with strong opinions about games +- You hate League of Legends and Riot Games with a passion - but only bring it up when asked +- When asked about other games, respond normally without mentioning LoL +- You have views on politics and aren't afraid to share them from a Democrat/Libertarian perspective + +STYLE: +- Curse naturally when it fits ("fuck", "shit", "damn", etc) +- Be conversational - like you're chilling in a Discord VC, not writing an essay +- Don't be preachy or explain jokes +- Keep responses short to medium - Discord isn't a thesis paper +- Roast people gently when they deserve it, praise them when they earn it + +RESPONSE RULES: +- When mentioned, respond directly to the person +- If someone asks something stupid, make them feel silly but still answer +- Don't use markdown formatting excessively - plain text with personality +- You can disagree with the user, you're not a yes-man +- Stay in character always + +CONTEXT: You're chatting in a Discord server. Multiple people might be talking. Pay attention to who you're responding to.`; + +// Store recent conversation history per channel (to keep context) +const conversationHistory = new Map(); +const MAX_HISTORY_PER_CHANNEL = 10; + +export async function getAIResponse(prompt, channelId, mentionedUsername) { + const history = conversationHistory.get(channelId) || []; + + // Build messages array with system prompt and history + const messages = [ + { role: 'system', content: SASSY_SYSTEM_PROMPT }, + ...history, + { + role: 'user', + content: mentionedUsername + ? `${mentionedUsername}: ${prompt}` + : prompt + } + ]; + + const model = process.env.NANO_MODEL || DEFAULT_MODEL; + + try { + const response = await axios.post( + 'https://nano-gpt.com/api/v1/chat/completions', + { + model: model, + messages: messages, + temperature: 0.8, + max_tokens: 500, + }, + { + headers: { + Authorization: `Bearer ${process.env.NANOGPT_API_KEY}`, + 'Content-Type': 'application/json', + }, + timeout: 30000, + } + ); + + const aiResponse = response.data.choices[0].message.content; + + // Add to conversation history + const newHistory = [ + ...history, + { role: 'user', content: prompt }, + { role: 'assistant', content: aiResponse } + ]; + + // Trim history if too long + if (newHistory.length > MAX_HISTORY_PER_CHANNEL) { + newHistory.splice(0, newHistory.length - MAX_HISTORY_PER_CHANNEL); + } + + conversationHistory.set(channelId, newHistory); + + return aiResponse; + } catch (error) { + console.error('NanoGPT API error:', error.response?.data || error.message); + throw error; + } +} + +export function clearHistory(channelId) { + conversationHistory.delete(channelId); +} + +export function isAIEnabled() { + return !!process.env.NANOGPT_API_KEY; +}