From a58353b9c377a9a60c4aca4a7c3bb25d367fee35 Mon Sep 17 00:00:00 2001 From: Raven Date: Wed, 2 Oct 2024 01:01:19 -0400 Subject: [PATCH] Add User Installable App --- backend-server/backend-server.js | 4 +- bot/installableApp.js | 207 +++++++++++++++++++++++++++++++ bot/userPrivacySettings.json | 3 + 3 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 bot/installableApp.js create mode 100644 bot/userPrivacySettings.json diff --git a/backend-server/backend-server.js b/backend-server/backend-server.js index b0bd105..845310c 100644 --- a/backend-server/backend-server.js +++ b/backend-server/backend-server.js @@ -45,7 +45,7 @@ app.use((req, res, next) => { conversationHistory[req.clientIp] = [{ role: 'system', - content: `My name is: ${name}, We are chatting inside ${guild} a Discord Server. ` + prompt + content: `My name is: ${name}, my Discord ID is ${req.clientIp}, We are chatting inside ${guild} a Discord Server. ` + prompt }]; } else { @@ -553,7 +553,7 @@ app.post('/api/v1/reset-conversation', (req, res) => { conversationHistory[req.clientIp] = [{ role: 'system', - content: `My name is: ${name}, We are chatting inside ${guild} a Discord Server. ` + prompt + content: `My name is: ${name}, my Discord ID is: ${req.clientIp}, We are chatting inside ${guild} a Discord Server. ` + prompt }]; } else { diff --git a/bot/installableApp.js b/bot/installableApp.js new file mode 100644 index 0000000..ae66b3d --- /dev/null +++ b/bot/installableApp.js @@ -0,0 +1,207 @@ +const { Client, GatewayIntentBits, REST, Routes, EmbedBuilder, SlashCommandBuilder } = require('discord.js'); +const axios = require('axios'); +const he = require('he'); +const fs = require('fs'); +require('dotenv').config(); +const { userResetMessages } = require('./assets/messages.js'); + +const client = new Client({ + intents: [GatewayIntentBits.Guilds] +}); + +// Load or initialize the user privacy settings +const userPrivacyFilePath = './userPrivacySettings.json'; +let userPrivacySettings = {}; +if (fs.existsSync(userPrivacyFilePath)) { + userPrivacySettings = JSON.parse(fs.readFileSync(userPrivacyFilePath)); +} + +// Save the user privacy settings +function saveUserPrivacySettings() { + fs.writeFileSync(userPrivacyFilePath, JSON.stringify(userPrivacySettings, null, 2)); +} + +// Define the commands +const commands = [ + new SlashCommandBuilder().setName('reset').setDescription('Reset the conversation'), + new SlashCommandBuilder().setName('restartcore').setDescription('Restart the core service'), + new SlashCommandBuilder().setName('chat').setDescription('Send a chat message') + .addStringOption(option => + option.setName('message') + .setDescription('Message to send') + .setRequired(true)), + new SlashCommandBuilder().setName('privacy').setDescription('Toggle between ephemeral and standard responses') +].map(command => { + const commandJSON = command.toJSON(); + + const extras = { + "integration_types": [0, 1], // 0 for guild, 1 for user + "contexts": [0, 1, 2] // 0 for guild, 1 for app DMs, 2 for GDMs and other DMs + }; + + Object.keys(extras).forEach(key => commandJSON[key] = extras[key]); + + return commandJSON; +}); + +// Register commands with Discord +const rest = new REST({ version: '10' }).setToken(process.env.THE_TOKEN_2); + +client.once('ready', async () => { + try { + console.log(`Logged in as ${client.user.tag}!`); + await rest.put(Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), { body: commands }); + console.log('Successfully registered application commands with extras.'); + } catch (error) { + console.error('Error registering commands: ', error); + } +}); + +client.on('interactionCreate', async interaction => { + if (!interaction.isCommand()) return; + + const { commandName, options } = interaction; + + if (commandName === 'reset') { + return await resetConversation(interaction); + } else if (commandName === 'restartcore') { + await restartCore(interaction); + } else if (commandName === 'chat') { + const content = options.getString('message'); + await handleUserMessage(interaction, content); + } else if (commandName === 'privacy') { + await togglePrivacy(interaction); + } +}); + +// Toggle privacy for the user +async function togglePrivacy(interaction) { + const userId = interaction.user.id; + const currentSetting = userPrivacySettings[userId] || false; + userPrivacySettings[userId] = !currentSetting; // Toggle the setting + saveUserPrivacySettings(); + + const message = userPrivacySettings[userId] + ? 'Your responses are now set to ephemeral (visible only to you).' + : 'Your responses are now standard (visible to everyone).'; + await interaction.reply({ content: message, ephemeral: true }); +} + +// Check if the user's responses should be ephemeral +function isEphemeral(userId) { + return userPrivacySettings[userId] || false; // Default to false (standard response) if not set +} + +async function handleUserMessage(interaction, content) { + const encodedMessage = he.encode(content); + + // Start typing indicator + await interaction.deferReply({ ephemeral: isEphemeral(interaction.user.id) }); + + try { + const response = await axios.post(`http://${process.env.ROOT_IP}:${process.env.ROOT_PORT}/api/v1/chat`, { + message: encodedMessage, + max_tokens: Number(process.env.MAX_TOKENS), + repeat_penalty: Number(process.env.REPEAT_PENALTY) + }, { + headers: { + 'Content-Type': 'application/json', + 'x-forwarded-for-id': interaction.user.id, + 'x-forwarded-for-name': interaction.user.username + } + }); + + const data = response.data; + await sendLongMessage(interaction, data.content); + } catch (error) { + if (error.response && error.response.status === 429) { + try { + await interaction.editReply({ content: 'I am currently busy. Please try again later.', ephemeral: isEphemeral(interaction.user.id) }); + } catch (dmError) { + console.error('Failed to send DM:', dmError); + interaction.editReply({ content: 'I am currently busy. Please try again later.', ephemeral: isEphemeral(interaction.user.id) }); + } + } else { + interaction.editReply({ content: 'Error: ' + error.message, ephemeral: isEphemeral(interaction.user.id) }); + } + } +} + +async function resetConversation(interaction) { + try { + const response = await axios.post(`${process.env.API_PATH}/reset-conversation`, {}, { + headers: { + 'Content-Type': 'application/json', + 'x-forwarded-for-id': interaction.user.id, + 'x-forwarded-for-name': interaction.user.username + } + }); + console.log(response.status); + interaction.reply({ content: 'Conversation reset successfully.', ephemeral: isEphemeral(interaction.user.id) }); + } catch (error) { + console.log(error); + interaction.reply({ content: 'Failed to reset the conversation.', ephemeral: isEphemeral(interaction.user.id) }); + } +} + +async function restartCore(interaction) { + try { + await axios.post(`${process.env.API_PATH}/restart-core`); + interaction.reply({ content: 'Core service restarted successfully.', ephemeral: isEphemeral(interaction.user.id) }); + } catch (error) { + console.log(error); + interaction.reply({ content: 'Failed to restart the core.', ephemeral: isEphemeral(interaction.user.id) }); + } +} + +async function sendLongMessage(interaction, responseText) { + const limit = 8096; + + if (responseText.length > limit) { + const lines = responseText.split('\n'); + const chunks = []; + let currentChunk = ''; + + for (const line of lines) { + if (currentChunk.length + line.length > limit) { + chunks.push(currentChunk); + currentChunk = ''; + } + currentChunk += line + '\n'; + } + + if (currentChunk.trim() !== '') { + chunks.push(currentChunk.trim()); + } + + if (chunks.length >= 80) return await interaction.reply({ content: "Response chunks too large. Try again", ephemeral: isEphemeral(interaction.user.id) }); + + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + const embed = new EmbedBuilder() + .setDescription(chunk) + .setColor("#3498DB") + .setTimestamp(); + + setTimeout(() => { + interaction.followUp({ + embeds: [embed], + ephemeral: isEphemeral(interaction.user.id) + }); + }, i * (process.env.OVERFLOW_DELAY || 3) * 1000); + } + } else { + const embed = new EmbedBuilder() + .setDescription(responseText) + .setColor("#3498DB") + .setTimestamp(); + + interaction.editReply({ + embeds: [embed], + ephemeral: isEphemeral(interaction.user.id) + }); + } +} + +// Log in the bot +client.login(process.env.THE_TOKEN_2); diff --git a/bot/userPrivacySettings.json b/bot/userPrivacySettings.json new file mode 100644 index 0000000..1263f21 --- /dev/null +++ b/bot/userPrivacySettings.json @@ -0,0 +1,3 @@ +{ + "342128351638585344": true +} \ No newline at end of file