Add privacy controls
This commit is contained in:
parent
440fd75f13
commit
1bf2e2f352
129
user-bot.js
129
user-bot.js
@ -10,6 +10,7 @@ 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';
|
||||||
|
const privacyFile = './user_privacy.json'; // Privacy file path
|
||||||
const config = JSON.parse(readFileSync('./config.json', 'utf8'));
|
const config = JSON.parse(readFileSync('./config.json', 'utf8'));
|
||||||
|
|
||||||
// MySQL connection
|
// MySQL connection
|
||||||
@ -38,6 +39,24 @@ function saveTokens(tokens) {
|
|||||||
jsonfile.writeFileSync(tokensFile, tokens, { spaces: 2 });
|
jsonfile.writeFileSync(tokensFile, tokens, { spaces: 2 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load privacy settings from JSON file
|
||||||
|
function loadUserPrivacySettings() {
|
||||||
|
try {
|
||||||
|
return jsonfile.readFileSync(privacyFile);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading privacy settings file:', error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save user privacy settings to JSON file
|
||||||
|
function saveUserPrivacySettings() {
|
||||||
|
jsonfile.writeFileSync(privacyFile, userPrivacySettings, { spaces: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load user privacy settings on startup
|
||||||
|
let userPrivacySettings = loadUserPrivacySettings();
|
||||||
|
|
||||||
// Automatically request a new token if it doesn't exist or is invalid
|
// Automatically request a new token if it doesn't exist or is invalid
|
||||||
async function fetchAndSaveToken(sshSurfID, interaction) {
|
async function fetchAndSaveToken(sshSurfID, interaction) {
|
||||||
return unirest
|
return unirest
|
||||||
@ -123,6 +142,24 @@ function sendSexyEmbedWithFields(title, fields, interaction, ephemeral = false)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.editReply({ 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
|
||||||
|
}
|
||||||
|
|
||||||
// Slash command definitions
|
// Slash command definitions
|
||||||
const commands = [
|
const commands = [
|
||||||
new SlashCommandBuilder().setName('hello').setDescription('Say hello via API'),
|
new SlashCommandBuilder().setName('hello').setDescription('Say hello via API'),
|
||||||
@ -140,6 +177,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
|
||||||
];
|
];
|
||||||
|
|
||||||
// Register commands with Discord
|
// Register commands with Discord
|
||||||
@ -178,9 +216,11 @@ 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 to allow time for the command to run
|
// Defer the reply based on whether it's the privacy command or not
|
||||||
await interaction.deferReply();
|
await interaction.deferReply({ ephemeral: isPrivacyCommand || isEphemeral(interaction.user.id) });
|
||||||
|
|
||||||
// First, we fetch the sshSurfID from the database using interaction.user.id
|
// First, we fetch the sshSurfID from the database using interaction.user.id
|
||||||
let sshSurfID = await new Promise((resolve, reject) => {
|
let sshSurfID = await new Promise((resolve, reject) => {
|
||||||
@ -205,21 +245,26 @@ client.on('interactionCreate', async interaction => {
|
|||||||
return sendSexyEmbed("Error", "User not found in the database.", interaction);
|
return sendSexyEmbed("Error", "User not found in the database.", interaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle privacy toggle command
|
||||||
|
if (interaction.commandName === 'privacy') {
|
||||||
|
await togglePrivacy(interaction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Once sshSurfID is set, we proceed with token fetching and API requests
|
// Once sshSurfID is set, we 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) {
|
||||||
case 'hello':
|
case 'hello':
|
||||||
const helloResponse = await makeApiRequest('/hello', apiToken, interaction);
|
const helloResponse = await makeApiRequest('/hello', apiToken, interaction);
|
||||||
sendSexyEmbed("Hello", `Message: ${helloResponse.message}`, interaction);
|
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);
|
], interaction, isEphemeral(interaction.user.id));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'start':
|
case 'start':
|
||||||
@ -227,7 +272,7 @@ client.on('interactionCreate', async interaction => {
|
|||||||
sendSexyEmbedWithFields('Start Server', [
|
sendSexyEmbedWithFields('Start Server', [
|
||||||
{ name: 'Status', value: 'Success' },
|
{ name: 'Status', value: 'Success' },
|
||||||
{ name: 'Message', value: startResponse.message }
|
{ name: 'Message', value: startResponse.message }
|
||||||
], interaction);
|
], interaction, isEphemeral(interaction.user.id));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'stop':
|
case 'stop':
|
||||||
@ -235,7 +280,7 @@ client.on('interactionCreate', async interaction => {
|
|||||||
sendSexyEmbedWithFields('Stop Server', [
|
sendSexyEmbedWithFields('Stop Server', [
|
||||||
{ name: 'Status', value: 'Success' },
|
{ name: 'Status', value: 'Success' },
|
||||||
{ name: 'Message', value: stopResponse.message }
|
{ name: 'Message', value: stopResponse.message }
|
||||||
], interaction);
|
], interaction, isEphemeral(interaction.user.id));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'restart':
|
case 'restart':
|
||||||
@ -243,13 +288,11 @@ client.on('interactionCreate', async interaction => {
|
|||||||
sendSexyEmbedWithFields('Restart Server', [
|
sendSexyEmbedWithFields('Restart Server', [
|
||||||
{ name: 'Status', value: 'Success' },
|
{ name: 'Status', value: 'Success' },
|
||||||
{ name: 'Message', value: restartResponse.message }
|
{ name: 'Message', value: restartResponse.message }
|
||||||
], interaction);
|
], 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);
|
||||||
|
|
||||||
// Extract and fallback data fields from the response
|
|
||||||
const containerName = infoResponse.data?.name || 'N/A';
|
const containerName = infoResponse.data?.name || 'N/A';
|
||||||
const ipAddress = infoResponse.data?.IPAddress || 'N/A';
|
const ipAddress = infoResponse.data?.IPAddress || 'N/A';
|
||||||
const macAddress = infoResponse.data?.MacAddress || 'N/A';
|
const macAddress = infoResponse.data?.MacAddress || 'N/A';
|
||||||
@ -263,7 +306,6 @@ client.on('interactionCreate', async interaction => {
|
|||||||
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';
|
||||||
|
|
||||||
// Format and send the embed
|
|
||||||
sendSexyEmbedWithFields('Container Info', [
|
sendSexyEmbedWithFields('Container Info', [
|
||||||
{ name: 'Name', value: containerName },
|
{ name: 'Name', value: containerName },
|
||||||
{ name: 'IP Address', value: ipAddress },
|
{ name: 'IP Address', value: ipAddress },
|
||||||
@ -276,7 +318,7 @@ client.on('interactionCreate', async interaction => {
|
|||||||
{ name: 'PID', value: `${pid}` },
|
{ name: 'PID', value: `${pid}` },
|
||||||
{ name: 'Started At', value: startedAt },
|
{ name: 'Started At', value: startedAt },
|
||||||
{ name: 'Created At', value: createdAt }
|
{ name: 'Created At', value: createdAt }
|
||||||
], interaction);
|
], interaction, isEphemeral(interaction.user.id));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'stats':
|
case 'stats':
|
||||||
@ -284,54 +326,45 @@ client.on('interactionCreate', async interaction => {
|
|||||||
sendSexyEmbedWithFields('Container Stats', [
|
sendSexyEmbedWithFields('Container Stats', [
|
||||||
{ name: 'Memory Usage', value: `${statsResponse.data.memory.raw} (${statsResponse.data.memory.percent})` },
|
{ name: 'Memory Usage', value: `${statsResponse.data.memory.raw} (${statsResponse.data.memory.percent})` },
|
||||||
{ name: 'CPU Usage', value: statsResponse.data.cpu }
|
{ name: 'CPU Usage', value: statsResponse.data.cpu }
|
||||||
], interaction);
|
], 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);
|
], 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, true);
|
], 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, true);
|
], 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, true);
|
], interaction, isEphemeral(interaction.user.id));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'x':
|
||||||
|
|
||||||
case 'x': {
|
|
||||||
|
|
||||||
const command = interaction.options.getString('command');
|
const command = interaction.options.getString('command');
|
||||||
|
|
||||||
// Get the user's current working directory or default to root (/)
|
|
||||||
let userPWD = userWorkingDirectories.get(sshSurfID) || '/';
|
let userPWD = userWorkingDirectories.get(sshSurfID) || '/';
|
||||||
|
|
||||||
// Handle 'cd' command logic
|
|
||||||
if (command.startsWith('cd')) {
|
if (command.startsWith('cd')) {
|
||||||
let argscmd = command.replace('cd ', '').trim();
|
let argscmd = command.replace('cd ', '').trim();
|
||||||
|
|
||||||
// Handle 'cd ..' for going up one directory
|
|
||||||
if (argscmd === '..') {
|
if (argscmd === '..') {
|
||||||
if (userPWD !== '/') {
|
if (userPWD !== '/') {
|
||||||
// Remove the last part of the current path
|
|
||||||
const newPWD = userPWD.split('/').slice(0, -1).join('/') || '/';
|
const newPWD = userPWD.split('/').slice(0, -1).join('/') || '/';
|
||||||
userPWD = newPWD;
|
userPWD = newPWD;
|
||||||
userWorkingDirectories.set(sshSurfID, newPWD);
|
userWorkingDirectories.set(sshSurfID, newPWD);
|
||||||
@ -342,7 +375,6 @@ client.on('interactionCreate', async interaction => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle '~' for home directory
|
|
||||||
if (argscmd === '~') {
|
if (argscmd === '~') {
|
||||||
userPWD = '/root';
|
userPWD = '/root';
|
||||||
userWorkingDirectories.set(sshSurfID, userPWD);
|
userWorkingDirectories.set(sshSurfID, userPWD);
|
||||||
@ -350,45 +382,32 @@ client.on('interactionCreate', async interaction => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle absolute and relative paths
|
|
||||||
let newPWD;
|
let newPWD;
|
||||||
if (argscmd.startsWith('/')) {
|
if (argscmd.startsWith('/')) {
|
||||||
// Absolute path
|
|
||||||
newPWD = argscmd;
|
newPWD = argscmd;
|
||||||
} else {
|
} else {
|
||||||
// Relative path
|
|
||||||
newPWD = `${userPWD}/${argscmd}`;
|
newPWD = `${userPWD}/${argscmd}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the path (remove extra slashes)
|
|
||||||
newPWD = newPWD.replace(/([^:]\/)\/+/g, "$1");
|
newPWD = newPWD.replace(/([^:]\/)\/+/g, "$1");
|
||||||
|
|
||||||
// Check if the user is trying to go back multiple directories (e.g., 'cd ../../')
|
|
||||||
if (argscmd.includes('../')) {
|
if (argscmd.includes('../')) {
|
||||||
const numDirsBack = argscmd.split('../').length - 1;
|
const numDirsBack = argscmd.split('../').length - 1;
|
||||||
newPWD = RemoveLastDirectoryPartOf(userPWD, numDirsBack);
|
newPWD = RemoveLastDirectoryPartOf(userPWD, numDirsBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the working directory
|
|
||||||
userWorkingDirectories.set(sshSurfID, newPWD);
|
userWorkingDirectories.set(sshSurfID, newPWD);
|
||||||
await interaction.editReply(`Directory changed to: ${newPWD}`);
|
await interaction.editReply(`Directory changed to: ${newPWD}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the command is not 'cd', run the command in the current working directory (or default to '/')
|
|
||||||
const execResponse = await makeApiRequest('/exec', apiToken, interaction, 'post', {
|
const execResponse = await makeApiRequest('/exec', apiToken, interaction, 'post', {
|
||||||
cmd: command,
|
cmd: command,
|
||||||
pwd: userPWD // Use the current directory or default to '/'
|
pwd: userPWD
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle the command output
|
|
||||||
if (execResponse.stdout.length > 2020) {
|
if (execResponse.stdout.length > 2020) {
|
||||||
// If the output is too large, save it to a temporary file and upload to a paste service
|
|
||||||
|
|
||||||
|
|
||||||
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(err);
|
||||||
@ -398,46 +417,30 @@ client.on('interactionCreate', async interaction => {
|
|||||||
|
|
||||||
const pasteout = await cmd("sleep 2; cat /tmp/paste | dpaste");
|
const pasteout = await cmd("sleep 2; cat /tmp/paste | dpaste");
|
||||||
|
|
||||||
// Create an embed with the paste link
|
|
||||||
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] });
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// If the output is small enough, format it in a markdown code block
|
|
||||||
let replyMessage = `\`\`\`\n${execResponse.stdout || 'No output'}\n\`\`\``;
|
let replyMessage = `\`\`\`\n${execResponse.stdout || 'No output'}\n\`\`\``;
|
||||||
|
|
||||||
// If there is an error, append the error message in another markdown code block
|
|
||||||
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\`\`\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply with the formatted message
|
|
||||||
await interaction.editReply(replyMessage);
|
await interaction.editReply(replyMessage);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to remove directories when using '../'
|
|
||||||
function RemoveLastDirectoryPartOf(the_url, num) {
|
|
||||||
var the_arr = the_url.split('/');
|
|
||||||
the_arr.splice(-num, num);
|
|
||||||
return the_arr.join('/') || '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
case 'notify':
|
case 'notify':
|
||||||
const message = interaction.options.getString('message');
|
const message = interaction.options.getString('message');
|
||||||
@ -447,16 +450,16 @@ client.on('interactionCreate', async interaction => {
|
|||||||
sendSexyEmbedWithFields('Notification', [
|
sendSexyEmbedWithFields('Notification', [
|
||||||
{ name: 'Status', value: 'Success' },
|
{ name: 'Status', value: 'Success' },
|
||||||
{ name: 'Message', value: notifyResponse.message }
|
{ name: 'Message', value: notifyResponse.message }
|
||||||
], interaction, true);
|
], interaction, isEphemeral(interaction.user.id));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
interaction.reply('Command not recognized.');
|
interaction.reply({ 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);
|
sendSexyEmbed('Error', 'An error occurred while processing your request.', interaction, isEphemeral(interaction.user.id));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
3
user_privacy.json
Normal file
3
user_privacy.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"342128351638585344": false
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user