336 lines
12 KiB
JavaScript
336 lines
12 KiB
JavaScript
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 = `
|
|
<html>
|
|
<head>
|
|
<link id="highlightjs-style" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/atom-one-dark.min.css" rel="stylesheet">
|
|
<style>
|
|
body { font-family: Arial, sans-serif; }
|
|
pre { background-color: #f4f4f4; padding: 10px; border-radius: 4px; }
|
|
</style>
|
|
</head>
|
|
<body><h1>Conversation History</h1>`;
|
|
|
|
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 += `<h3>${role}:</h3>`;
|
|
htmlContent += `<div>${renderedMarkdown}</div>`;
|
|
});
|
|
|
|
htmlContent += `</body></html>`;
|
|
|
|
// 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);
|