node.jsdiscorddiscord.js

Discord.js: Trying to register commands globally across DMs. Commands show on Servers and DM with bot, but not on other DMs


I'm trying to get a Discord bot that has convenient commands across people's DMs (Like ESMbot). I coded it with the Discord.js module. However, I tried registering the slash commands and it appears on a DM with the bot (and I check if it appeared on servers; It did), but it doesn't appear anywhere else on any other people's DMs.

The App is authorized on my Discord account, and it has all the intents needed (See index.js file below). Checking the app directly from the games section shows my bot, but no commands.

Image of the Discord bot's info showing no commands

I also did check and ensured the app had the correct permissions with my account. It's authorized with the 2 permissions needed for it to create commands. Also, the other person has my bot added, and it still doesn't work for them either.

Here's my code for the bot:

index.js

// Import the necessary modules
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js');
const { token } = require('./config.json');

// Define the necessary variables
const OWNER_ID = '################'

console.log(`[Info] Creating Discord Client`);
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.DirectMessages], partials: ['CHANNEL'] });


client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);

for (const folder of commandFolders) {
    const commandsPath = path.join(foldersPath, folder);
    const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
    for (const file of commandFiles) {
        const filePath = path.join(commandsPath, file);
        const command = require(filePath);
        if ('data' in command && 'execute' in command) {
            client.commands.set(command.data.name, command);
        } else {
            console.log(`[Warn] The command at ${filePath} is missing a required "data" or "execute" property.`);
        }
    }
}

client.once(Events.ClientReady, async readyClient => {
    console.log(`[Client] Logged in as ${readyClient.user.tag}`);
    console.log(`[Info] Sending message to owner`);
    await sendDM(OWNER_ID, `[Debug] Bot started!`).catch(error => {
        console.warn(`[Client] Failed to send startup message to owner:`, error);
    });
    console.log(`[Info] Ready!`);
});

