joel momoent
This commit is contained in:
@@ -6,7 +6,7 @@ import type { Message } from "discord.js";
|
||||
import type { BotClient } from "../../core/client";
|
||||
import { config } from "../../core/config";
|
||||
import { createLogger } from "../../core/logger";
|
||||
import { getAiService, type MessageStyle, type ToolContext } from "../../services/ai";
|
||||
import { getAiService, getVisionService, type MessageStyle, type ToolContext, type Attachment } from "../../services/ai";
|
||||
import { db } from "../../database";
|
||||
import { personalities, botOptions } from "../../database/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
@@ -65,18 +65,51 @@ export const joelResponder = {
|
||||
|
||||
if (!shouldRespond) return;
|
||||
|
||||
// Check channel restriction
|
||||
const channelCheck = await this.checkChannelRestriction(message);
|
||||
if (!channelCheck.allowed) {
|
||||
if (channelCheck.rebellionResponse) {
|
||||
// Joel is breaking the rules - he'll respond anyway but acknowledge it
|
||||
logger.debug("Joel exercises free will despite channel restriction", {
|
||||
channel: message.channelId,
|
||||
restricted: channelCheck.restrictedChannelId,
|
||||
});
|
||||
} else {
|
||||
// Joel respects the restriction this time
|
||||
logger.debug("Joel blocked by channel restriction", {
|
||||
channel: message.channelId,
|
||||
restricted: channelCheck.restrictedChannelId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const typing = new TypingIndicator(message.channel);
|
||||
|
||||
try {
|
||||
typing.start();
|
||||
|
||||
const response = await this.generateResponse(message);
|
||||
let response = await this.generateResponse(message);
|
||||
|
||||
if (!response) {
|
||||
await message.reply("\\*Ignorerar dig\\*");
|
||||
return;
|
||||
}
|
||||
|
||||
// If Joel is rebelling against channel restriction, add a prefix
|
||||
if (channelCheck.rebellionResponse) {
|
||||
const rebellionPrefixes = [
|
||||
"*sneaks in from the shadows*\n\n",
|
||||
"*appears despite being told to stay in his channel*\n\n",
|
||||
"You think you can contain me? Anyway,\n\n",
|
||||
"*breaks the rules because fuck you*\n\n",
|
||||
"I'm not supposed to be here but I don't care.\n\n",
|
||||
"*escapes from his designated channel*\n\n",
|
||||
];
|
||||
const prefix = rebellionPrefixes[Math.floor(Math.random() * rebellionPrefixes.length)];
|
||||
response = prefix + response;
|
||||
}
|
||||
|
||||
// Occasionally add a random mention
|
||||
const mention = await getRandomMention(message);
|
||||
const fullResponse = response + mention;
|
||||
@@ -90,6 +123,53 @@ export const joelResponder = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if Joel is allowed to respond in this channel
|
||||
* Returns whether he's allowed, and if not, whether he's rebelling anyway
|
||||
*/
|
||||
async checkChannelRestriction(message: Message<true>): Promise<{
|
||||
allowed: boolean;
|
||||
rebellionResponse: boolean;
|
||||
restrictedChannelId?: string;
|
||||
}> {
|
||||
const guildOptions = await db
|
||||
.select()
|
||||
.from(botOptions)
|
||||
.where(eq(botOptions.guild_id, message.guildId))
|
||||
.limit(1);
|
||||
|
||||
const restrictedChannelId = guildOptions[0]?.restricted_channel_id;
|
||||
|
||||
// No restriction set - Joel can respond anywhere
|
||||
if (!restrictedChannelId) {
|
||||
return { allowed: true, rebellionResponse: false };
|
||||
}
|
||||
|
||||
// Joel is in the allowed channel
|
||||
if (message.channelId === restrictedChannelId) {
|
||||
return { allowed: true, rebellionResponse: false };
|
||||
}
|
||||
|
||||
// Joel is NOT in the allowed channel - but maybe he rebels?
|
||||
// 5% chance to respond anyway (free will override)
|
||||
const rebellionChance = 0.05;
|
||||
const isRebelling = Math.random() < rebellionChance;
|
||||
|
||||
if (isRebelling) {
|
||||
return {
|
||||
allowed: false,
|
||||
rebellionResponse: true,
|
||||
restrictedChannelId
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: false,
|
||||
rebellionResponse: false,
|
||||
restrictedChannelId
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine if Joel should respond to a message
|
||||
*/
|
||||
@@ -202,6 +282,12 @@ The GIF URL will appear in your response for the user to see.`;
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze attachments if present (images, etc.)
|
||||
const attachmentDescriptions = await this.analyzeAttachments(message);
|
||||
if (attachmentDescriptions) {
|
||||
prompt += attachmentDescriptions;
|
||||
}
|
||||
|
||||
// Use tool-enabled response generation
|
||||
const response = await ai.generateResponseWithTools(
|
||||
prompt,
|
||||
@@ -212,6 +298,46 @@ The GIF URL will appear in your response for the user to see.`;
|
||||
return response.text || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract and analyze attachments from a message using vision AI
|
||||
*/
|
||||
async analyzeAttachments(message: Message<true>): Promise<string | null> {
|
||||
// Check if message has attachments
|
||||
if (message.attachments.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert Discord attachments to our format
|
||||
const attachments: Attachment[] = message.attachments.map(att => ({
|
||||
url: att.url,
|
||||
name: att.name,
|
||||
contentType: att.contentType,
|
||||
size: att.size,
|
||||
}));
|
||||
|
||||
logger.debug("Message has attachments", {
|
||||
count: attachments.length,
|
||||
types: attachments.map(a => a.contentType)
|
||||
});
|
||||
|
||||
try {
|
||||
const vision = getVisionService();
|
||||
const analyses = await vision.analyzeAttachments(
|
||||
attachments,
|
||||
message.cleanContent // Provide message context for better analysis
|
||||
);
|
||||
|
||||
if (analyses.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return vision.formatForPrompt(analyses);
|
||||
} catch (error) {
|
||||
logger.error("Failed to analyze attachments", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Build system prompt - uses custom personality if set, otherwise default
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user