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'); // Import 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 ''; // use external default escaping } }); 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)); } // Store the last interaction time and conversation history for each user let userLastInteraction = {}; let conversationHistories = {}; // Function to reset conversation history if more than one hour has passed async function resetConversationIfExpired(userId) { const now = Date.now(); if (userLastInteraction[userId] && now - userLastInteraction[userId] >= 3600000) { // 1 hour in milliseconds 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': userId, } }); console.log(response.status); console.log(`Conversation history reset for user ${userId} due to inactivity.`); } catch (error) { console.log(error); console.log(`Conversation history failed to reset for user ${userId} due to inactivity.`); } } } // Function to update last interaction time for the user function updateUserInteractionTime(userId) { userLastInteraction[userId] = Date.now(); } // Register 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') ].map(command => { const commandJSON = command.toJSON(); const extras = { "integration_types": [0, 1], "contexts": [0, 1, 2] }; Object.keys(extras).forEach(key => commandJSON[key] = extras[key]); 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}!`); 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; const userId = interaction.user.id; // Update the last interaction time for the user updateUserInteractionTime(userId); // Reset the conversation history if an hour has passed 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.' }); } } }); // 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 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 restartCore(interaction) { await interaction.deferReply(); try { await axios.post(`${process.env.API_PATH}/restart-core`); interaction.editReply({ content: 'Core service restarted successfully.', ephemeral: isEphemeral(interaction.user.id) }); } catch (error) { console.log(error); interaction.editReply({ 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) }); } } async function generatePDF(interaction) { try { // Fetch the conversation history from the API 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; // Filter out system messages const filteredHistory = conversationHistory.filter(message => message.role !== 'system'); // Convert the conversation history to HTML with syntax highlighting let htmlContent = `

Conversation History

`; filteredHistory.forEach(message => { const role = message.role.charAt(0).toUpperCase() + message.role.slice(1); const content = he.decode(message.content); // Decode any HTML entities const renderedMarkdown = md.render(content); // Convert Markdown to HTML with syntax highlighting // Append the formatted HTML to the document htmlContent += `

${role}:

`; htmlContent += `
${renderedMarkdown}
`; }); htmlContent += ``; // Use puppeteer to generate a PDF from the HTML content const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const page = await browser.newPage(); await page.setContent(htmlContent); const pdfPath = `./conversation_${interaction.user.id}.pdf`; await page.pdf({ path: pdfPath, format: 'A4' }); await browser.close(); // Send the PDF back to the user const attachment = new AttachmentBuilder(pdfPath); await interaction.editReply({ content: 'Here is your conversation history in PDF format:', files: [attachment], ephemeral: isEphemeral(interaction.user.id) }); // Clean up the PDF file after sending it fs.unlinkSync(pdfPath); } catch (error) { console.error('Failed to generate PDF:', error); await interaction.reply({ content: 'Failed to generate PDF.', ephemeral: isEphemeral(interaction.user.id) }); } } // Log in the bot client.login(process.env.THE_TOKEN_2);