From da9c85d90c2f93668e045d7a49106195ce667d8b Mon Sep 17 00:00:00 2001 From: Niels Simenon Date: Tue, 11 Apr 2023 00:37:03 +0200 Subject: [PATCH] Added PM support to chat. --- src/games/chat.js | 70 ++++++++++++++++++++++++++++---------------- src/games/geo.js | 2 +- src/games/hunt.js | 2 +- src/games/letters.js | 2 +- src/games/numbers.js | 27 +++++++++++++++-- src/play.js | 6 ++-- 6 files changed, 76 insertions(+), 33 deletions(-) diff --git a/src/games/chat.js b/src/games/chat.js index e88ceb4..7581653 100644 --- a/src/games/chat.js +++ b/src/games/chat.js @@ -7,9 +7,11 @@ const style = require('../utils/style'); const knex = require('../knex'); const promptRegex = new RegExp(`^${config.usernamePrefix ? `${config.usernamePrefix}?` : ''}${config.user.username}[:,\\s]`, 'ui'); -const history = new Map(); -const settings = config.chat; +const settings = { ...config.chat }; + +const history = new Map(); +const processing = new Set(); async function onStart(context) { const totalExists = await knex.schema.hasTable('chat_tokens'); @@ -40,7 +42,7 @@ async function onStart(context) { function setHistory(value, context) { if (!value) { - context.sendMessage(`Chat history is set to ${style.bold(settings.history)}`, context.room.id, { label: false }); + context.sendMessage(`Chat history is set to ${style.bold(settings.history)}`, context.room?.id, { label: false }, context.user.username); return; } @@ -50,17 +52,17 @@ function setHistory(value, context) { settings.history = newHistory; context.logger.info(`Chat history set to ${newHistory} by ${context.user.username}`); - context.sendMessage(`Chat history set to ${style.bold(newHistory)} by ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Chat history set to ${style.bold(newHistory)} by ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); return; } - context.sendMessage(`Chat history must be a valid number between 0 and 10, ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Chat history must be a valid number between 0 and 10, ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); } function setReplyWordLimit(value, context) { if (!value) { - context.sendMessage(`Chat reply word limit is set to ${style.bold(settings.replyWordLimit)}`, context.room.id, { label: false }); + context.sendMessage(`Chat reply word limit is set to ${style.bold(settings.replyWordLimit)}`, context.room?.id, { label: false }, context.user.username); return; } @@ -70,17 +72,17 @@ function setReplyWordLimit(value, context) { settings.replyWordLimit = newReplyWordLimit; context.logger.info(`Chat reply word limit set to ${newReplyWordLimit} by ${context.user.username}`); - context.sendMessage(`Chat reply word limit set to ${style.bold(newReplyWordLimit)} by ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Chat reply word limit set to ${style.bold(newReplyWordLimit)} by ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); return; } - context.sendMessage(`Chat reply word limit must be a valid number between 3 and 200, ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Chat reply word limit must be a valid number between 3 and 200, ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); } function setTemperature(value, context) { if (!value) { - context.sendMessage(`Chat temperature is set to ${style.bold(settings.temperature)}`, context.room.id, { label: false }); + context.sendMessage(`Chat temperature is set to ${style.bold(settings.temperature)}`, context.room?.id, { label: false }, context.user.username); return; } @@ -90,17 +92,17 @@ function setTemperature(value, context) { settings.temperature = newTemperature; context.logger.info(`Chat temperature set to ${newTemperature} by ${context.user.username}`); - context.sendMessage(`Chat temperature set to ${style.bold(newTemperature)} by ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Chat temperature set to ${style.bold(newTemperature)} by ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); return; } - context.sendMessage(`Chat temperature must be a valid number between 0 and 2, ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Chat temperature must be a valid number between 0 and 2, ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); } function setModel(model, context) { if (!model) { - context.sendMessage(`Chat model is set to ${style.bold(settings.model)}`, context.room.id, { label: false }); + context.sendMessage(`Chat model is set to ${style.bold(settings.model)}`, context.room?.id, { label: false }, context.user.username); return; } @@ -108,17 +110,17 @@ function setModel(model, context) { settings.model = model; context.logger.info(`Chat model set to '${model}' by ${context.user.username}`); - context.sendMessage(`Chat model set to '${style.bold(model)}' by ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Chat model set to '${style.bold(model)}' by ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); return; } - context.sendMessage(`Model '${model}' is not supported right now, ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Model '${model}' is not supported right now, ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); } function setRule(rule, context) { if (!rule) { - context.sendMessage(`Chat is ${style.bold(settings.rule)}`, context.room.id, { label: false }); + context.sendMessage(`Chat is ${style.bold(settings.rule)}`, context.room?.id, { label: false }, context.user.username); return; } @@ -126,7 +128,7 @@ function setRule(rule, context) { settings.rule = config.chat.rule; context.logger.info(`Chat reset by ${context.user.username} to be ${config.chat.rule}`); - context.sendMessage(`Chat reset by ${context.user.prefixedUsername} to be ${style.bold(config.chat.rule)}`, context.room.id, { label: false }); + context.sendMessage(`Chat reset by ${context.user.prefixedUsername} to be ${style.bold(config.chat.rule)}`, context.room?.id, { label: false }, context.user.username); return; } @@ -135,12 +137,12 @@ function setRule(rule, context) { settings.rule = `${rule.replace(/\.+$/, '')}`; context.logger.info(`Chat set by ${context.user.username} to be ${rule}`); - context.sendMessage(`Chat set by ${context.user.prefixedUsername} to be ${style.bold(rule)}`, context.room.id, { label: false }); + context.sendMessage(`Chat set by ${context.user.prefixedUsername} to be ${style.bold(rule)}`, context.room?.id, { label: false }, context.user.username); return; } - context.sendMessage(`Chat rule must be at least 3 characters long, ${context.user.prefixedUsername}`, context.room.id, { label: false }); + context.sendMessage(`Chat rule must be at least 3 characters long, ${context.user.prefixedUsername}`, context.room?.id, { label: false }, context.user.username); } async function getTokens(username) { @@ -163,7 +165,7 @@ async function resetTokens(username) { .delete(); } -async function onCommand(args, context) { +async function onCommand(args, context, isConversation) { if (context.subcommand === 'history' && config.operators.includes(context.user.username)) { setHistory(args[0], context); return; @@ -193,7 +195,7 @@ async function onCommand(args, context) { const username = args[0] ? args[0].replace(new RegExp(`^${config.usernamePrefix}`), '') : context.user.username; const tokens = await getTokens(username); - context.sendMessage(`${args[0] ? `${style.bold(username)} has` : 'You have'} ${style.bold(config.chat.userTokenLimit - tokens)} chat tokens remaining. They will be returned gradually over ${config.chat.userTokenPeriod} hours.`, context.room.id, { label: false }); + context.sendMessage(`${args[0] ? `${style.bold(username)} has` : 'You have'} ${style.bold(config.chat.userTokenLimit - tokens)} chat tokens remaining. They will be returned gradually over ${config.chat.userTokenPeriod} hours.`, context.room?.id, { label: false }, context.user.username); return; } @@ -203,19 +205,26 @@ async function onCommand(args, context) { await resetTokens(username); - context.sendMessage(args[0] ? `Chat tokens reset for ${style.bold(username)}` : 'Chat tokens reset', context.room.id, { label: false }); + context.sendMessage(args[0] ? `Chat tokens reset for ${style.bold(username)}` : 'Chat tokens reset', context.room?.id, { label: false }, context.user.username); return; } + if (processing.has(context.user.username)) { + context.logger.info(`Skipped prompt from ${context.user.username} due processing lock`); + return; + } + const prompt = args.join(' '); + processing.add(context.user.username); + try { const usedTokens = await getTokens(context.user.username); if (usedTokens >= config.chat.userTokenLimit) { context.logger.info(`${context.user.username} was rate limited at ${usedTokens}: ${prompt}`); - context.sendMessage(`Sorry, I love talking with you ${context.user.prefixedUsername}, but I need to take a break :( Check ${config.prefix}chat:tokens for more information.`, context.room.id, { label: false }); + context.sendMessage(`Sorry, I love talking with you ${context.user.prefixedUsername}, but I need to take a break :( Check ${config.prefix}chat:tokens for more information.`, context.room?.id, { label: false }, context.user.username); return; } @@ -247,7 +256,12 @@ async function onCommand(args, context) { const curatedContent = reply.content.replace(/\n+/g, '. '); context.logger.info(`OpenAI ${config.chat.model} replied to ${context.user.username} (${res.body.usage.total_tokens} tokens, ${(usedTokens || 0) + res.body.usage.total_tokens}/${config.chat.userTokenLimit} used): ${curatedContent}`); - context.sendMessage(`${context.user.prefixedUsername}: ${curatedContent}`, context.room.id, { label: false }); + + if (isConversation) { + context.sendMessage(`${curatedContent}`, context.room?.id, { label: false }, context.user.username); + } else { + context.sendMessage(`${context.user.prefixedUsername}: ${curatedContent}`, context.room.id, { label: false }); + } await knex('chat_tokens').insert({ user_id: context.user.username, @@ -260,13 +274,17 @@ async function onCommand(args, context) { } } catch (error) { context.logger.error(error.response ? `${error.response.status}: ${JSON.stringify(error.response.data)}` : error.message); - context.sendMessage('Sorry, I can\'t think right now', context.room.id, { label: false }); + context.sendMessage('Sorry, I can\'t think right now', context.room?.id, { label: false }, context.user.username); } + + processing.delete(context.user.username); } function onMessage(message, context) { - if (promptRegex.test(message.body)) { - onCommand([message.body.replace(promptRegex, '').trim()], context); + const isConversation = message.recipient === config.user.username && !context.room && !context.containsCommand; + + if (promptRegex.test(message.body) || isConversation) { + onCommand([message.body.replace(promptRegex, '').trim()], context, isConversation); } } diff --git a/src/games/geo.js b/src/games/geo.js index 5014878..27fe107 100644 --- a/src/games/geo.js +++ b/src/games/geo.js @@ -97,7 +97,7 @@ async function onCommand(args, context) { async function onMessage(message, context) { ['countries', 'states', 'flags'].forEach(async (type) => { - const game = games[type].get(context.room.id); + const game = games[type].get(context.room?.id); if (!game) { return; diff --git a/src/games/hunt.js b/src/games/hunt.js index d6f386b..ac64d08 100644 --- a/src/games/hunt.js +++ b/src/games/hunt.js @@ -145,7 +145,7 @@ function onCommand(args, context) { } function onMessage(message, context) { - if (games.has(context.room.id)) { + if (games.has(context.room?.id)) { const letter = message.body.match(/^\s*\w\s*$/)?.[0]; const word = message.body.match(/^\s*\w{2,}\s*$/)?.[0]; diff --git a/src/games/letters.js b/src/games/letters.js index a28d92f..3f59527 100755 --- a/src/games/letters.js +++ b/src/games/letters.js @@ -88,7 +88,7 @@ async function play(context) { game.state = 'words'; game.counts = countLetters(game.word); - context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(settings.timeout))}, let's start!`, context.room.id); + context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(settings.timeout))} seconds, let's start!`, context.room.id); try { await timers.setTimeout((settings.timeout / 3) * 1000, null, { signal: game.ac.signal }); diff --git a/src/games/numbers.js b/src/games/numbers.js index e0ffde9..5e5fca9 100644 --- a/src/games/numbers.js +++ b/src/games/numbers.js @@ -44,7 +44,7 @@ mathImport({ divide, }, { override: true }); -const settings = config.numbers; +const settings = { ...config.numbers }; const games = new Map(); /* eslint-disable no-irregular-whitespace */ @@ -89,6 +89,24 @@ function countNumbers(numbers) { return numbers.reduce((acc, number) => ({ ...acc, [number]: (acc[number] || 0) + 1 }), {}); } +function setTimeout(timeout, context) { + if (!timeout) { + context.sendMessage(`Timeout is set to ${style.bold(settings.timeout)}`, context.room.id); + return; + } + + const parsedTimeout = Number(timeout); + + if (Number.isNaN(parsedTimeout) || parsedTimeout < 10 || parsedTimeout > 300) { + context.sendMessage('Timeout must be a number between 10 and 300', context.room.id); + return; + } + + settings.timeout = parsedTimeout; + + context.sendMessage(`Timeout set to ${style.bold(settings.timeout)} by ${context.user.prefixedUsername}`, context.room.id); +} + function getWinnerText(game) { if (game.state === 'pick') { return null; @@ -327,6 +345,11 @@ function onCommand(args, context) { return; } + if (context.subcommand === 'timeout') { + setTimeout(args[0], context); + return; + } + if (['calculate', 'calc', 'solve'].includes(context.subcommand || context.command)) { solve(args.join(' '), context); // two from the top, four from the bottom, please Rachel return; @@ -343,7 +366,7 @@ function onCommand(args, context) { } function onMessage(message, context) { - const game = games.get(context.room.id); + const game = games.get(context.room?.id); const body = message.originalBody || message.body; // * gets resolved to if (game?.state === 'pick') { diff --git a/src/play.js b/src/play.js index 964a409..c76f816 100755 --- a/src/play.js +++ b/src/play.js @@ -124,8 +124,8 @@ async function getGames(bot, identifier) { if (config.platform === 'schat') { bot.socket.transmit('message', { roomId, - recipient, - type: recipient && options?.type !== 'message' ? 'whisper' : 'message', + recipient: options?.type === 'whisper' || !roomId ? recipient : null, + type: options?.type || 'message', body: curatedBody, style: { ...config.style, ...options?.style }, }); @@ -248,6 +248,8 @@ function onMessage(message, bot, games) { ...game, bot, message, + containsCommand: command, + containsSubcommand: subcommand, user: user && { ...user, points: points[game.key]?.[`${user.id}:${user.username}`] || 0,