Export an instance of Distube so that it is accessible from slash commands

I am trying to make a simple music bot using DisTube and it worked using the examples they gave but now I am trying to incorporate slash commands. Here is my code:

const { Client , GatewayIntentBits, Collection, Events } = require('discord.js');
const fs = require('node:fs');
const path = require('node:path');
const { token } = require('./config.json');
const { DisTube } = require("distube");

const client = new Client({
    intents: [
      GatewayIntentBits.Guilds,
      GatewayIntentBits.GuildMessages,
      GatewayIntentBits.GuildVoiceStates,
      GatewayIntentBits.MessageContent
    ],
  });

const Distube = new DisTube(client, {
    leaveOnStop: false,
    emitNewSongOnly: true,
    emitAddSongWhenCreatingQueue: false,
    emitAddListWhenCreatingQueue: false,
});

const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
client.commands = new Collection();

for (const file of commandFiles) {
    const filePath = path.join(commandsPath, file);
    const command = require(filePath);
    // Set a new item in the Collection with the key as the command name and the value as the exported module
    if ('data' in command && 'execute' in command) {
        client.commands.set(command.data.name, command);
    } else {
        console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
    }
}

client.login(token);

client.on('ready', () => {
    console.log(`Logged in as ${client.user.tag}!`);
});

// Handling slash commands
client.on(Events.InteractionCreate, async interaction => {
    if (!interaction.isChatInputCommand()) return;
    const command = interaction.client.commands.get(interaction.commandName);

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

    try {
        await command.execute(interaction);
    } catch (error) {
        console.error(error);
        await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
    }
});

module.exports = {
    DisTube: Distube,
};
const { SlashCommandBuilder } = require('discord.js');
const { DisTube } = require('../bot');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('play')
        .setDescription('Begin playing an audio clip')
        .addStringOption(option =>
            option.setName('audio-track')
                .setDescription('Audio track you want to play')
                .setRequired(true)
        ),
    async execute(interaction) {
        await interaction.deferReply();

        const audioTrack = interaction.options.getString('audio-track');
        const voiceChannel = interaction.member.voice.channel;

        if (!voiceChannel || voiceChannel.type !== 2) {
            console.log('Member is not in a voice channel or voice channel type is incorrect.');
            return interaction.followUp('You must be in a voice channel to use this command.');
        }

        console.log('Attempting to play:', audioTrack);
        
        DisTube.play(voiceChannel, audioTrack, {
            member: interaction.member,
            textChannel: interaction.channel,
        });

        // Event listener for successful playback
        DisTube.on('playSong', (queue, song) => {
            interaction.followUp(`Now playing: ${song.name}`);
            console.log(DisTube.getQueue(interaction.guildId));
        });

        // Event listener for playback errors
        client.DisTube.on('error', (channel, error) => {
            console.error(`Error in voice channel ${channel.id}:`, error);
            interaction.followUp('There was an issue, please try again.');
        });
    },
};


If I don’t export DisTube like that and instead set up my slash command like this:

const { SlashCommandBuilder } = require('discord.js');
const { Client, GatewayIntentBits } = require('discord.js');
const { token } = require('../config.json');
const { DisTube } = require("distube");

const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.GuildVoiceStates,
        GatewayIntentBits.MessageContent
    ],
});

client.DisTube = new DisTube(client, {
    leaveOnStop: false,
    emitNewSongOnly: true,
    emitAddSongWhenCreatingQueue: false,
    emitAddListWhenCreatingQueue: false,
});

client.login(token);

module.exports = {

Then everything works. However, this isn’t a good approach because then in another slash command like ‘pause.js’ I need to do this:

const queue = DisTube.getQueue(interaction.guildId);

but the instance of DisTube won’t be the same so queue is always undefined. So inside my bot.js file I am attempting to export DisTube:

module.exports = {
    DisTube: Distube,
};

The problem is that when I try importing DisTube in play.js I get a circular dependency error.

How can I share the instance of DisTube from bot.js such that I can access it in my slash commands? Alternatively, if this is not a good approach how would you handle this?

I actually figured it out for anyone in the future with this problem. There is no need to export client or DisTube. All Discord.js classes have an internal client property, so to access DisTube I modified my bot.js slightly:

  client.DisTube = new DisTube(client, {
    leaveOnStop: false,
    emitNewSongOnly: true,
    emitAddSongWhenCreatingQueue: false,
    emitAddListWhenCreatingQueue: false,
})

and remove the export.

Then in the slash commands just do: const DisTube = interaction.client.DisTube;

I hope this helps someone in the future.

Leave a Comment