Build a Discord Lyric Bot

image This guide will show you how to create a Discord bot from scratch which will allow the user to search for songs and lyrics and play the songs in a voice channel.

View Project View Code Clone Project

Goals

  1. Create a fully functional Discord bot which lets outputs the lyrics of any song
  2. Give the option to play songs in a voice channel too

Strategy

  1. Create a bot on Discord website
  2. Create a Discord server and add your Bot
  3. Get your MusixMatch API key
  4. Set up Qoom environment to develop a Discord bot
  5. Write code to get input from user and output the result from MusixMatch

Need Help?

Get help from the Qoom team and our community members. Join Qoom Community


1. Create a bot on Discord

Create an account on discord if you havent already. Then visit the Discord developer portal to start creating your bot.

Click New Application, type a name (you could name it whatever you'd like) and select Create.

On the left there will be some options — select Bot, then Add Bot, and name the bot.

2. Create a Discord server and add your Bot

Create a server on Discord.

To connect the bot to your server, go to the Discord developer portal and click "OAuth2", check the "bot" checkbox and then copy the URL to open in another tab. Click "Authorize" and select the server you made.

OAuth

3. Get a MusixMatch API Key

Visit the MusixMatch develeoper website and sign up. Make sure to enter a valid email address because they will send a confirmation email.

The email they send will contain a confirmation link which will provide you with the API key. Make sure to save this key for later.

4. Setting up the Qoom environment

Go to the Qoom website and login/signup to be directed to your Qoom Space.

Once there, click New Folder and name it whatever you want. New Project

Click on one of the files which will redirect you to an editor where you can edit your files, and click on New File and create a file named discord.js

On the left there should also be a Backend button which opens up a menu on the right where you should click the Create a Discord Bot option.

Fill in the following fields accordingly

Enter your discord token:
Paste the token from the Discord Developer Portal

Update any Environment Settings (JSON):

{
	"MUSIXMATCHKEY": "your musixmatch key"	
}

Any npm packages:

ytdl-core
yt-search
ffmpeg-static
@discordjs/opus

Hit Save then Update

In discord.js:

const Discord = require('discord.js');
const fs = require('fs');

function start(token) {

	const prefix = "%"
	const https = require('https')
	const EventEmitter = require('events');
	const musixmatch = "https://api.musixmatch.com/ws/1.1/"
	const ytdl = require( "ytdl-core" );
	const ytsearch = require( 'yt-search' )
	const client = new Discord.Client();
	
	
	client.on( "message", async ( message ) => {
		if ( !message.content.startsWith( prefix ) || message.author.bot )
			return;

		let command = message.content.slice( prefix.length ).split( " " )[0]; 
		if( command == "hello" )
		{
			message.reply( "Hi!" );
		}
			
		let content = message.content;
	});
	
	client.login(token);
}
module.exports = { start };

Now, in the Backend menu hit Push to upload your code.

In your Discord server, send a message saying %hello and you should get a response from the bot.

4. Write code to get input from user and output the result from MusixMatch

It is recommended that you skim over the MusixMatch documentation. And from now on, all the code that we write will go inside the start function

Create an HTTP request function

function httpRequest( params, requestType, callback )
{
    let keys = Object.keys( params )
    let paramUrl = "?format=json&";

    keys.forEach( e => {
        paramUrl += e + "=" + encodeURIComponent( params[ e ] ) + "&"
    })
    
    let url = musixmatch + 
              requestType +
              paramUrl +
              "&page_size=20" +
              "&s_track_rating=desc" +
              "&s_artist_rating=desc" +
              "&apikey=" + process.env.MUSIXMATCHKEY

    https.get( url, function(res){
        var body = '';
        res.on('data', chunk => {
            body += chunk;
        });
        res.on('end', function(){
            let jsonRes = JSON.parse( body.slice(body.indexOf( "{"), body.lastIndexOf( "}") + 1  ) );
            callback( jsonRes );
        });
    }).on('error', function(e){
        console.log("Got an error: ", e);
    });
}

Add some more variables and create a function to output tracks from a query

const apiParams = {
    getlyricsbysong: "matcher.lyrics.get",
    getlyricsbyid: "track.lyrics.get",
    search: "track.search" 
}

const states = {
    DEFAULT: "default",
    NUMBER: "number"
}

let state = states.DEFAULT

let trackQuery = "";
let tracks = [];
let playing = false;

let emmiter = new EventEmitter();

const getTracksByQuery = ( message, query, params, category ) => {
    httpRequest( params, apiParams.search, (res) => { 
        let msg = "";
        let obj = res.message.body.track_list;
        if ( obj )
            obj.forEach( (e, i) =>{
                msg += `${ i + 1 }. **${e.track.track_name}** (${e.track.artist_name}) \n`;
            })
        let embed = new Discord.MessageEmbed()
                    .setTitle( `Tracks by the ${category} **"${ query.join( " " ) }"**` )
                    .setDescription( msg.slice( 0,4096 ) ) ;
        message.channel.send( embed );
        message.channel.send( "Choose a track number from the list above" );

        state = states.NUMBER;

        emmiter.removeAllListeners()
        // Recreates the emmiter based on the new information
        let handleTrackSelection = async ( args ) => {
            onTrackSelected( message, args, obj )
        }
        emmiter.on( 'trackselected', handleTrackSelection );
    })
}

This function will make an HTTP request to the MusixMatch API to get a list of tracks using the user's query. Then creates an event listener for when the user selects a track from the list, which will call onTrackSelected() which we will write next.

Writing onTrackSelected()

const onTrackSelected = async ( message, args, obj ) => {
    let num = args.num - 1;

    // Organizing the track data
    let tracks = obj.map( a => ({ 
                                    track: a.track.track_name, 
                                    author: a.track.artist_name, 
                                    id: a.track.track_id,
                                    url: a.track.track_share_url,
                                })
                        );

    // If the input is within bounds
    if( args.num <= tracks.length )
    {
        message.reply( "Getting lyrics for **" + tracks[num].track + "** by " + tracks[num].author +"..." )
        trackQuery = tracks[num].track + " by " + tracks[num].author;
        const videos = await ytsearch( trackQuery )      
        httpRequest( { track_id: tracks[ num ].id }, apiParams.getlyricsbyid, ( json ) => {
            if( json.message.body )
            {
                let lyrics = json.message.body.lyrics.lyrics_body;
                message.channel.send( new Discord.MessageEmbed()
                                            .setDescription( 
                                                lyrics.slice( 0, lyrics.indexOf( "******* T" ) ) + 
                                                "\n [More Lyrics...](" + 
                                                tracks[ num ].url + 
                                                ") \n [Video]("+ videos.videos[0].url +")" )
                                            .setTitle( "Lyrics for " + trackQuery ) 
                                    )
            }
        })
    }
    else  
        message.channel.send( "The number you chose was out of bounds" );
}

Commands object

const commands = 
{
    help:
    {
        description: "Displays help menu",
        func: ( message ) => 
        {
            let msg = ""
            Object.keys( commands ).forEach( key => {
                msg += "`" + key + "` " + commands[key].description + "\n"
            })
            msg += "\n\nExample command:    `%q Blinding Lights The Weeknd`"
            message.channel.send( new Discord.MessageEmbed()
                .setTitle( "Bot Help")
                .setColor( 0x00ff00 )
                .setDescription( msg )
                .setThumbnail('https://res.cloudinary.com/practicaldev/image/fetch/s--CT4BsRWH--/c_fill,f_auto,fl_progressive,h_320,q_auto,w_320/https://dev-to-uploads.s3.amazonaws.com/uploads/organization/profile_image/2736/f920082b-79f1-40e5-ac80-693bd900b716.png')  
            )
        }
    },
    artist:
    {
        description: "Search for tracks with artist name",
        func: ( message ) => 
        {
            let query = message.content.split( " " ).slice( 1 ) 
            getTracksByQuery( message, query, { q_artist: query }, "artist" )
        }
    },

    lyrics:
    {
        description: "Search for tracks with certain lyrics",
        func: ( message ) => 
        {
            let query = message.content.split( " " ).slice( 1 ) 
            getTracksByQuery( message, query, { q_lyrics: query }, "lyrics" )
        }
    },

    track:
    {
        description: "Search for tracks with track name",
        func: ( message ) => 
        {
            let query = message.content.split( " " ).slice( 1 ) 
            getTracksByQuery( message, query, { q_track: query }, "track name" )
        }
    },

    q:
    {
        description: "Search for anything",
        func: ( message ) => 
        {
            let query = message.content.split( " " ).slice( 1 ) 
            getTracksByQuery( message, query, { q: query }, "query" )
        }
    }
}

Add some code to the client.on("message") listener

client.on( "message", async ( message ) => {
	if ( !message.content.startsWith( prefix ) || message.author.bot )
		return;

	let command = message.content.slice( prefix.length ).split( " " )[0]; 
	
	if( state == states.DEFAULT )
    	commands[command] && commands[command].func( message );

	else if( state == states.NUMBER )
	{
	    let num = parseInt( key );
	    if( !isNaN( num ) )
	    {
	        emmiter.emit( 'trackselected', { num: parseInt( num ) } );
	    }
	    else
	    {
	        commands[command] && commands[command].func( message );
	    }
	    state = states.DEFAULT;
	}
});

This is how your function should look

Test out your code

The Weeknd Embed

Congrats you've built a Discord Lyric bot!





© Sooraj Gupta 2021