diff --git a/_opt/responses.js b/_opt/responses.js index 7f088d2..e803dc3 100644 --- a/_opt/responses.js +++ b/_opt/responses.js @@ -74,17 +74,15 @@ function splitMessage(text, maxLength = MAX_DISCORD_MSG_LENGTH) { /** * Determine whether the bot should respond to a message. - * Triggers when the bot is mentioned or when the message is a direct reply. - * @param {Message} message - The incoming Discord message. - * @param {string} botId - The bot user ID. - * @param {object} logger - Logger for debugging. - * @returns {Promise} True if the bot should respond. + * Controlled by enableMentions and enableReplies in config. */ -async function shouldRespond(message, botId, logger) { +async function shouldRespond(message, botId, cfg, logger) { if (message.author.bot || !botId) return false; - const isMention = message.mentions.users.has(botId); + const enableMentions = cfg.enableMentions ?? true; + const enableReplies = cfg.enableReplies ?? true; + const isMention = enableMentions && message.mentions.users.has(botId); let isReply = false; - if (message.reference?.messageId) { + if (enableReplies && message.reference?.messageId) { try { const ref = await message.channel.messages.fetch(message.reference.messageId); isReply = ref.author.id === botId; @@ -248,7 +246,8 @@ async function onMessage(client, cfg, message) { const logger = client.logger; const botId = client.user?.id; client.logger.debug(`[onMessage] Received message ${message.id} from ${message.author.id}`); - if (!(await shouldRespond(message, botId, logger))) return; + // Check if bot should respond, based on config (mentions/replies) + if (!(await shouldRespond(message, botId, cfg, logger))) return; await message.channel.sendTyping(); // Determine channel/thread key for context diff --git a/_opt/responsesRandomizer.js b/_opt/responsesRandomizer.js new file mode 100644 index 0000000..85a97e5 --- /dev/null +++ b/_opt/responsesRandomizer.js @@ -0,0 +1,37 @@ +/** + * responsesRandomizer module + * Listens to all guild messages and randomly sends a generated narrative. + * Uses sendNarrative from responses.js. + */ + +import { sendNarrative } from './responses.js'; + +/** + * Initialize the responsesRandomizer module. + * @param {import('discord.js').Client} client - Discord client instance. + * @param {object} clientConfig - Full client configuration object. + */ +export async function init(client, clientConfig) { + const cfg = clientConfig.responsesRandomizer; + const chance = Number(cfg.chance); + if (isNaN(chance) || chance <= 0) { + client.logger.warn(`[module:responsesRandomizer] Invalid chance value: ${cfg.chance}. Module disabled.`); + return; + } + client.logger.info(`[module:responsesRandomizer] Enabled with chance=${chance}`); + + client.on('messageCreate', async (message) => { + try { + // Skip bot messages or non-guild messages + if (message.author.bot || !message.guild) return; + const content = message.content?.trim(); + if (!content) return; + // Roll the dice + if (Math.random() > chance) return; + // Generate and send narrative + await sendNarrative(client, clientConfig.responses, message.channel.id, content); + } catch (err) { + client.logger.error(`[module:responsesRandomizer] Error processing message: ${err.message}`); + } + }); +} diff --git a/_opt/tempvc.js b/_opt/tempvc.js index c74e8ff..7d2b9a6 100644 --- a/_opt/tempvc.js +++ b/_opt/tempvc.js @@ -356,6 +356,8 @@ export const commands = [ } await interaction.reply({ content: `Preset **${name}** saved.`, flags: MessageFlags.Ephemeral }); } else if (sub === 'reset') { + // Defer to avoid Discord interaction timeout during reset + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); // reset channel to default parameters const owner = interaction.member; const display = owner.displayName || owner.user.username; @@ -378,7 +380,7 @@ export const commands = [ ]); sess.roleId = guild.roles.everyone.id; await client.pb.updateOne('tempvc_sessions', sess.pbId, { roleId: guild.roles.everyone.id, invitedUserIds: [] }); - await interaction.reply({ content: 'Channel has been reset to default settings.', flags: MessageFlags.Ephemeral }); + await interaction.editReply({ content: 'Channel has been reset to default settings.' }); } else if (sub === 'mode') { const mode = interaction.options.getString('mode', true); sess.mode = mode; @@ -396,13 +398,15 @@ export const commands = [ await client.pb.updateOne('tempvc_sessions', sess.pbId, { mode }); await interaction.reply({ content: `Channel mode set to **${mode}**.`, flags: MessageFlags.Ephemeral }); } else if (sub === 'restore') { + // Defer initial reply to extend Discord interaction window + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); const name = interaction.options.getString('name', true); const preset = await client.pb.getFirst( 'tempvc_presets', `guildId = "${guild.id}" && userId = "${interaction.user.id}" && name = "${name}"` ); if (!preset) { - return interaction.reply({ content: `Preset **${name}** not found.`, flags: MessageFlags.Ephemeral }); + return interaction.editReply({ content: `Preset **${name}** not found.` }); } // apply settings await voice.setName(preset.channelName); @@ -433,7 +437,7 @@ export const commands = [ { roleId: preset.roleId || '', mode } ); sess.roleId = preset.roleId || ''; - await interaction.reply({ content: `Preset **${name}** restored (mode: ${mode}).`, flags: MessageFlags.Ephemeral }); + await interaction.editReply({ content: `Preset **${name}** restored (mode: ${mode}).` }); } } catch (err) { client.logger.error(`[module:tempvc][vc] ${err.message}`); diff --git a/config.js b/config.js index fc9e171..db5446a 100644 --- a/config.js +++ b/config.js @@ -53,6 +53,8 @@ export default { defaultTemperature: 0.7, conversationExpiry: 30 * 60 * 1000, minScore: 1.0, + enableMentions: true, + enableReplies: true, tools: { webSearch: true, fileSearch: false, @@ -174,6 +176,8 @@ export default { defaultTemperature: 0.7, conversationExpiry: 30 * 60 * 1000, minScore: 0.5, + enableMentions: true, + enableReplies: true, tools: { webSearch: false, fileSearch: false, @@ -260,6 +264,8 @@ export default { defaultTemperature: 0.7, conversationExpiry: 30 * 60 * 1000, minScore: 0, + enableMentions: true, + enableReplies: true, tools: { webSearch: false, fileSearch: false, @@ -282,6 +288,82 @@ export default { }, + { + id: 'GRANDPA', + enabled: true, + owner: process.env.OWNER_ID, + + discord: { + appId: process.env.GRANDPA_DISCORD_APPID, + token: process.env.GRANDPA_DISCORD_TOKEN + }, + + logging: { + console: { + enabled: true, + colorize: true, + level: 'silly', + }, + file: { + dateFormat: 'YYYY-MM-DD', + timestampFormat: 'YYYY-MM-DD HH:mm:ss', + combined: { + enabled: true, + level: 'silly', + location: 'logs', + maxSize: '12m', + maxFiles: '30d', + }, + error: { + enabled: true, + level: 'error', + location: 'logs', + maxSize: '12m', + maxFiles: '365d', + } + } + }, + + pocketbase: { + url: process.env.SHARED_POCKETBASE_URL, + username: process.env.SHARED_POCKETBASE_USERNAME, + password: process.env.SHARED_POCKETBASE_PASSWORD + }, + + responses: { + apiKey: process.env.SHARED_OPENAI_API_KEY, + defaultModel: 'gpt-4.1', + defaultMaxTokens: 200, + defaultTemperature: 0.7, + conversationExpiry: 30 * 60 * 1000, + minScore: 0, + enableMentions: false, + enableReplies: true, + tools: { + webSearch: false, + fileSearch: false, + imageGeneration: false, + }, + imageGeneration: { + defaultModel: 'gpt-image-1', + defaultQuality: 'standard', + imageSavePath: './images' + } + }, + + responsesRandomizer: { + chance: 0.01, + }, + modules: [ + 'botUtils', + 'pbUtils', + 'responses', + 'responsesPrompt', + 'responsesRandomizer' + ] + + }, + { id: 'Smuuush', enabled: true, @@ -331,6 +413,8 @@ export default { defaultTemperature: 0.7, conversationExpiry: 30 * 60 * 1000, minScore: 0, + enableMentions: true, + enableReplies: true, tools: { webSearch: false, fileSearch: false, diff --git a/docs/config.js.sample b/docs/config.js.sample index 66de975..f7df95e 100644 --- a/docs/config.js.sample +++ b/docs/config.js.sample @@ -97,6 +97,8 @@ export default { systemPromptPath: './prompts/IO3.txt', conversationExpiry: 30 * 60 * 1000, minScore: 1.0, + enableMentions: true, + enableReplies: true, tools: { webSearch: false, fileSearch: false, @@ -114,18 +116,27 @@ export default { baseOutput: 1000, commendationValue: 1.0, citationValue: 1.2, + cooldown: 0, decay: 90, schedule: '0 0 * * 0', }, // Modules to load for this client modules: [ + 'ansi', + 'botUtils', 'pbUtils', + 'gitUtils', + 'condimentX', 'responses', + 'responsesPrompt', 'responsesQuery', + 'responsesRandomizer', + 'messageQueue-example', 'scorekeeper', 'scorekeeper-example', - 'condimentX', + 'scExecHangarStatus', + 'tempvc', ], }, ], diff --git a/package-lock.json b/package-lock.json index 33460e8..a471e52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,9 +42,9 @@ } }, "node_modules/@discordjs/builders": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.1.tgz", - "integrity": "sha512-2zDAVuoeAkdv0YQzYKO8vZfaDfB+1KZ60ymBKtD7QDpsh6lzAnQSUBLqeRkhlons6BT9+yRctOh9fPy94w6kDA==", + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.2.tgz", + "integrity": "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==", "license": "Apache-2.0", "dependencies": { "@discordjs/formatters": "^0.6.1", @@ -63,9 +63,9 @@ } }, "node_modules/@discordjs/builders/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.3.tgz", + "integrity": "sha512-vijevLh06Gtmex6BQzc9jRrGce6La0qnsF4bKwKM2L1ou0/sbJIOAkg7wz6YLLaodnUwQLljIhtrGxnkMjc1Ew==", "license": "MIT" }, "node_modules/@discordjs/collection": { @@ -93,9 +93,9 @@ } }, "node_modules/@discordjs/formatters/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.3.tgz", + "integrity": "sha512-vijevLh06Gtmex6BQzc9jRrGce6La0qnsF4bKwKM2L1ou0/sbJIOAkg7wz6YLLaodnUwQLljIhtrGxnkMjc1Ew==", "license": "MIT" }, "node_modules/@discordjs/rest": { @@ -134,9 +134,9 @@ } }, "node_modules/@discordjs/rest/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.3.tgz", + "integrity": "sha512-vijevLh06Gtmex6BQzc9jRrGce6La0qnsF4bKwKM2L1ou0/sbJIOAkg7wz6YLLaodnUwQLljIhtrGxnkMjc1Ew==", "license": "MIT" }, "node_modules/@discordjs/util": { @@ -187,9 +187,9 @@ } }, "node_modules/@discordjs/ws/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.3.tgz", + "integrity": "sha512-vijevLh06Gtmex6BQzc9jRrGce6La0qnsF4bKwKM2L1ou0/sbJIOAkg7wz6YLLaodnUwQLljIhtrGxnkMjc1Ew==", "license": "MIT" }, "node_modules/@sapphire/async-queue": { @@ -226,9 +226,9 @@ } }, "node_modules/@types/node": { - "version": "22.15.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.2.tgz", - "integrity": "sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==", + "version": "22.15.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.10.tgz", + "integrity": "sha512-j2U4KRlgZ9Q8tVO/KDAvXu68vutX4kxoRysL6Q22oEU4ZFT2A16aIyqiIWAwFBZkvKep2UOcSGNoLe/6BI0nrg==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -402,12 +402,12 @@ "license": "MIT" }, "node_modules/discord.js": { - "version": "14.19.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.19.1.tgz", - "integrity": "sha512-r5jsPyaeoCrRGbdse4vQNbHAsoc2zuueyiTFJ2Ce7BiaJak9OldzKZWaWGwKdCFDH3zXlthU1hHXkx1EswKZCA==", + "version": "14.19.3", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.19.3.tgz", + "integrity": "sha512-lncTRk0k+8Q5D3nThnODBR8fR8x2fM798o8Vsr40Krx0DjPwpZCuxxTcFMrXMQVOqM1QB9wqWgaXPg3TbmlHqA==", "license": "Apache-2.0", "dependencies": { - "@discordjs/builders": "^1.11.1", + "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.5.0", @@ -429,9 +429,9 @@ } }, "node_modules/discord.js/node_modules/discord-api-types": { - "version": "0.38.1", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.1.tgz", - "integrity": "sha512-vsjsqjAuxsPhiwbPjTBeGQaDPlizFmSkU0mTzFGMgRxqCDIRBR7iTY74HacpzrDV0QtERHRKQEk1tq7drZUtHg==", + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.3.tgz", + "integrity": "sha512-vijevLh06Gtmex6BQzc9jRrGce6La0qnsF4bKwKM2L1ou0/sbJIOAkg7wz6YLLaodnUwQLljIhtrGxnkMjc1Ew==", "license": "MIT" }, "node_modules/dotenv": { @@ -909,9 +909,9 @@ } }, "node_modules/openai": { - "version": "4.96.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.96.0.tgz", - "integrity": "sha512-dKoW56i02Prv2XQolJ9Rl9Svqubqkzg3QpwEOBuSVZLk05Shelu7s+ErRTwFc1Bs3JZ2qBqBfVpXQiJhwOGG8A==", + "version": "4.97.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.97.0.tgz", + "integrity": "sha512-LRoiy0zvEf819ZUEJhgfV8PfsE8G5WpQi4AwA1uCV8SKvvtXQkoWUFkepD6plqyJQRghy2+AEPQ07FrJFKHZ9Q==", "license": "Apache-2.0", "dependencies": { "@types/node": "^18.11.18", @@ -939,9 +939,9 @@ } }, "node_modules/openai/node_modules/@types/node": { - "version": "18.19.87", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.87.tgz", - "integrity": "sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==", + "version": "18.19.94", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.94.tgz", + "integrity": "sha512-6zRqqZiwWiA9nwKamxQzEogpoCf78fpzTOxxBhGDgQci1FJwm3udGjj4NEceGN7CZdJb51iW1+K6z4wcT8gdlQ==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -1178,9 +1178,9 @@ } }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", "engines": { "node": ">=10.0.0"