yukufy

1.5.0 • Public • Published

distify

Yukufy

Yukufy is a Node.js library designed for managing music playback in Discord servers. It allows you to search, play, pause, resume, skip tracks, stop playback, adjust volume, and fetch lyrics from Spotify and SoundCloud.

📃 Documentation

Explore the full documentation here.

Features

  • Search and stream music from Spotify and SoundCloud.
  • Manage playback with play, pause, resume, skip, and stop functionalities.
  • Retrieve lyrics for the current or specified song.
  • Adjust playback volume and manage a queue of songs.

⚙️ Having Issues? Join our Discord server for support and updates.

Note: This library is still under development and may contain bugs. Feel free to report issues or suggest features in our Discord!

> Yukufy Projects

- Yukufy Bot Example by lNazuna

- Yoruka by CroneGamesPlays

🛠️ Installation

Install Yukufy via npm:

npm install yukufy

🛠️ Setup

To use Yukufy, initialize the YukufyClient with your Spotify credentials and player options.

const { Client, GatewayIntentBits } = require('discord.js');
const { YukufyClient } = require('yukufy');

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

const yukufy = new YukufyClient(client, {
  // Spotify API configuration
  configApi: {
    clientId: 'YOUR_SPOTIFY_CLIENT_ID',
    clientSecret: 'YOUR_SPOTIFY_CLIENT_SECRET',
  },
  // Player configuration
  configPlayer: {
    defaultVolume: 75, // Default volume
    leaveOnEmptyQueue: true,
    leaveOnEmptyQueueCooldown: 10000
  }
});

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

client.login('YOUR_DISCORD_BOT_TOKEN');

🎵 Usage

Player Functions

Search for Music

You can search for songs from Spotify or SoundCloud using the search method.

const query = "NEFFEX Fight Back";
const source = "spotify"; // or "soundcloud"
const tracks = await yukufy.search(query, source);
console.log(tracks);

Play Music

To play a song, specify the song name and the voice channel.

const channel = interaction.member.voice.channel;
const song = "NEFFEX Michael Jordan";
const source = "spotify"; // or "soundcloud"
const music = await yukufy.play(song, channel, source, {
    member: interaction.member,
    textChannel: interaction.channel,
    guildId: interaction.guild.id
  });

Control the Player

  • Pause Music:

yukufy.pause();
  • Resume Music:

yukufy.resume();
  • Skip to Next Song:

yukufy.skip();
  • Stop Playback:

yukufy.stop();
  • Adjust Volume:

const volume = 50; // Volume level between 0 and 100
yukufy.setVolume(volume);
  • Get Lyrics

To fetch lyrics for the current song or a specific song:

const lyrics = await yukufy.lyrics("NEFFEX Fight Back");
console.log(lyrics);

Slash Commands

The bot supports various slash commands. Here’s how you can use them:

  • /play [query]: Play a song in the voice channel. Example: /play NEFFEX Fight Back
  • /stop: Stop the music and leave the voice channel.
  • /skip: Skip to the next song in the queue.
  • /pause: Pause the current song.
  • /resume: Resume the paused song.
  • /volume [number]: Adjust the volume between 0 and 100. Example: /volume 50
  • /search [query] [source]: Search for a song on Spotify or SoundCloud. Example: /search NEFFEX Fight Back spotify
  • /queue: Show the song queue.
  • /join: Join the voice channel.
  • /leave: Leave the voice channel.
  • /loop: Toggle the repeat mode.
  • /nowplaying: Show the currently playing song.
  • /lyrics [query]: Show the lyrics of the current song or a specific song. Example: /lyrics NEFFEX Fight Back

Events

Yukufy also emits various events that you can listen to:

  • playSong: Fired when a song starts playing.
yukufy.on('playSong', ({ track }) => {
  console.log(`Now playing: ${track.title} by ${track.artist}`);
});
  • addSong: Fired when a song is added to the queue.
yukufy.on('addSong', ({ track }) => {
  console.log(`Song added to queue: ${track.title} by ${track.artist}`);
});
  • finishQueue: Fired when the queue finishes.
yukufy.on('finishQueue', () => {
  console.log('Music queue finished.');
});
  • emptyQueue: Fired when the queue becomes empty.
yukufy.on('emptyQueue', () => {
  console.log('Queue is empty.');
});
  • clientDisconnect: Fired when the client disconnects from the voice channel.
yukufy.on('clientDisconnect', () => {
  console.log('Disconnected from voice channel.');
});
  • playerError: Fired when there is an error in the player.
