Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
421: Building a Simple Discord Bot
When you first dive into building a Discord bot with discord.py, your instinct is probably to go straight for the on_message event. It feels intuitive: the bot sees a message, you check what the message says, and then you do something. I've seen a lot of junior devs start this way, and for a bot that only does one thing—like responding "Pong!" to "Ping!"—it's perfectly fine. But the moment you want to build something actually useful, like a team coffee tracker, this approach falls apart.
The If-Else Nightmare
Imagine we're building a bot that lets team members log how many coffees they've had today. In a naive implementation, you'd put everything inside a single asynchronous function. You'd have to manually check if the message starts with your prefix, then split the string to figure out which command was called, and then manually cast the remaining text into an integer for the coffee count.
@bot.event
async def on_message(message):
if message.author == bot.user:
return
if message.content.startswith('!'):
parts = message.content.split(' ')
command = parts[0][1:] # Strip the '!'
if command == 'coffee':
try:
count = int(parts[1])
await message.channel.send(f"Logged {count} coffees!")
except (IndexError, ValueError):
await message.channel.send("You forgot the number!")
elif command == 'stats':
await message.channel.send("Here are the totals...")
# Imagine 20 more 'elif' blocks here
This is what I call the "if-else nightmare." It's brittle. If a user adds an extra space, your split(' ') might return empty strings. If they forget an argument, your bot crashes or throws a messy IndexError. More importantly, as you add features, this one function becomes a thousand-line monster that's impossible to test or maintain. You're spending more time writing boilerplate string parsing than actually building features.
Leveraging the Command Framework
The professional way to handle this is to move away from raw events and use the commands.Bot class. This shifts the responsibility of parsing the message from you to the library. Instead of one giant switchboard, you define discrete functions decorated with @bot.command(). This allows you to use Python's type hinting to handle argument conversion automatically.
from discord.ext import commands
bot = commands.Bot(command_prefix='!', intents=discord.Intents.all())
@bot.command()
async def coffee(ctx, amount: int):
# The library already checked if 'amount' is an integer
await ctx.send(f"Logged {amount} coffees!")
@bot.command()
async def stats(ctx):
await ctx.send("Here are the totals...")
Notice the difference. I didn't have to split the string, I didn't have to wrap the input in a try/except block for the integer conversion, and I didn't have to write a single if statement to route the command. The ctx (context) object replaces the raw message, giving you easier access to the channel, the author, and the server in one neat package.
Where the Trade-off Lies
You might wonder why anyone ever uses on_message. The trade-off is control versus convenience. The command framework is a layer of abstraction. If you need to build a bot that reacts to *every* message (like a profanity filter or a custom auto-responder), the framework won't help you—you'll still need on_message.
However, there's a catch: if you use both, on_message will actually block your commands from running. Because on_message is an event that fires every time, it "consumes" the message. To fix this, you have to explicitly tell the bot to keep processing the message by adding await bot.process_commands(message) at the end of your event handler. It's a small detail, but it's the most common reason people think their commands "stopped working" after adding a custom event.
📋 Practical Task
Build a Team Task-Assigner Bot
Create a Discord bot using the commands.Bot framework that implements a task assignment system. Your bot should have two specific commands:
!assign [user] [task_name]: This should take a member object and a string, then respond with a confirmation message like: "Task 'Fix CSS' has been assigned to @User!"!clear: This should simply respond letting the user know the task list has been wiped.
Requirements:
- Use type hinting in the
assigncommand so that the bot automatically resolves the member mention into adiscord.Memberobject. - Ensure you are using a
command_prefix. - Implement a custom
on_messageevent that prints the content of every message to the console, but ensure your!assignand!clearcommands still function correctly.
There are no comments for now.