client.on(Events.InteractionCreate, async interaction => {
    console.log(`[Client] Command received: /${interaction.commandName} in ${interaction.guild ? interaction.guild.name : 'DM'}`);
    if (!interaction.isChatInputCommand()) return;
    const command = interaction.client.commands.get(interaction.commandName);

    if (!command) {
        console.error(`[Error] No command matching ${interaction.commandName} was found.`);
        return;
    }

    try {
        await command.execute(interaction);
    } catch (error) {
        console.error(`[Error] An error occured`, error);
        if (interaction.replied || interaction.deferred) {
            await interaction.followUp({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
        } else {
            await interaction.reply({ content: 'There was an error while executing this command!', flags: MessageFlags.Ephemeral });
        }
    }
});

client.on(Events.MessageCreate, async message => {
    if (message.author.id === client.user.id) return; // Ignore messages from the bot itself
    if (message.author.id == OWNER_ID) {

    }; // Only process messages from the owner
    console.log(`[Client] Message received from ${message.author.tag} in ${message.channel.type}`);
    const content = message.content.trim();
    if (!content) {
        console.warn(`[Info] Empty message received from owner. Ignoring.`);
        return;
    }

    console.log(`[Info] Forwarding message to owner`);
    for (const userId of client.users.cache.map(user => user.id)) {
        if (userId === OWNER_ID) continue; // Skip the owner
        await sendDM(OWNER_ID, `(${message.author.tag}) ${content}`);
    }
});

async function sendDM(userId, message) {
    try {
        const user = await client.users.fetch(userId);
        await user.send(message);
        console.log(`[Client] DM Response sent to ${user.tag} (${user.id})`);
    } catch (error) {
        if (error.code === 50007) {
            console.error(`[Client] Cannot send messages to user ${userId}: ${error.message}`);
        } else {
            console.error(`[Client] Failed to send DM message to ${userId}:`, error);
        }
    }
}

console.log(`[Client] Logging in with token`);
client.login(token);

deploy_commands.js (This is the code that deploys my commands)

const { REST, Routes } = require('discord.js');
const { clientId, guildId, token } = require('./config.json');
const fs = require('node:fs');
const path = require('node:path');

const commands = [];
// Grab all the command folders from the commands directory you created earlier
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);

console.log(`[Info] Identifying command files`)
for (const folder of commandFolders) {
    // Grab all the command files from the commands directory you created earlier
    const commandsPath = path.join(foldersPath, folder);
    const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
    // Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
    for (const file of commandFiles) {
        console.log(`[Info] Found ${file}`);
        // Construct a file path to the command file
        const filePath = path.join(commandsPath, file);
        const command = require(filePath);
        if ('data' in command && 'execute' in command) {
            commands.push(command.data.toJSON());
        } else {
            console.warn(`[Warn] The command at ${filePath} is missing a required "data" or "execute" property.`);
        }
    }
}

console.log(`[Info] ${commands.length} commands found`)

// Construct and prepare an instance of the REST module
const rest = new REST().setToken(token);

// and deploy your commands!
(async () => {
    try {
        console.log(`[Info] Refreshing application (/) commands.`);

        // The put method is used to fully refresh all commands in the guild with the current set
        const data = await rest.put(
            Routes.applicationCommands(clientId),
            { body: commands },
        );

        console.log(`[Info] Successfully reloaded ${data.length} application (/) commands.`);

        // Get the data to confirm the commands were registered
        const registeredCommands = await rest.get(Routes.applicationCommands(clientId));
        console.log(`[Info] Registered commands: ${registeredCommands.map(cmd => cmd.name).join(', ')}`);
    } catch (error) {
        // And of course, make sure you catch and log any errors!
        console.error(`[Error] Error occured: ${error}`);
    }
})();

Here are some of the commands I test to see if it works:

ping.js

const { SlashCommandBuilder } = require('discord.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('ping')
        .setDescription('Replies with Pong!!')
        .setDMPermission(true),
    async execute(interaction) {
        await interaction.reply('Pong!');
    },
};

send.js

const { SlashCommandBuilder } = require('discord.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('send')
        .setDescription('Send a message to a specific person or channel (Channel part coming soon)')
        .setDMPermission(true)
        .addStringOption(options =>
            options
                .setName('user')
                .setDescription('The user to send the message to')
                .setRequired(true))
        .addStringOption(options =>
            options
                .setName('message')
                .setDescription('The url to the mod')
                .setRequired(true)),
    async execute(interaction) {
        await interaction.reply({ content: 'Please wait. Sending Message...', ephemeral: true });
        const messageContent = interaction.options.getString('message');
        const user = await interaction.client.users.fetch(interaction.options.getString('user')).catch(error => {
            console.error(`[Command] Failed to fetch user:`, error);
            return null;
        });
        
        if (!user) {
            await interaction.editReply({ content: 'An error occurred: User not found. Please provide a valid user.', ephemeral: true });
            return;
        }
        if (!messageContent) {
            await interaction.editReply({ content: 'An error occurred: No message provided. Please provide a valid message.', ephemeral: true });
            return;
        }
        try {
            await user.send(messageContent);
            await interaction.editReply({ content: 'Message sent successfully!', ephemeral: true });
        } catch (error) {
            console.error(`[Command] Failed to send message to ${user.tag}:`, error);
            if (error.code === 50007) {
                await interaction.editReply({ content: `We couldn't send the message. Maybe the user can't receive messages from me.`, ephemeral: true });
                return;
            }
            await interaction.editReply({ content: 'An error occurred while sending the message. Please try again later.', ephemeral: true });
        }
    }
}

What am I missing for this to work? Are bots (or really apps) like mine just not able to do stuff like this?


Update: Seems that using .setContexts fixed it or my update to the dev channel fixed it. It wasn't recognizing it, so I updated to the devchannel for discord.js and it did. I'll see if it gets it to work.

Here's the updated code I'm trying:

send.js

const { SlashCommandBuilder, InteractionContextType} = require('discord.js');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('send')
        .setDescription('Send a message to a specific person or channel (Channel part coming soon)')
        .setDMPermission(true)
        .setContexts(InteractionContextType.PrivateChannel, InteractionContextType.BotDM, InteractionContextType.Guild)
        .addStringOption(options =>
            options
                .setName('user')
                .setDescription('The user to send the message to')
                .setRequired(true))
        .addStringOption(options =>
            options
                .setName('message')
                .setDescription('The url to the mod')
                .setRequired(true)),
    async execute(interaction) {
        await interaction.reply({ content: 'Please wait. Sending Message...', ephemeral: true });
        const messageContent = interaction.options.getString('message');
        const user = await interaction.client.users.fetch(interaction.options.getString('user')).catch(error => {
            console.error(`[Command] Failed to fetch user:`, error);
            return null;
        });
        
        if (!user) {
            await interaction.editReply({ content: 'An error occurred: User not found. Please provide a valid user.', ephemeral: true });
            return;
        }
        if (!messageContent) {
            await interaction.editReply({ content: 'An error occurred: No message provided. Please provide a valid message.', ephemeral: true });
            return;
        }
        try {
            await user.send(messageContent);
            await interaction.editReply({ content: 'Message sent successfully!', ephemeral: true });
        } catch (error) {
            console.error(`[Command] Failed to send message to ${user.tag}:`, error);
            if (error.code === 50007) {
                await interaction.editReply({ content: `We couldn't send the message. Maybe the user can't receive messages from me.`, ephemeral: true });
                return;
            }
            await interaction.editReply({ content: 'An error occurred while sending the message. Please try again later.', ephemeral: true });
        }
    }
}

Solution

  • You need to set the contexts in the SlashCommandBuilder with setContexts.
    To allow command on every channel you can try:

    new SlashCommandBuilder().setContexts(InteractionContextType.PrivateChannel, InteractionContextType.BotDM, InteractionContextType.Guild)
    

    Make sure to import InteractionContextType.