yukufy.on('playerError', (error) => {
  console.error('Player error:', error);
});

Yukufy Example Usage

const { Client, GatewayIntentBits, InteractionType, REST, Routes } = require('discord.js');
const { YukufyClient } = require('yukufy');

// Create a new Discord client instance with specified intents
const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildVoiceStates,
    GatewayIntentBits.GuildMessages
  ]
});

// Initialize the YukufyClient with configuration for API and player
const yukufy = new YukufyClient(client, {
  configApi: {
    clientId: "YourSpotifyClientId",
    clientSecret: "YourSpotifyClientSecret"
  },
  configPlayer: {
    defaultVolume: 75,
    leaveOnEmptyQueue: true,
    leaveOnEmptyQueueCooldown: 10000
  }
});

const clientToken = "YourBotToken";

// Define the slash commands for the bot
const commands = [
  {
    name: 'play',
    description: 'Play a song in the voice channel',
    options: [
      {
        name: 'query',
        type: 3, // Type STRING
        description: 'Name of the song or URL',
        required: true
      }
    ]
  },
  {
    name: 'stop',
    description: 'Stop the music and leave the voice channel'
  },
  {
    name: 'skip',
    description: 'Skip to the next song in the queue'
  },
  {
    name: 'pause',
    description: 'Pause the current song'
  },
  {
    name: 'resume',
    description: 'Resume the paused song'
  },
  {
    name: 'volume',
    description: 'Adjust the volume of the player',
    options: [
      {
        name: 'number',
        type: 10, // Type NUMBER
        description: 'Volume between 0 and 100',
        required: true
      }
    ]
  },
  {
    name: 'search',
    description: 'Search for a song on Spotify or SoundCloud',
    options: [
      {
        name: 'query',
        type: 3, // Type STRING
        description: 'Name of the song or URL',
        required: true
      },
      {
        name: 'source',
        type: 3, // Type STRING
        description: 'Source (spotify or soundcloud)',
        required: true,
        choices: [
          {
            name: 'Spotify',
            value: 'spotify'
          },
          {
            name: 'SoundCloud',
            value: 'soundcloud'
          }
        ]
      }
    ]
  },
  {
    name: 'queue',
    description: 'Show the song queue'
  },
  {
    name: 'join',
    description: 'Join the voice channel'
  },
  {
    name: 'leave',
    description: 'Leave the voice channel'
  },
  {
    name: 'loop',
    description: 'Toggle the repeat mode'
  },
  {
    name: 'nowplaying',
    description: 'Show the currently playing song'
  },
  {
    name: 'lyrics',
    description: 'Show the lyrics of the current song or a specific song',
    options: [
      {
        name: 'query',
        type: 3, // Type STRING
        description: 'Name of the song (optional)',
        required: false
      }
    ]
  }
];

// Initialize REST API with the bot token
const rest = new REST({ version: '10' }).setToken(clientToken);

