https://www.theverge.com/2021/8/24/22640024/youtube-discord-groovy-music-bot-closure
:no_upscale()/cdn.vox-cdn.com/uploads/chorus_asset/file/22803360/99nShh7.png)
https://www.theverge.com/2021/9/12/22669502/youtube-discord-rythm-music-bot-closure

Recently, youtube kill’s some of the most used music robots on discord, and other public ones may also be asked to be shut down, so today let us make our discord bot!!!
Step 1 : Create your bot
Goto this website https://discord.com/developers/applications, I have already created one, click the “New Application” button.

And name your bot.

If you create the bot successfully, you should see it appear on your main page, just click it.
Go and copy your APPLICATION ID, and fill in this link:
https://discordapp.com/oauth2/authorize?&client_id=xxxxxxxxxxxxx&scope=bot&permissions=8 # replace xxxxx with you Id

with this link, you can invite it to your channel.

Step 2 : authorization token
Go to the “bot” and click “copy”, this will give you your Token

Step 3 : Start coding
Here, I’m will build the bot with python in windows, so you will need python on your computer
open cmd and download discord’s API
pip install discord.py

Open sublime and paste the following code to start a simple bot
save and open with cmd.
we know this is not enough…, we need a music bot, not a ping-pong idiot…, next, I will speed up and point out the difficulties and solutions I encountered.
Step 4 : A music bot
A normal music bot should be able to play youtube music and have the following functions.
- play : Add a song or a playlist to the Queqe and invite the bot to the voice channel
- pause : pause
- skip : skip current song
- list : show the playlist
- clear : clear the playlist
- loop : loop the playlist
- leave : leave the voice channel
we define severals function that we want our music bot have.
Step 5 : Play a music
Let’s see how others play music
with this code we can understand how everything works, first, we need FFmpeg.
https://www.ffmpeg.org/download.html
when a user types a play command, the code will get its message channel (ctx), and get the Author’s voice channel (channel), then with “channel.connect()” to connect the bot to the voice channel.
channel = ctx.message.author.voice.channel #this line will get the user's voice channel
voice = await channel.connect() # connect to the user's voice channel
voice.play(discord.FFmpegPCMAudio(song_path), after = lambda e: somefuciton())
# play the music , 'after' will run when the song is finish.
Step 6 : Create a class
you may ask: why do we need a class?
If our robot is to serve different servers, it means that there will be different ctx, channel, and voice, we also need to queue the songs of each channel, so the best choice is to create a class, each class serves a server.
class MusicBot:
def __init__(self,channel, voice , ctx):
self.music_msg = None # The message for current music
self.loop = False # Enable loop or not
self.live = True # Kill this Musicbot if self.live = False
self.channel = channel # Voice channel
self.channelid = channel.id # This Music serves channel Id
self.floder = "music" # Folder to store music
self.ctx = ctx # ctx
self.voice = voice # voice client
self.queqed = [] # music queqed for play
self.passed = [] # Played music
self.state = 0 # 0:not playing , 1:playing , 2:pause
self.rejoin_c = 0 # rejoin every 20 songs
self.dont_stop = 0 # will play untill dont_stop = 3 to check again
so I define a class that needs channel, voice, and ctx as input.
Step 7 : Download youtube music
we know we can use youtube_dl to download the music.
pip install youtube-dl
self.ytl = {
'format': '249/250/251',
"outtmpl" : f"{self.floder}/{this_song_name}",
'noplaylist': False
}
with youtube_dl.YoutubeDL(self.ytl) as ydl:
ydl.download([this_song_url])
But I think it is difficult to manage which songs the user has queued and youtube_dl will download the entire playlist before I play the music, of course, I can use a thread, But it is too complex.
So, I will use a youtube data API to grab the playlist’s songs and download one at a time and play it. the following is the youtube API code, however, I will not explain this code since our main character is a discord music bot.
with this API, I can write a function to add music to the queue
def add_thread(self,url):
print("[*] Adding rest in this thread ... ")
output = grab_playlist(url,50)
for each in output[10:50]:
self.queqed.append(each)
print("[*] Adding Thread done.")
print(f"[*] Qeuqed {len(self.queqed)} songs ->",self.channelid)
async def add(self,url):
print("[*]", url)
if ("list" in url):
print("[*] Adding a play list in", self.channelid)
output = grab_playlist(url,10)
for each in output[0:10]:
self.queqed.append(each)
threading.Thread(target = self.add_thread, args=(url,)).start()
else:
print("[*] Adding a single video in", self.channelid)
self.queqed.append((get_title(url),url))
print("\n[*]",self.channelid,"-> Enqueued :",len(self.queqed), "the remaining songs will continue add in the background")
if (self.state ==0):
self._next() # the play music function
add function can handle a video or a playlist, if it is a video, just add it directly, but if it is a playlist, I will first grab the first 10 videos, the rest will throw in a thread to continue, this is to speed up the time to start the song, otherwise, the user will feel impatient.
Play command
Users_class = {}
@client.command(brief="Play music (My wonderful singing )")
async def play(ctx, url : str):
if not ctx.message.author.voice:
await ctx.send('you are not connected to a voice channel')
return
else:
channel = ctx.message.author.voice.channel
if (channel.id in Users_class):
if ctx.guild.voice_client not in client.voice_clients:
await Users_class[channel.id].kill() # kill the music player
del Users_class[channel.id]
print("[*] rejoin the voice channel")
voice = await channel.connect()
MB = MusicBot(channel, voice , ctx)
Users_class[channel.id] = MB
await Users_class[channel.id].add(url)
else:
voice = await channel.connect()
MB = MusicBot(channel, voice , ctx)
print(f"[*] creating Class id : {id(MB)} for serving channel",channel.id)
Users_class[channel.id] = MB
await MB.add(url)
In play command, I will create the music bot class and save the class to a dictionary by using channel id as the key.
# play next music
async def _next(self):
if (len(self.queqed) == 0):
if (self.loop): # refill the music queqed for play with Played music
self.queqed = self.passed
self.passed = []
else:
self.state = 0
return
self.state = 1 # State = 1 -> start playing
self.this_song = self.queqed.pop(0) # get the song
self.passed.append(self.this_song) # Store in history music
print("[*] playing :", self.this_song[0],"in", self.channelid)
this_song_url = self.this_song[1]
this_song_name = self.this_song[0]
# This for is beacuse the youtube_dl will change the filename
# when handling special char
for each_char in ['"', "'", ":", "|"]:
if (each_char in this_song_name):
this_song_name = this_song_name.replace(each_char,"#")
# youtube_dl will create a floder for \ and /
for each_char in ["\\", "/"]:
if (each_char in this_song_name):
this_song_name = this_song_name.replace(each_char,"")
song_path = os.path.join(self.floder , this_song_name)
# download youtube music
if ( not os.path.isfile(song_path)):
self.ytl = {
'format': '249/250/251',
"outtmpl" : f"{self.floder}/{this_song_name}",
'noplaylist': False,
}
self.dowloading = await self.ctx.send(f'... Downloading {this_song_name}')
print("[*] downloading ->", this_song_name,"\n")
with youtube_dl.YoutubeDL(self.ytl) as ydl:
ydl.download([this_song_url])
print("\n[*] ------------ download successful ------------")
# Check if bot is in the voice channel
if self.ctx.guild.voice_client not in client.voice_clients:
print("[*] get kicked from",self.channelid)
return
# send currently playing music
self.music_msg = await self.ctx.send(f':musical_note: Now playing ({len(self.passed)}/{len(self.queqed)+len(self.passed)}) : {this_song_name} :musical_note:')
###################### I will explian this later
FFMPEG_OPTS = {
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
'options': '-vn'
}
self.rejoin_c += 1
if (self.rejoin_c == 10):
self.voice.play(
discord.FFmpegPCMAudio(song_path, **FFMPEG_OPTS),
after=lambda e: print("[*] reconnecting the ffmpeg , error : ",e)
)
self.rejoin_c = 0
await asyncio.sleep(5)
######################
# play the music, call _endsong when the song finish
self.voice.play(
discord.FFmpegPCMAudio(song_path),
after = lambda e: asyncio.run_coroutine_threadsafe(self._endsong(e), client.loop)
)
# check every three song
self.dont_stop +=1
if (self.dont_stop > 3):
client.loop.create_task(self.check())
This is a quite long function, but most of them are actually pretty easy to understand, except the FFMPEG_OPTS part, this part is to solve a known problem.
The robot will suddenly have no sound while playing a song, I often encounter this problem when I am testing,
To solve this problem, you have to use that FFMPEG_OPTS variable. It will reconnect the bot to the source.
But there is a bug, I’m not sure if it is my problem or how, my FFmpeg will show “reconnect” Option not found, I still trying to fix it.
Step 8 : Complete every thing
This is my MusicBot with all the functions we want.
Then try to run the bot and see if everything works well.

Step 9 : Develop on server
Now try to put this robot on a server, you can use Heroku or something else, for me , I will put it on the Raspberry Pi
So, first I will use python to open a simple webserver on windows to send my bot’s code to Raspberry Pi .
python -m http.server

Cause I’m connect to the same route, so I can use the local internet to send my bot, use Ip config to get you computer’s local IP
ipconfig

Just wget the ip: port/file and you can grab the file

Set up your environment and run the server
pip install google-api-python-client
pip install discord.py
pip install youtube_dl
sudo apt install ffmpeg
