npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

yukufy

v1.5.0

Published

A Player Package built with Node.js, using Spotify and SoundCloud as search and streaming engines, easy and simple to use for your bot.

Downloads

365

Readme

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!