Build a Discord Lyric Bot
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.
Goals
- Create a fully functional Discord bot which lets outputs the lyrics of any song
- Give the option to play songs in a voice channel too
Strategy
- Create a bot on Discord website
- Create a Discord server and add your Bot
- Get your MusixMatch API key
- Set up Qoom environment to develop a Discord bot
- 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.
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.
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);
});
}
- In this function,
params
are the request parameters.requestType
is what tells MusixMatch what kind of information we are sending and what kind of response we want back.- And
callback
is what we do with the response.
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" );
}
- This function takes in parameters on the selected track from the user, then organizes the data and then creates a message with the appropriate data.
- The message also includes a YouTube video which is retrieved by using the
ytsearch
library
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" )
}
}
}
- The object contains the command names which each contain their description and a function
- The
help
command sends a message which lists all the commands in this object and their description.
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
- Upload your code by clicking the
Push
button like before. - First test out the help message by sending
%help
in your Discord server - Then send a message saying
%artist The Weeknd
(or any other artist) The bot should reply with a message the at looks like this.
- Now you can "select" a track with its respective number. For example to select: "Blinding Lights" you would send
%2
- The bot should reply with the lyrics of the song.
Congrats you've built a Discord Lyric bot!
- Test out other commanbds with different queries to see what you get!
- Use the
%lyrics
command to get songs that contain certain lyrics
© Sooraj Gupta 2021