fix errors with no config found for new bot
Some checks failed
Builds / Discord-Node-Build (push) Has been cancelled
Builds / Discord-Ollama-Container-Build (push) Has been cancelled
Coverage / Discord-Node-Coverage (push) Has been cancelled

This commit is contained in:
2025-05-22 09:42:11 -04:00
parent 23a860a905
commit 2630f7d083
2 changed files with 110 additions and 63 deletions

View File

@@ -54,10 +54,14 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
// Check if message is a bot response to avoid loops // Check if message is a bot response to avoid loops
const isBotResponseKey = `message:${message.id}:is_bot_response` const isBotResponseKey = `message:${message.id}:is_bot_response`
if (isBotMessage) { if (isBotMessage) {
const isBotResponse = await redis.get(isBotResponseKey) try {
if (isBotResponse === 'true') { const isBotResponse = await redis.get(isBotResponseKey)
log(`Skipping bot message ${message.id} as it is a bot response.`) if (isBotResponse === 'true') {
return log(`Skipping bot message ${message.id} as it is a bot response.`)
return
}
} catch (error) {
log(`Failed to check is_bot_response: ${error}`)
} }
} }
@@ -110,16 +114,18 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
try { try {
// Retrieve Server/Guild Preferences // Retrieve Server/Guild Preferences
let attempt = 0 let attempt = 0
let serverConfig: ServerConfig | undefined
while (attempt < maxRetries) { while (attempt < maxRetries) {
try { try {
await new Promise((resolve, reject) => { serverConfig = await new Promise((resolve, reject) => {
getServerConfig(`${message.guildId}-config.json`, (config) => { getServerConfig(`${message.guildId}-config.json`, (config) => {
if (config === undefined) { if (!config) {
redis.set(`server:${message.guildId}:config`, JSON.stringify({ options: { 'toggle-chat': true } })) reject(new Error('Failed to retrieve or create Server Preferences'))
reject(new Error('Failed to locate or create Server Preferences\n\nPlease try chatting again...'))
} else if (!config.options['toggle-chat']) { } else if (!config.options['toggle-chat']) {
reject(new Error('Admin(s) have disabled chat features.\n\nPlease contact your server\'s admin(s).')) reject(new Error('Admin(s) have disabled chat features.\n\nPlease contact your server\'s admin(s).'))
} else { } else {
// Sync with Redis
redis.set(`server:${message.guildId}:config`, JSON.stringify(config)).catch((err) => log(`Failed to sync server config to Redis: ${err}`))
resolve(config) resolve(config)
} }
}) })
@@ -131,7 +137,21 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
log(`Attempt ${attempt} failed for Server Preferences. Retrying in ${delay}ms...`) log(`Attempt ${attempt} failed for Server Preferences. Retrying in ${delay}ms...`)
await new Promise(ret => setTimeout(ret, delay)) await new Promise(ret => setTimeout(ret, delay))
} else { } else {
throw new Error(`Could not retrieve Server Preferences, please try chatting again...`) // Check Redis for server config as fallback
try {
const redisConfig = await redis.get(`server:${message.guildId}:config`)
if (redisConfig) {
serverConfig = JSON.parse(redisConfig)
if (serverConfig.options['toggle-chat']) {
break
} else {
throw new Error('Admin(s) have disabled chat features.\n\nPlease contact your server\'s admin(s).'))
}
}
} catch (redisError) {
log(`Redis fallback failed: ${redisError}`)
}
throw new Error('Could not retrieve Server Preferences, please try chatting again...')
} }
} }
} }
@@ -305,7 +325,7 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
: `Responding to user ${message.author.tag}` : `Responding to user ${message.author.tag}`
// Construct prompt with [CHARACTER], [SENTIMENT], and [CONTEXT] // Construct prompt with [CHARACTER], [SENTIMENT], and [CONTEXT]
const prompt = `[CHARACTER]\n${personality}\n[SENTIMENT]\n${sentimentData}\n[CONTEXT]\n${messageContext}\n[USER_INPUT]\n${cleanedMessage}` const prompt = `[CHARACTER]\n${personality}\n[SENTIMENT]\n${sentimentData}\n[CONTEXT]\n${messageContext}\n[USER_INPUT]\n${cleanedMessage}\n[INSTRUCTION]\nRespond in JSON format with keys: status, reply, metadata. Example: {"status":"success","reply":"Hi!","metadata":{}}`
// Set up message history queue // Set up message history queue
msgHist.setQueue(chatMessages) msgHist.setQueue(chatMessages)
@@ -333,6 +353,9 @@ export default event(Events.MessageCreate, async ({ log, msgHist, ollama, client
// Strip Markdown code fences if present // Strip Markdown code fences if present
let content = response.message.content let content = response.message.content
content = content.replace(/^```json\n|```$/g, '').trim() content = content.replace(/^```json\n|```$/g, '').trim()
if (!content.startsWith('{') || !content.endsWith('}')) {
throw new Error('Model response is not valid JSON')
}
jsonResponse = JSON.parse(content) jsonResponse = JSON.parse(content)
if (!jsonResponse.status || !jsonResponse.reply) { if (!jsonResponse.status || !jsonResponse.reply) {
throw new Error('Missing status or reply in model response') throw new Error('Missing status or reply in model response')

View File

@@ -1,67 +1,91 @@
import { ChannelType } from 'discord.js' import { Configuration, ServerConfig, UserConfig, isServerConfigurationKey } from '../index.js'
import { UserMessage } from './index.js' import fs from 'fs/promises' // Use promises for async
import path from 'path'
export interface UserConfiguration {
'message-stream'?: boolean,
'modify-capacity': number,
'switch-model': string
}
export interface ServerConfiguration {
'toggle-chat'?: boolean,
}
/** /**
* Parent Configuration interface * Method to open a file in the working directory and modify/create it
* *
* @see ServerConfiguration server settings per guild * @param filename name of the file
* @see UserConfiguration user configurations (only for the user for any server) * @param key key value to access
* @param value new value to assign
*/ */
export interface Configuration { export async function openConfig(filename: string, key: string, value: any): Promise<void> {
readonly name: string const fullFileName = `data/${filename}`
options: UserConfiguration | ServerConfiguration
let object: Configuration;
try {
if (await fs.access(fullFileName).then(() => true).catch(() => false)) {
const data = await fs.readFile(fullFileName, 'utf8');
object = JSON.parse(data);
object['options'][key] = value;
} else {
// Create new config
object = {
name: isServerConfigurationKey(key) ? "Server Configurations" : "User Configurations",
options: { [key]: value }
};
const directory = path.dirname(fullFileName);
await fs.mkdir(directory, { recursive: true });
}
await fs.writeFile(fullFileName, JSON.stringify(object, null, 2));
console.log(`[Util: openConfig] Updated/Created '${filename}' in working directory`);
} catch (error) {
console.error(`[Error: openConfig] Failed to process config file: ${error}`);
throw error;
}
} }
/** /**
* User config to use outside of this file * Method to obtain the configurations of the message chat/thread
*
* @param filename name of the configuration file to get
* @param callback function to allow a promise from getting the config
*/ */
export interface UserConfig { export async function getServerConfig(filename: string, callback: (config: ServerConfig | undefined) => void): Promise<void> {
readonly name: string const fullFileName = `data/${filename}`;
options: UserConfiguration
}
export interface ServerConfig { try {
readonly name: string if (await fs.access(fullFileName).then(() => true).catch(() => false)) {
options: ServerConfiguration const data = await fs.readFile(fullFileName, 'utf8');
} callback(JSON.parse(data));
} else {
export interface Channel { // Create default server config
readonly id: string const defaultConfig: ServerConfig = {
readonly name: string name: "Server Configurations",
readonly user: string options: { 'toggle-chat': true }
messages: UserMessage[] };
const directory = path.dirname(fullFileName);
await fs.mkdir(directory, { recursive: true });
await fs.writeFile(fullFileName, JSON.stringify(defaultConfig, null, 2));
console.log(`[Util: getServerConfig] Created default config '${filename}'`);
callback(defaultConfig);
}
} catch (error) {
console.error(`[Error: getServerConfig] Failed to read or create config: ${error}`);
callback(undefined);
}
} }
/** /**
* The following 2 types is allow for better readability in commands * Method to obtain the configurations of the message chat/thread
* Admin Command -> Don't run in Threads *
* User Command -> Used anywhere * @param filename name of the configuration file to get
* @param callback function to allow a promise from getting the config
*/ */
export const AdminCommand = [ export async function getUserConfig(filename: string, callback: (config: UserConfig | undefined) => void): Promise<void> {
ChannelType.GuildText const fullFileName = `data/${filename}`;
]
export const UserCommand = [ try {
ChannelType.GuildText, if (await fs.access(fullFileName).then(() => true).catch(() => false)) {
ChannelType.PublicThread, const data = await fs.readFile(fullFileName, 'utf8');
ChannelType.PrivateThread callback(JSON.parse(data));
] } else {
callback(undefined); // User config handled by Redis in messageCreate.ts
/** }
* Check if the configuration we are editing/taking from is a Server Config } catch (error) {
* @param key name of command we ran console.error(`[Error: getUserConfig] Failed to read config: ${error}`);
* @returns true if command is from Server Config, false otherwise callback(undefined);
*/ }
export function isServerConfigurationKey(key: string): key is keyof ServerConfiguration { }
return ['toggle-chat'].includes(key);
}