fix user installable bot - if no container is running let the user know

This commit is contained in:
dlinux-host 2025-05-22 03:59:03 -04:00
parent b9e5f78e14
commit 243128e925

View File

@ -3,10 +3,11 @@ import jsonfile from 'jsonfile';
import unirest from 'unirest'; import unirest from 'unirest';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import mysql from 'mysql2'; import mysql from 'mysql2';
const userWorkingDirectories = new Map();
import fs from 'fs'; import fs from 'fs';
import cmd from 'cmd-promise'; 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 // Paths to config and tokens files
const tokensFile = './tokens.json'; const tokensFile = './tokens.json';
@ -65,7 +66,7 @@ async function fetchAndSaveToken(sshSurfID, interaction) {
.send({ "username": `${sshSurfID}`, "password": config.password.toString() }) .send({ "username": `${sshSurfID}`, "password": config.password.toString() })
.then((tokenInfo) => { .then((tokenInfo) => {
const tokens = loadTokens(); 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); saveTokens(tokens);
return tokenInfo.body.token; return tokenInfo.body.token;
}) })
@ -99,10 +100,10 @@ async function makeApiRequest(endpoint, token, interaction, method = 'get', body
} }
return request.then(response => { return request.then(response => {
if (response.error) { if (response.error || !response.body) {
console.error('API Error:', response.error); console.error('API Error:', response.error || 'No response body');
sendSexyEmbed("Error", "An error occurred while communicating with the API.", interaction); sendSexyEmbed("Error", "An error occurred while communicating with the API.", interaction);
throw response.error; throw new Error('API request failed');
} }
return response.body; return response.body;
}); });
@ -149,8 +150,8 @@ async function togglePrivacy(interaction) {
userPrivacySettings[userId] = !currentSetting; // Toggle the setting userPrivacySettings[userId] = !currentSetting; // Toggle the setting
saveUserPrivacySettings(); saveUserPrivacySettings();
const message = userPrivacySettings[userId] const message = userPrivacySettings[userId]
? 'Your responses are now set to ephemeral (visible only to you).' ? 'Your responses are now set to ephemeral (visible only to you).'
: 'Your responses are now standard (visible to everyone).'; : 'Your responses are now standard (visible to everyone).';
await interaction.editReply({ content: message, ephemeral: true }); await interaction.editReply({ content: message, ephemeral: true });
} }
@ -177,7 +178,7 @@ const commands = [
.addStringOption(option => option.setName('command').setDescription('Command to execute').setRequired(true)), .addStringOption(option => option.setName('command').setDescription('Command to execute').setRequired(true)),
new SlashCommandBuilder().setName('notify').setDescription('Send a notification to Discord') new SlashCommandBuilder().setName('notify').setDescription('Send a notification to Discord')
.addStringOption(option => option.setName('message').setDescription('Message to send').setRequired(true)), .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 // Register commands with Discord
@ -201,10 +202,10 @@ const rest = new REST({ version: '10' }).setToken(config.token);
return jsonCommand; return jsonCommand;
}); });
// Register commands with Discord, making sure all commands are sent in an array // Register commands with Discord
const data = await rest.put( await rest.put(
Routes.applicationCommands(config.clientId), Routes.applicationCommands(config.clientId),
{ body: commandsWithExtras } // Send all commands in one array { body: commandsWithExtras }
); );
console.log('Successfully reloaded application (/) commands.'); console.log('Successfully reloaded application (/) commands.');
@ -216,13 +217,14 @@ const rest = new REST({ version: '10' }).setToken(config.token);
// Handle bot interactions // Handle bot interactions
client.on('interactionCreate', async interaction => { client.on('interactionCreate', async interaction => {
if (!interaction.isCommand()) return; 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) => { let sshSurfID = await new Promise((resolve, reject) => {
connection.query( connection.query(
"SELECT uid FROM users WHERE discord_id = ?", "SELECT uid FROM users WHERE discord_id = ?",
@ -251,7 +253,7 @@ client.on('interactionCreate', async interaction => {
return; 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); const apiToken = await getToken(sshSurfID, interaction);
try { try {
switch (interaction.commandName) { switch (interaction.commandName) {
@ -259,14 +261,14 @@ client.on('interactionCreate', async interaction => {
const helloResponse = await makeApiRequest('/hello', apiToken, interaction); const helloResponse = await makeApiRequest('/hello', apiToken, interaction);
sendSexyEmbed("Hello", `Message: ${helloResponse.message}`, interaction, isEphemeral(interaction.user.id)); sendSexyEmbed("Hello", `Message: ${helloResponse.message}`, interaction, isEphemeral(interaction.user.id));
break; break;
case 'name': case 'name':
const nameResponse = await makeApiRequest('/name', apiToken, interaction); const nameResponse = await makeApiRequest('/name', apiToken, interaction);
sendSexyEmbedWithFields('Username', [ sendSexyEmbedWithFields('Username', [
{ name: 'Username', value: nameResponse.message } { name: 'Username', value: nameResponse.message }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'start': case 'start':
const startResponse = await makeApiRequest('/start', apiToken, interaction); const startResponse = await makeApiRequest('/start', apiToken, interaction);
sendSexyEmbedWithFields('Start Server', [ sendSexyEmbedWithFields('Start Server', [
@ -274,7 +276,7 @@ client.on('interactionCreate', async interaction => {
{ name: 'Message', value: startResponse.message } { name: 'Message', value: startResponse.message }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'stop': case 'stop':
const stopResponse = await makeApiRequest('/stop', apiToken, interaction); const stopResponse = await makeApiRequest('/stop', apiToken, interaction);
sendSexyEmbedWithFields('Stop Server', [ sendSexyEmbedWithFields('Stop Server', [
@ -282,7 +284,7 @@ client.on('interactionCreate', async interaction => {
{ name: 'Message', value: stopResponse.message } { name: 'Message', value: stopResponse.message }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'restart': case 'restart':
const restartResponse = await makeApiRequest('/restart', apiToken, interaction); const restartResponse = await makeApiRequest('/restart', apiToken, interaction);
sendSexyEmbedWithFields('Restart Server', [ sendSexyEmbedWithFields('Restart Server', [
@ -290,7 +292,7 @@ client.on('interactionCreate', async interaction => {
{ name: 'Message', value: restartResponse.message } { name: 'Message', value: restartResponse.message }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'info': case 'info':
const infoResponse = await makeApiRequest('/info', apiToken, interaction); const infoResponse = await makeApiRequest('/info', apiToken, interaction);
const containerName = infoResponse.data?.name || 'N/A'; 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 startedAt = infoResponse.data?.state?.StartedAt || 'N/A';
const image = infoResponse.data?.image || 'N/A'; const image = infoResponse.data?.image || 'N/A';
const createdAt = infoResponse.data?.created || 'N/A'; const createdAt = infoResponse.data?.created || 'N/A';
sendSexyEmbedWithFields('Container Info', [ sendSexyEmbedWithFields('Container Info', [
{ name: 'Name', value: containerName }, { name: 'Name', value: containerName },
{ name: 'IP Address', value: ipAddress }, { name: 'IP Address', value: ipAddress },
@ -320,7 +322,7 @@ client.on('interactionCreate', async interaction => {
{ name: 'Created At', value: createdAt } { name: 'Created At', value: createdAt }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'stats': case 'stats':
const statsResponse = await makeApiRequest('/stats', apiToken, interaction); const statsResponse = await makeApiRequest('/stats', apiToken, interaction);
sendSexyEmbedWithFields('Container Stats', [ sendSexyEmbedWithFields('Container Stats', [
@ -328,39 +330,39 @@ client.on('interactionCreate', async interaction => {
{ name: 'CPU Usage', value: statsResponse.data.cpu } { name: 'CPU Usage', value: statsResponse.data.cpu }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'time': case 'time':
const timeResponse = await makeApiRequest('/time', apiToken, interaction); const timeResponse = await makeApiRequest('/time', apiToken, interaction);
sendSexyEmbedWithFields('Container Expire Time', [ sendSexyEmbedWithFields('Container Expire Time', [
{ name: 'Expire Date', value: timeResponse.expireDate } { name: 'Expire Date', value: timeResponse.expireDate }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'root-password': case 'root-password':
const rootPassResponse = await makeApiRequest('/rootpass', apiToken, interaction); const rootPassResponse = await makeApiRequest('/rootpass', apiToken, interaction);
sendSexyEmbedWithFields('Root Password', [ sendSexyEmbedWithFields('Root Password', [
{ name: 'New Root Password', value: rootPassResponse.newRootPass } { name: 'New Root Password', value: rootPassResponse.newRootPass }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'new-api-key': case 'new-api-key':
const newKeyResponse = await makeApiRequest('/new-key', apiToken, interaction); const newKeyResponse = await makeApiRequest('/new-key', apiToken, interaction);
sendSexyEmbedWithFields('New API Key', [ sendSexyEmbedWithFields('New API Key', [
{ name: 'New API Key', value: newKeyResponse.newAPIKey } { name: 'New API Key', value: newKeyResponse.newAPIKey }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'key-expire-time': case 'key-expire-time':
const keyTimeResponse = await makeApiRequest('/key-time', apiToken, interaction); const keyTimeResponse = await makeApiRequest('/key-time', apiToken, interaction);
sendSexyEmbedWithFields('API Key Expire Time', [ sendSexyEmbedWithFields('API Key Expire Time', [
{ name: 'Expire Date', value: keyTimeResponse.expireDate } { name: 'Expire Date', value: keyTimeResponse.expireDate }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
case 'x': case 'x':
const command = interaction.options.getString('command'); const command = interaction.options.getString('command');
let userPWD = userWorkingDirectories.get(sshSurfID) || '/'; let userPWD = userWorkingDirectories.get(sshSurfID) || '/';
if (command.startsWith('cd')) { if (command.startsWith('cd')) {
let argscmd = command.replace('cd ', '').trim(); let argscmd = command.replace('cd ', '').trim();
if (argscmd === '..') { if (argscmd === '..') {
@ -374,14 +376,14 @@ client.on('interactionCreate', async interaction => {
} }
return; return;
} }
if (argscmd === '~') { if (argscmd === '~') {
userPWD = '/root'; userPWD = '/root';
userWorkingDirectories.set(sshSurfID, userPWD); userWorkingDirectories.set(sshSurfID, userPWD);
await interaction.editReply(`Directory changed to: ${userPWD}`); await interaction.editReply(`Directory changed to: ${userPWD}`);
return; return;
} }
let newPWD; let newPWD;
if (argscmd.startsWith('/')) { if (argscmd.startsWith('/')) {
newPWD = argscmd; newPWD = argscmd;
@ -389,59 +391,82 @@ client.on('interactionCreate', async interaction => {
newPWD = `${userPWD}/${argscmd}`; newPWD = `${userPWD}/${argscmd}`;
} }
newPWD = newPWD.replace(/([^:]\/)\/+/g, "$1"); newPWD = newPWD.replace(/([^:]\/)\/+/g, "$1");
if (argscmd.includes('../')) { if (argscmd.includes('../')) {
const numDirsBack = argscmd.split('../').length - 1; const numDirsBack = argscmd.split('../').length - 1;
newPWD = RemoveLastDirectoryPartOf(userPWD, numDirsBack); newPWD = userPWD.split('/').slice(0, -numDirsBack).join('/') || '/';
} }
userWorkingDirectories.set(sshSurfID, newPWD); userWorkingDirectories.set(sshSurfID, newPWD);
await interaction.editReply(`Directory changed to: ${newPWD}`); await interaction.editReply(`Directory changed to: ${newPWD}`);
return; return;
} }
const execResponse = await makeApiRequest('/exec', apiToken, interaction, 'post', { const execResponse = await makeApiRequest('/exec', apiToken, interaction, 'post', {
cmd: command, cmd: command,
pwd: userPWD 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) { if (execResponse.stdout.length > 2020) {
const tempFilePath = '/tmp/paste'; const tempFilePath = '/tmp/paste';
const pasteCommand = `Command: ${command} | Container Owner: ${interaction.user.username}\n${execResponse.stdout}`; const pasteCommand = `Command: ${command} | Container Owner: ${interaction.user.username}\n${execResponse.stdout}`;
fs.writeFileSync(tempFilePath, pasteCommand, (err) => { fs.writeFileSync(tempFilePath, pasteCommand, (err) => {
if (err) { if (err) {
console.error(err); console.error('Error writing to file:', err);
return;
} }
}); });
const pasteout = await cmd("sleep 2; cat /tmp/paste | dpaste"); const pasteout = await cmd("sleep 2; cat /tmp/paste | dpaste");
const mainEmbed = new EmbedBuilder() const mainEmbed = new EmbedBuilder()
.setColor('#0099ff') .setColor('#0099ff')
.setTitle(`Container Owner: ${interaction.user.username}`) .setTitle(`Container Owner: ${interaction.user.username}`)
.setDescription(`The command: ${command} was too large for Discord.`) .setDescription(`The command: ${command} was too large for Discord.`)
.addFields({ .addFields({
name: 'Please check the below output log:', 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("Paste Saved: ", "")
.replace("-------------------------------------------------------", "") .replace("-------------------------------------------------------", "")
}) })
.setFooter({ text: `Requested by ${interaction.user.username}`, iconURL: `${interaction.user.displayAvatarURL()}` }); .setFooter({ text: `Requested by ${interaction.user.username}`, iconURL: `${interaction.user.displayAvatarURL()}` });
await interaction.editReply({ embeds: [mainEmbed] }); 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 { } else {
let replyMessage = `\`\`\`\n${execResponse.stdout || 'No output'}\n\`\`\``; let replyMessage = `\`\`\`\n${execResponse.stdout || 'No output'}\n\`\`\``;
if (execResponse.stderr && execResponse.stderr.trim()) { if (execResponse.stderr && execResponse.stderr.trim()) {
replyMessage += `\n**Error:**\n\`\`\`\n${execResponse.stderr}\n\`\`\``; replyMessage += `\n**Error:**\n\`\`\`\n${execResponse.stderr}\n\`\`\``;
} }
await interaction.editReply(replyMessage); await interaction.editReply(replyMessage);
} }
break; break;
case 'notify': case 'notify':
const message = interaction.options.getString('message'); const message = interaction.options.getString('message');
const notifyResponse = await makeApiRequest('/notify', apiToken, interaction, 'post', { const notifyResponse = await makeApiRequest('/notify', apiToken, interaction, 'post', {
@ -452,16 +477,16 @@ client.on('interactionCreate', async interaction => {
{ name: 'Message', value: notifyResponse.message } { name: 'Message', value: notifyResponse.message }
], interaction, isEphemeral(interaction.user.id)); ], interaction, isEphemeral(interaction.user.id));
break; break;
default: 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; break;
} }
} catch (error) { } catch (error) {
console.error('Command error:', error); console.error('Command error:', error);
sendSexyEmbed('Error', 'An error occurred while processing your request.', interaction, isEphemeral(interaction.user.id)); sendSexyEmbed('Error', 'An error occurred while processing your request.', interaction, isEphemeral(interaction.user.id));
} }
}); });
// Log in to Discord // Log in to Discord
client.login(config.token); client.login(config.token);