Building PyMusicBot: A Custom Discord Music Bot
Discord bots have become a staple in many servers, offering functionality that enhances user experience, from moderation tools to entertainment features like music playback. As someone who spends a lot of time on Discord, I often found myself using bots to play music while hanging out with friends. But after a while, I started to wonder: why not build my own music bot?
That curiosity led me to create PyMusicBot, a simple Python-based music bot for Discord. In this post, I'll share the story behind the project, why I built it, and how I went about making it work.
The Inspiration: Why I Decided to Build My Own Bot
I’ve always been interested in programming and automation, so the idea of creating something that could improve my Discord experience felt natural. While there are already plenty of music bots out there, I found that many of them had issues, from inconsistent uptime to being overloaded with unnecessary features that I didn’t need. It made me think—what if I could build a lightweight bot that only does what I want it to do? A bot that is simple, efficient, and easy to manage.
I didn’t need a bot with hundreds of commands. I didn’t need a bot that could play music from every possible source. I just needed something that could join a voice channel, play music from YouTube, and let me manage a simple song queue. And so, PyMusicBot was born.
The Challenge: Creating a Simple Yet Functional Music Bot
Building a music bot isn’t as straightforward as you might think. There are several components involved, such as interacting with Discord’s API, downloading and streaming audio from YouTube, and managing the state of the bot (like keeping track of the queue and handling inactivity).
I chose Python for this project because it’s a language I’m comfortable with, and it has excellent libraries for building Discord bots—namely, discord.py for interacting with the Discord API and yt-dlp for downloading YouTube audio. These libraries made the process much easier, as they handle most of the heavy lifting, allowing me to focus on the core functionality of the bot.
One of the most interesting challenges I encountered was managing the bot's inactivity. I didn’t want the bot to stay connected to a voice channel indefinitely if it wasn’t being used. So, I implemented an inactivity timer that checks if the bot has been idle for a certain period and then disconnects it. This was an important feature, as it would ensure that the bot isn’t occupying resources unnecessarily when no one is actively using it.
The Process: Writing the Code
Setting Up the Bot
The first thing I did was set up the basic structure of the bot. I used discord.py to create a bot instance and set up a few basic commands like !play, !skip, and !stop. These commands would let users control the bot’s playback in voice channels.
Integrating YouTube Audio
To play music, I needed a way to extract audio from YouTube videos. This led me to yt-dlp, a powerful tool that allows you to extract video and audio from YouTube and other sites. By using yt-dlp in combination with ffmpeg, I was able to stream audio directly into Discord’s voice channel. The setup for this was surprisingly straightforward, and I was up and running quickly.
YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist': 'True'}
FFMPEG_OPTIONS = {
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
'options': '-vn'
}
Managing the Queue
With the basic playback functionality in place, I turned to managing the song queue. I wanted users to be able to add songs to a queue, skip songs, and even clear the entire queue when needed. Implementing a queue system was a challenge at first, but I decided to keep it simple: each guild (server) would have its own queue stored in a dictionary, and the bot would handle playback by pulling songs from the front of the queue.
song_queue = {}
@bot.command(name='play')
async def play(ctx, url):
guild_id = ctx.guild.id
if guild_id not in song_queue:
song_queue[guild_id] = []
song_queue[guild_id].append({'url': url})
await play_next_song(ctx)
Handling Inactivity
The inactivity feature was one of the most fun parts of the bot to implement. I didn’t want the bot to stay connected to the voice channel if no one was listening to the music. So, I added a timer that checks how long it’s been since the bot last played music. If the bot has been inactive for longer than a set limit, it will disconnect automatically.
async def check_inactivity(ctx):
guild_id = ctx.guild.id
while True:
await asyncio.sleep(10)
if guild_id in vc_dict and vc_dict[guild_id].is_connected():
elapsed = asyncio.get_event_loop().time() - last_activity_time[guild_id]
if elapsed >= INACTIVITY_LIMIT:
await vc_dict[guild_id].disconnect()
break
Adding the Final Touches
Finally, I wanted to give PyMusicBot a bit of personality, so I added a custom status feature. I felt that a bot should feel like more than just a machine, so I made it easy to set a custom status message that would appear while the bot is online. This gave the bot a more friendly, human-like touch.
The Docker Image: Hosting PyMusicBot Locally
Once the bot was up and running, I wanted a way to host it reliably without worrying about keeping my computer on all the time. I decided to create a Docker image for PyMusicBot, allowing me to host it easily on my Unraid server. Docker made this process incredibly smooth, as it provided a consistent environment for the bot to run in.
I created a Dockerfile that installs all the dependencies, sets up the bot, and ensures that it starts on boot. The advantage of using Docker is that I can run the bot in a container, keeping everything isolated from my main system and ensuring that any potential issues won’t affect the rest of the server.
Here’s the basic Dockerfile I used:
FROM python:3.9-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "bot.py"]
This setup allowed me to push the bot into a container, and with a few simple Docker commands, I could get it running on my Unraid server without issues. The containerised setup also makes it easier to update the bot, as I can simply rebuild and redeploy the Docker image when necessary.
The Result: PyMusicBot
After a few days of work, I had a fully functioning music bot that did exactly what I wanted. It could join voice channels, play music, manage a queue, and disconnect automatically when not in use. Plus, the custom status added that extra bit of personality.
By hosting the bot in Docker on my Unraid server, I now have a reliable, always-on bot that can be used whenever I want. I don’t need to keep my personal computer running 24/7, and I can manage the bot easily from within Unraid.
I’m really happy with how PyMusicBot turned out. It’s not the most complex bot, but it does exactly what I need it to do—without all the fluff. It’s lightweight, customisable, and efficient.
Why Build a Custom Bot?
You might be wondering: why go through the effort of building your own bot when there are plenty of pre-made ones out there? For me, the answer is simple: control.
Building my own bot allowed me to tailor it exactly to my needs, ensuring that it only had the features I wanted and none of the unnecessary extras. Plus, it was a fun project that helped me learn more about Python and how Discord bots work under the hood.
Ultimately, creating PyMusicBot was a great learning experience and a practical tool that I now use regularly. Whether you're building your own bot for fun or to meet specific needs, I highly recommend diving into the world of Discord bot development. It's an incredibly rewarding process, and the possibilities are endless.
Final Thoughts
Creating PyMusicBot was a journey of learning, experimenting, and simplifying things. It was exciting to see a basic idea come to life, and now I have a bot that I can use in my Discord servers without worrying about complicated settings or overloaded features.
Hosting it in Docker on my Unraid server ensures it's always available and easy to maintain.
Stay tuned for more projects, and happy coding!