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.
Explore the full documentation here.
- 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 Bot Example by lNazuna
- Yoruka by CroneGamesPlays
Install Yukufy
via npm:
npm install yukufy
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');
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);
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
});
yukufy.pause();
yukufy.resume();
yukufy.skip();
yukufy.stop();
const volume = 50; // Volume level between 0 and 100
yukufy.setVolume(volume);
To fetch lyrics for the current song or a specific song:
const lyrics = await yukufy.lyrics("NEFFEX Fight Back");
console.log(lyrics);
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
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);
});
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);
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.
This project is licensed under the MIT License - see the LICENSE file for details.
If you have any questions or need help, join our Discord server for support and updates.
Enjoy managing your Discord music experience with Yukufy!