From 243128e9254f4cf64f9b21689850340fbf38de2e Mon Sep 17 00:00:00 2001 From: dlinux-host Date: Thu, 22 May 2025 03:59:03 -0400 Subject: [PATCH] fix user installable bot - if no container is running let the user know --- user-bot.js | 131 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 53 deletions(-) diff --git a/user-bot.js b/user-bot.js index 7c355ed..44debb9 100644 --- a/user-bot.js +++ b/user-bot.js @@ -3,10 +3,11 @@ import jsonfile from 'jsonfile'; import unirest from 'unirest'; import { readFileSync } from 'fs'; import mysql from 'mysql2'; -const userWorkingDirectories = new Map(); import fs from 'fs'; import cmd from 'cmd-promise'; -let sshSurfID; // Variable to store the user ID from the database + +const userWorkingDirectories = new Map(); +let sshSurfID; // Variable to store the user ID from the database // Paths to config and tokens files const tokensFile = './tokens.json'; @@ -65,7 +66,7 @@ async function fetchAndSaveToken(sshSurfID, interaction) { .send({ "username": `${sshSurfID}`, "password": config.password.toString() }) .then((tokenInfo) => { const tokens = loadTokens(); - tokens[sshSurfID] = tokenInfo.body.token; // Save the new token for sshSurfID + tokens[sshSurfID] = tokenInfo.body.token; // Save the new token for sshSurfID saveTokens(tokens); return tokenInfo.body.token; }) @@ -99,10 +100,10 @@ async function makeApiRequest(endpoint, token, interaction, method = 'get', body } return request.then(response => { - if (response.error) { - console.error('API Error:', response.error); + if (response.error || !response.body) { + console.error('API Error:', response.error || 'No response body'); sendSexyEmbed("Error", "An error occurred while communicating with the API.", interaction); - throw response.error; + throw new Error('API request failed'); } return response.body; }); @@ -149,8 +150,8 @@ async function togglePrivacy(interaction) { userPrivacySettings[userId] = !currentSetting; // Toggle the setting saveUserPrivacySettings(); - const message = userPrivacySettings[userId] - ? 'Your responses are now set to ephemeral (visible only to you).' + 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.editReply({ content: message, ephemeral: true }); } @@ -177,7 +178,7 @@ const commands = [ .addStringOption(option => option.setName('command').setDescription('Command to execute').setRequired(true)), new SlashCommandBuilder().setName('notify').setDescription('Send a notification to Discord') .addStringOption(option => option.setName('message').setDescription('Message to send').setRequired(true)), - new SlashCommandBuilder().setName('privacy').setDescription('Toggle ephemeral responses') // New command to toggle privacy + new SlashCommandBuilder().setName('privacy').setDescription('Toggle ephemeral responses') ]; // Register commands with Discord @@ -201,10 +202,10 @@ const rest = new REST({ version: '10' }).setToken(config.token); return jsonCommand; }); - // Register commands with Discord, making sure all commands are sent in an array - const data = await rest.put( + // Register commands with Discord + await rest.put( Routes.applicationCommands(config.clientId), - { body: commandsWithExtras } // Send all commands in one array + { body: commandsWithExtras } ); console.log('Successfully reloaded application (/) commands.'); @@ -216,13 +217,14 @@ const rest = new REST({ version: '10' }).setToken(config.token); // Handle bot interactions client.on('interactionCreate', async interaction => { if (!interaction.isCommand()) return; - // Check if the command is /privacy, always make it ephemeral - const isPrivacyCommand = interaction.commandName === 'privacy'; - - // Defer the reply based on whether it's the privacy command or not - await interaction.deferReply({ ephemeral: isPrivacyCommand || isEphemeral(interaction.user.id) }); - // First, we fetch the sshSurfID from the database using interaction.user.id + // Check if the command is /privacy, always make it ephemeral + const isPrivacyCommand = interaction.commandName === 'privacy'; + + // Defer the reply based on whether it's the privacy command or not + await interaction.deferReply({ ephemeral: isPrivacyCommand || isEphemeral(interaction.user.id) }); + + // Fetch the sshSurfID from the database using interaction.user.id let sshSurfID = await new Promise((resolve, reject) => { connection.query( "SELECT uid FROM users WHERE discord_id = ?", @@ -251,7 +253,7 @@ client.on('interactionCreate', async interaction => { return; } - // Once sshSurfID is set, we proceed with token fetching and API requests + // Once sshSurfID is set, proceed with token fetching and API requests const apiToken = await getToken(sshSurfID, interaction); try { switch (interaction.commandName) { @@ -259,14 +261,14 @@ client.on('interactionCreate', async interaction => { const helloResponse = await makeApiRequest('/hello', apiToken, interaction); sendSexyEmbed("Hello", `Message: ${helloResponse.message}`, interaction, isEphemeral(interaction.user.id)); break; - + case 'name': const nameResponse = await makeApiRequest('/name', apiToken, interaction); sendSexyEmbedWithFields('Username', [ { name: 'Username', value: nameResponse.message } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'start': const startResponse = await makeApiRequest('/start', apiToken, interaction); sendSexyEmbedWithFields('Start Server', [ @@ -274,7 +276,7 @@ client.on('interactionCreate', async interaction => { { name: 'Message', value: startResponse.message } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'stop': const stopResponse = await makeApiRequest('/stop', apiToken, interaction); sendSexyEmbedWithFields('Stop Server', [ @@ -282,7 +284,7 @@ client.on('interactionCreate', async interaction => { { name: 'Message', value: stopResponse.message } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'restart': const restartResponse = await makeApiRequest('/restart', apiToken, interaction); sendSexyEmbedWithFields('Restart Server', [ @@ -290,7 +292,7 @@ client.on('interactionCreate', async interaction => { { name: 'Message', value: restartResponse.message } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'info': const infoResponse = await makeApiRequest('/info', apiToken, interaction); const containerName = infoResponse.data?.name || 'N/A'; @@ -305,7 +307,7 @@ client.on('interactionCreate', async interaction => { const startedAt = infoResponse.data?.state?.StartedAt || 'N/A'; const image = infoResponse.data?.image || 'N/A'; const createdAt = infoResponse.data?.created || 'N/A'; - + sendSexyEmbedWithFields('Container Info', [ { name: 'Name', value: containerName }, { name: 'IP Address', value: ipAddress }, @@ -320,7 +322,7 @@ client.on('interactionCreate', async interaction => { { name: 'Created At', value: createdAt } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'stats': const statsResponse = await makeApiRequest('/stats', apiToken, interaction); sendSexyEmbedWithFields('Container Stats', [ @@ -328,39 +330,39 @@ client.on('interactionCreate', async interaction => { { name: 'CPU Usage', value: statsResponse.data.cpu } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'time': const timeResponse = await makeApiRequest('/time', apiToken, interaction); sendSexyEmbedWithFields('Container Expire Time', [ { name: 'Expire Date', value: timeResponse.expireDate } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'root-password': const rootPassResponse = await makeApiRequest('/rootpass', apiToken, interaction); sendSexyEmbedWithFields('Root Password', [ { name: 'New Root Password', value: rootPassResponse.newRootPass } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'new-api-key': const newKeyResponse = await makeApiRequest('/new-key', apiToken, interaction); sendSexyEmbedWithFields('New API Key', [ { name: 'New API Key', value: newKeyResponse.newAPIKey } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'key-expire-time': const keyTimeResponse = await makeApiRequest('/key-time', apiToken, interaction); sendSexyEmbedWithFields('API Key Expire Time', [ { name: 'Expire Date', value: keyTimeResponse.expireDate } ], interaction, isEphemeral(interaction.user.id)); break; - + case 'x': const command = interaction.options.getString('command'); let userPWD = userWorkingDirectories.get(sshSurfID) || '/'; - + if (command.startsWith('cd')) { let argscmd = command.replace('cd ', '').trim(); if (argscmd === '..') { @@ -374,14 +376,14 @@ client.on('interactionCreate', async interaction => { } return; } - + if (argscmd === '~') { userPWD = '/root'; userWorkingDirectories.set(sshSurfID, userPWD); await interaction.editReply(`Directory changed to: ${userPWD}`); return; } - + let newPWD; if (argscmd.startsWith('/')) { newPWD = argscmd; @@ -389,59 +391,82 @@ client.on('interactionCreate', async interaction => { newPWD = `${userPWD}/${argscmd}`; } newPWD = newPWD.replace(/([^:]\/)\/+/g, "$1"); - + if (argscmd.includes('../')) { const numDirsBack = argscmd.split('../').length - 1; - newPWD = RemoveLastDirectoryPartOf(userPWD, numDirsBack); + newPWD = userPWD.split('/').slice(0, -numDirsBack).join('/') || '/'; } - + userWorkingDirectories.set(sshSurfID, newPWD); await interaction.editReply(`Directory changed to: ${newPWD}`); return; } - + const execResponse = await makeApiRequest('/exec', apiToken, interaction, 'post', { cmd: command, pwd: userPWD }); - + console.log('execResponse:', JSON.stringify(execResponse, null, 2)); + + // Handle case where container does not exist + if (execResponse?.success === false && execResponse.message?.includes('no such container')) { + sendSexyEmbed( + 'Error', + 'No running container found. Please use the `/generate` command in the Discord-Linux server to create a new container.', + interaction, + isEphemeral(interaction.user.id) + ); + return; + } + + // Check if execResponse has the expected structure + if (!execResponse || !execResponse.stdout) { + console.error('Invalid execResponse:', execResponse); + await interaction.editReply('Error: No output received from the command.'); + return; + } + if (execResponse.stdout.length > 2020) { const tempFilePath = '/tmp/paste'; const pasteCommand = `Command: ${command} | Container Owner: ${interaction.user.username}\n${execResponse.stdout}`; fs.writeFileSync(tempFilePath, pasteCommand, (err) => { if (err) { - console.error(err); - return; + console.error('Error writing to file:', err); } }); - + const pasteout = await cmd("sleep 2; cat /tmp/paste | dpaste"); - + const mainEmbed = new EmbedBuilder() .setColor('#0099ff') .setTitle(`Container Owner: ${interaction.user.username}`) .setDescription(`The command: ${command} was too large for Discord.`) .addFields({ name: 'Please check the below output log:', - value: pasteout.stdout.replace("Pro tip: you can password protect your paste just by typing a username and password after your paste command.", "") + value: pasteout.stdout + .replace("Pro tip: you can password protect your paste just by typing a username and password after your paste command.", "") .replace("Paste Saved: ", "") .replace("-------------------------------------------------------", "") }) .setFooter({ text: `Requested by ${interaction.user.username}`, iconURL: `${interaction.user.displayAvatarURL()}` }); - + await interaction.editReply({ embeds: [mainEmbed] }); - + + // Clean up the temporary file + fs.unlink(tempFilePath, (err) => { + if (err) console.error('Error cleaning up temp file:', err); + }); } else { let replyMessage = `\`\`\`\n${execResponse.stdout || 'No output'}\n\`\`\``; - + if (execResponse.stderr && execResponse.stderr.trim()) { replyMessage += `\n**Error:**\n\`\`\`\n${execResponse.stderr}\n\`\`\``; } - + await interaction.editReply(replyMessage); } break; - + case 'notify': const message = interaction.options.getString('message'); const notifyResponse = await makeApiRequest('/notify', apiToken, interaction, 'post', { @@ -452,16 +477,16 @@ client.on('interactionCreate', async interaction => { { name: 'Message', value: notifyResponse.message } ], interaction, isEphemeral(interaction.user.id)); break; - + default: - interaction.reply({ content: 'Command not recognized.', ephemeral: isEphemeral(interaction.user.id) }); + await interaction.editReply({ content: 'Command not recognized.', ephemeral: isEphemeral(interaction.user.id) }); break; } } catch (error) { console.error('Command error:', error); sendSexyEmbed('Error', 'An error occurred while processing your request.', interaction, isEphemeral(interaction.user.id)); } -}); +}); // Log in to Discord -client.login(config.token); +client.login(config.token); \ No newline at end of file