async function main() {
  try {
    console.log('Starting slash command registration.');

    // Register the slash commands with Discord
    await rest.put(
      Routes.applicationCommands(client.user.id),
      { body: commands }
    );

    console.log('Slash commands registered successfully.');
  } catch (error) {
    console.error('Error registering slash commands:', error);
  }

  // Event fired when the client is ready
  client.once('ready', () => {
    console.log(`Logged in as ${client.user.tag}`);
  });

  // Event fired when an interaction is created
  client.on('interactionCreate', async (interaction) => {
    if (!interaction.isCommand()) return;
    if (interaction.type !== InteractionType.ApplicationCommand) return;
    if (!interaction.isChatInputCommand()) return;

    const { commandName, options } = interaction;

    // Handle the 'play' command
    if (commandName === 'play') {
      const song = options.getString('query');
      const channel = interaction.member.voice.channel;
      const source = "spotify"; // "soundcloud"

      if (!channel) {
        await interaction.reply({ content: 'You need to be in a voice channel to play music!' });
        return;
      }
      try {
        const music = await yukufy.play(song, channel, source, {
          member: interaction.member,
          textChannel: interaction.channel,
          guildId: interaction.guild.id
        });
        if (!music) {
          await interaction.reply('❌ Song not found.');
          return;
        }
        const title = music.title || 'Title not available';
        const artist = music.artist || 'Artist not available';
        const duration = music.duration || 'Duration not available';
        
        await interaction.reply({
          content: `🔊 Searching... Song found: **${artist}** - **${title}** | **${duration}**`,
          ephemeral: false
        });
      } catch (error) {
        await interaction.reply('❌ Error playing song');
        console.error(error);
      }
    }

    // Handle the 'stop' command
    if (commandName === 'stop') {
      yukufy.stop();
      await interaction.reply('🛑 Music stopped.');
    }

    // Handle the 'skip' command
    if (commandName === 'skip') {
      yukufy.skip();
      await interaction.reply('⏭️ Song skipped.');
    }

    // Handle the 'pause' command
    if (commandName === 'pause') {
      const result = await yukufy.pause();
      if (result.status === 'alreadyPaused') {
        await interaction.reply('⏸️ Music is already paused.');
        } else if (result.status === 'paused') {
          await interaction.reply('⏸️ Music paused.');
      }
    }
    
    // Handle the 'resume' command
    if (commandName === 'resume') {
      const result = await yukufy.resume();
      if (result.status === 'alreadyPlaying') {
        await interaction.reply('▶️ Music is already playing.');
        } else if (result.status === 'resumed') {
          await interaction.reply('▶️ Music resumed.');
      }
    }

    // Handle the 'volume' command
    if (commandName === 'volume') {
      const volume = options.getNumber('number');
      try {
        yukufy.setVolume(volume);
        await interaction.reply(`🔊 Volume set to: ${volume}`);
      } catch (error) {
        await interaction.reply('❌ Error adjusting volume');
        console.error(error);
      }
    }

    // Handle the 'search' command
    if (commandName === 'search') {
      const query = options.getString('query');
      const source = options.getString('source');
      try {
        const results = await yukufy.search(query, source);
        if (results && results.length > 0) {
          const searchResults = results.map((r, index) => `${index + 1}. **${r.title}** by **${r.artist}**`).join('\n');
          await interaction.reply(`🔍 Search results:\n${searchResults}`);
        } else {
          await interaction.reply('No results found.');
        }
      } catch (error) {
        await interaction.reply('Error searching for songs.');
        console.error(error);
      }
    }

    // Handle the 'queue' command
    if (commandName === 'queue') {
      try {
        const queue = await yukufy.getQueue();
        if (queue.length === 0) {
          await interaction.reply('The queue is empty.');
        } else {
          const queueString = queue.map(track => `${track.position}. ${track.title} - ${track.artist}`).join('\n');
          await interaction.reply(`🎵 Song queue:\n${queueString}`);
        }
      } catch (error) {
        console.error('Error getting the queue:', error);
        await interaction.reply('An error occurred while trying to get the song queue.');
      }
    }

    // Handle the 'join' command
    if (commandName === 'join') {
      const channel = interaction.member.voice.channel;

      if (!channel) {
        await interaction.reply('You need to be in a voice channel to invite me!');
        return;
      }

      try {
        await yukufy.join(channel);
        await interaction.reply(`🔊 Joined the voice channel: ${channel.name}`);
      } catch (error) {
        await interaction.reply('❌ Error joining the voice channel');
        console.error(error);
      }
    }

    // Handle the 'leave' command
    if (commandName === 'leave') {
      try {
        yukufy.leave(interaction.member.voice.channel.id);
        await interaction.reply('👋 Left the voice channel.');
      } catch (error) {
        await interaction.reply('❌ Error leaving the voice channel');
        console.error(error);
      }
    }

    // Handle the 'loop' command
    if (commandName === 'loop') {
      try {
        const loopOnOff = await yukufy.toggleLoop();
        const statusMessage = loopOnOff ? 'enabled' : 'disabled';
        await interaction.reply(`🔄 Loop is now ${statusMessage}`);
      } catch (error) {
        console.error('Error toggling loop:', error);
        await interaction.reply('❌ There was an error trying to toggle the loop.');
      }
    }    

    // Handle the 'nowplaying' command
    if (commandName === 'nowplaying') {
      const nowPlaying = await yukufy.nowPlaying();
      if (nowPlaying) {
        const title = nowPlaying.title || 'Title not available';
        const artist = nowPlaying.artist || 'Artist not available';
        const url = nowPlaying.url || 'URL not available';
        const duration = nowPlaying.duration || 'Duration not available';
        const elapsedTime = nowPlaying.elapsedTime || 'Elapsed time not available';
        
        await interaction.reply(`🎶 Now playing: **[${title}](${url})**\n🎤 Artist: **${artist}**\n⏱️ Duration: **${elapsedTime}**`);
      } else {
        await interaction.reply('No song is currently playing.');
      }
    }  

    // Handle the 'lyrics' command
    if (commandName === 'lyrics') {
      const query = options.getString('query');
  
      try {
        let searchQuery;
        
        // If no query, try to get the currently playing song
        if (!query) {
          const nowPlaying = await yukufy.nowPlaying();
          
          if (!nowPlaying) {
            return await interaction.reply('No song playing at the moment and no search query provided.');
          }
  
          // Use the currently playing song for the lyrics search
          searchQuery = `${nowPlaying.artist} ${nowPlaying.title}`;
        } else {
          // Use the provided query
          searchQuery = query;
        }
  
        // Search for the lyrics
        const lyrics = await yukufy.lyrics(searchQuery);
  
        if (!lyrics) {
          return await interaction.reply('Lyrics not found.');
        }
  
        // Check if lyrics exceed 2000 characters
        if (lyrics.length > 2000) {
          // Create a Buffer with the lyrics and send as a .txt file
          const lyricsBuffer = Buffer.from(lyrics, 'utf-8');
          
          await interaction.reply({
            content: '🎤 Lyrics are too long, see the file:',
            files: [{
              attachment: lyricsBuffer,
              name: 'lyrics.txt'
            }]
          });
        } else {
          // Send lyrics normally if under 2000 characters
          await interaction.reply(`🎤 Lyrics:\n${lyrics}`);
        }
      } catch (error) {
        await interaction.reply('❌ Error fetching lyrics.');
        console.error(error);
      }
    }
  });

  // Event fired when a song starts playing
  yukufy.on('playSong', ({ track }) => {
    const { title, artist, url, duration, source, likes, thumbnail, member, textChannel, guildId } = track;
    textChannel.send(`🎶 Now playing: **${artist} - ${title}** added by **${member.displayName}**`);
  });

  // Event fired when a song is added to the queue
  yukufy.on('addSong', ({ track }) => {
    const { title, artist, url, duration, source, likes, thumbnail, member, textChannel, guildId } = track;
    textChannel.send(`🎵 Song **${artist} - ${title}** added to queue by **${member.displayName}**`);
  });

  // Event fired when the queue finishes
  yukufy.on('finishQueue', ({ track }) => {
    track.textChannel.send('🔚 Music queue finished.');
  });

  // Event fired when the queue becomes empty
  yukufy.on('emptyQueue', ({ track }) => {
    track.textChannel.send('I was waiting, no more songs were added to the queue, so I am leaving...');
  });

  // Event fired when the client disconnects from the voice channel
  yukufy.on('clientDisconnect', () => {
    console.log('👋 Disconnected from the voice channel.');
  });

  // Event fired when there is an error in the player
  yukufy.on('playerError', () => {
    console.log('Error');
  });

  // Log in to Discord with the bot token
  await client.login(clientToken);

  /* Uncomment this section for additional error handling
  process.on("unhandledRejection", async (reason, promise) => {
    console.log("[AntiCrash] | [UnhandledRejection_Logs] | [start] : ===============");
    console.log("Unhandled Rejection at:", promise, "reason:", reason);
    console.log("[AntiCrash] | [UnhandledRejection_Logs] | [end] : ===============");
  });

  process.on("uncaughtException", async (err, origin) => {
    console.log("[AntiCrash] | [UncaughtException_Logs] | [Start] : ===============");
    console.log(`Uncaught exception: ${err}\n` + `Exception origin: ${origin}`);
    console.log("[AntiCrash] | [UncaughtException_Logs] | [End] : ===============");
  });

  process.on("uncaughtExceptionMonitor", async (err, origin) => {
    console.log("[AntiCrash] | [UncaughtExceptionMonitor_Logs] | [Start] : ===============");
    console.log(`Uncaught exception monitor: ${err}\n` + `Exception origin: ${origin}`);
    console.log("[AntiCrash] | [UncaughtExceptionMonitor_Logs] | [End] : ===============");
  });
  */
}

// Start the main function and handle errors
main().catch(console.error);

📢 Contributing

Contributions are welcome! Please fork the repository, make your changes, and submit a pull request. For major changes or new features, please open an issue to discuss them first.

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.

👥 Support

If you have any questions or need help, join our Discord server for support and updates.

Enjoy managing your Discord music experience with Yukufy!

Package Sidebar

Install

npm i yukufy

Weekly Downloads

365

Version

1.5.0

License

ISC

Unpacked Size

37.9 kB

Total Files

5

Last publish

Collaborators

  • cronegamesplays