const { Client, GatewayIntentBits, REST, Routes, EmbedBuilder, SlashCommandBuilder, AttachmentBuilder } = require('discord.js'); const axios = require('axios'); const he = require('he'); const fs = require('fs'); const PDFDocument = require('pdfkit'); require('dotenv').config(); const markdownIt = require('markdown-it'); const puppeteer = require('puppeteer'); const highlight = require('highlight.js'); const md = new markdownIt({ highlight: function (str, lang) { if (lang && highlight.getLanguage(lang)) { try { return highlight.highlight(str, { language: lang }).value; } catch (__) {} } return ''; } }); const client = new Client({ intents: [GatewayIntentBits.Guilds] }); const userPrivacyFilePath = './userPrivacySettings.json'; let userPrivacySettings = {}; if (fs.existsSync(userPrivacyFilePath)) { userPrivacySettings = JSON.parse(fs.readFileSync(userPrivacyFilePath)); } function saveUserPrivacySettings() { fs.writeFileSync(userPrivacyFilePath, JSON.stringify(userPrivacySettings, null, 2)); } // Check if the user's responses should be ephemeral function isEphemeral(userId) { return userPrivacySettings[userId] || false; // Default to false (standard response) if not set } let userLastInteraction = {}; let conversationHistories = {}; async function resetConversationIfExpired(userId) { const now = Date.now(); if (userLastInteraction[userId] && now - userLastInteraction[userId] >= 3600000) { try { const response = await axios.post(`${process.env.API_PATH}/reset-conversation`, {}, { headers: { 'Content-Type': 'application/json', 'x-forwarded-for-id': userId, } }); console.log(`Conversation history reset for user ${userId} due to inactivity. Status: ${response.status}`); } catch (error) { console.error(`Failed to reset conversation for user ${userId}: ${error.message}`); } } } function updateUserInteractionTime(userId) { userLastInteraction[userId] = Date.now(); } // Create 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'), new SlashCommandBuilder().setName('pdf').setDescription('Download conversation history as a PDF') ]; // Add integration types and contexts to each command const commandData = commands.map(command => { const commandJSON = command.toJSON(); const extras = { "integration_types": [0, 1], // 0 for guilds, 1 for user apps "contexts": [0, 1, 2] // 0 for guilds, 1 for app DMs, 2 for group DMs }; // Merge extras into command JSON Object.assign(commandJSON, extras); return commandJSON; }); 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}!`); // Register the commands with Discord const data = await rest.put( Routes.applicationCommands(process.env.DISCORD_CLIENT_ID), { body: commandData } // Send the array of commands with integration types and contexts ); console.log('Successfully registered application commands.'); } catch (error) { console.error('Error registering commands: ', error); } }); client.on('interactionCreate', async interaction => { if (!interaction.isCommand()) return; const { commandName, options } = interaction; const userId = interaction.user.id; updateUserInteractionTime(userId); await resetConversationIfExpired(userId); if (commandName === 'reset') { 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); } else if (commandName === 'pdf') { try { await interaction.deferReply({ ephemeral: isEphemeral(interaction.user.id) }); await generatePDF(interaction); } catch (error) { console.error('Failed to generate PDF:', error); await interaction.editReply({ content: 'Failed to generate PDF.' }); } } }); async function handleUserMessage(interaction, content) { const encodedMessage = he.encode(content); // Start typing indicator await interaction.deferReply({ ephemeral: isEphemeral(interaction.user.id) }); try { // Making the API call and expecting the plain text response 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 } }); // Since response is expected to be plain text const responseText = response.data || 'Oops, something went wrong. No content received.'; await sendLongMessage(interaction, responseText); } 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 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) }); } } async function resetConversation(interaction) { try { const userId = interaction.user.id; conversationHistories[userId] = []; // Reset conversation history for the user 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 generatePDF(interaction) { try { const response = await axios.get(`http://${process.env.ROOT_IP}:${process.env.ROOT_PORT}/api/v1/conversation-history`, { headers: { 'x-forwarded-for-id': interaction.user.id } }); const conversationHistory = response.data; const filteredHistory = conversationHistory.filter(message => message.role !== 'system'); let htmlContent = `