IRC bots are one of the most rewarding programming projects you can build. They are practical, interactive, and give you a deep understanding of how network protocols work under the hood. Whether you want to automate channel moderation, provide useful information to users, or just create something fun, building an IRC bot with Python is the perfect way to get started.
In this comprehensive tutorial, we will walk through the entire process of creating an IRC bot from scratch using nothing but Python's standard library. No third-party frameworks, no abstraction layers. Just raw sockets, the IRC protocol, and your own code. By the end, you will have a fully functional bot running on TwistedNET's IRC network that responds to commands, handles errors gracefully, and can be extended with any feature you can imagine.
Why Build an IRC Bot?
// Practical reasons to dive in
Building an IRC bot teaches you fundamental concepts that apply across all of software engineering. You will learn about TCP socket programming, text-based protocol parsing, event-driven architecture, and state management. These are the same skills used in building web servers, API clients, and distributed systems.
Beyond the educational value, IRC bots serve genuinely useful purposes. Channel bots can moderate conversations, log important discussions, provide real-time notifications from external services, run trivia games, look up weather data, translate messages, or even serve as bridges to other platforms. On TwistedNET's chat rooms, bots are a valued part of the community.
Python is the ideal language for this project. Its readable syntax makes the protocol logic easy to follow, its standard library includes everything you need for socket and SSL connections, and its string manipulation capabilities make parsing IRC messages straightforward. You do not need to install a single package to get started.
Prerequisites
// What you need before we start
Any modern Python 3 version will work. Check yours with python3 --version
Functions, loops, string handling, and basic OOP. Nothing advanced required.
Use TwistedNET's web client or install one from our IRC clients page.
VS Code, Vim, Emacs, or any editor you are comfortable with.
Understanding the IRC Protocol
// How IRC works at the wire level
Before writing any code, it helps to understand what IRC actually is at the protocol level. IRC (Internet Relay Chat) is a text-based protocol that operates over TCP. Every message between your bot and the server is a simple line of text terminated by \r\n. There are no binary headers, no complex framing, and no compression. This simplicity is what makes IRC so accessible to implement from scratch.
When your bot connects to an IRC server like irc.twistednet.org, it needs to perform a handshake by sending a few specific commands. The server responds with numeric codes and text messages. Your bot reads these responses, parses them, and reacts accordingly. The entire conversation is human-readable, which makes debugging straightforward.
Here are the core IRC commands your bot needs to know. For a full list, check our IRC commands reference:
# Connection registration NICK mybotname # Set your bot's nickname USER mybotname 0 * :My IRC Bot # Set username and realname # Channel operations JOIN #channel # Join a channel PART #channel :Goodbye # Leave a channel PRIVMSG #channel :Hello world # Send a message to a channel PRIVMSG username :Hello # Send a private message # Server interaction PING :server # Server sends this to check if you're alive PONG :server # You must respond with this QUIT :Reason # Disconnect from the server
Messages received from the server follow a specific format. A typical channel message looks like this:
:nick!user@host PRIVMSG #channel :This is the message text
The prefix (everything before the first space, starting with a colon) identifies who sent the message. The command comes next, followed by parameters. Understanding this format is crucial because your bot will need to parse every incoming line to figure out what is happening and how to respond.
Building the Minimal Bot
// From zero to connected in under 100 lines
Let us start by building the simplest possible IRC bot. This initial version will connect to TwistedNET using SSL, register a nickname, join a channel, and respond to a basic command. We will use only Python's built-in socket and ssl modules.
#!/usr/bin/env python3 """A minimal IRC bot for TwistedNET.""" import socket import ssl # Configuration SERVER = "irc.twistednet.org" PORT = 6697 # SSL port NICK = "MyPyBot" CHANNEL = "#twisted" REALNAME = "Python IRC Bot" def create_ssl_connection(server, port): """Create an SSL-wrapped socket connection to the IRC server.""" raw_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ssl_context = ssl.create_default_context() wrapped_socket = ssl_context.wrap_socket( raw_socket, server_hostname=server ) wrapped_socket.connect((server, port)) return wrapped_socket def send_raw(sock, message): """Send a raw IRC message, appending CRLF.""" sock.send(f"{message}\r\n".encode("utf-8")) print(f">> {message}") def main(): # Connect with SSL print(f"Connecting to {SERVER}:{PORT} with SSL...") irc = create_ssl_connection(SERVER, PORT) print("Connected!") # Register with the server send_raw(irc, f"NICK {NICK}") send_raw(irc, f"USER {NICK} 0 * :{REALNAME}") # Buffer for incomplete lines buffer = "" while True: # Receive data from server data = irc.recv(4096).decode("utf-8", errors="replace") if not data: print("Disconnected from server.") break buffer += data lines = buffer.split("\r\n") buffer = lines.pop() # Keep incomplete line in buffer for line in lines: if not line: continue print(f"<< {line}") # Respond to PING to stay connected if line.startswith("PING"): pong_response = line.replace("PING", "PONG", 1) send_raw(irc, pong_response) continue # Join channel after welcome message (numeric 001) if " 001 " in line: send_raw(irc, f"JOIN {CHANNEL}") continue # Parse PRIVMSG for commands if "PRIVMSG" in line: parts = line.split(" ", 3) if len(parts) >= 4: sender = parts[0].split("!")[0][1:] target = parts[2] message = parts[3][1:] # Respond to !hello if message.strip() == "!hello": reply_to = target if target.startswith("#") else sender send_raw(irc, f"PRIVMSG {reply_to} :Hello, {sender}!") if __name__ == "__main__": main()
Save this as bot.py and run it with python3 bot.py. You should see the connection handshake scroll by, and your bot will join the channel. Type !hello in the channel using your IRC client, and the bot will greet you by name.
Understanding the Message Parser
// Breaking down IRC messages like a pro
The minimal bot above works, but its message parsing is fragile. Let us build a proper parser that handles any IRC message correctly. According to RFC 2812, an IRC message has this structure:
[:prefix] command [params] [:trailing]
Here is a robust parsing function that handles all cases:
def parse_irc_message(raw_line): """Parse a raw IRC message into its components. Returns a dict with keys: prefix, command, params, trailing """ prefix = "" trailing = "" # Extract prefix if present if raw_line.startswith(":"): prefix, raw_line = raw_line[1:].split(" ", 1) # Extract trailing parameter (everything after " :") if " :" in raw_line: raw_line, trailing = raw_line.split(" :", 1) # Split remaining into command and params parts = raw_line.split() command = parts[0] if parts else "" params = parts[1:] if len(parts) > 1 else [] return { "prefix": prefix, "nick": prefix.split("!")[0] if "!" in prefix else prefix, "command": command, "params": params, "trailing": trailing, } # Example usage: line = ":[email protected] PRIVMSG #twisted :!hello world" msg = parse_irc_message(line) print(msg["nick"]) # "Ghost" print(msg["command"]) # "PRIVMSG" print(msg["params"]) # ["#twisted"] print(msg["trailing"]) # "!hello world"
Building a Full-Featured Bot
// Adding commands, error handling, and reconnection
Now that we understand the fundamentals, let us build a production-quality IRC bot using object-oriented design. This version includes multiple commands, automatic reconnection, admin authentication, channel logging, and clean error handling.
#!/usr/bin/env python3 """TwistedNET IRC Bot - Full featured example.""" import socket import ssl import time import datetime import re import urllib.request import json class IRCBot: def __init__(self, server, port, nick, channels, admins=None): self.server = server self.port = port self.nick = nick self.channels = channels self.admins = admins or [] self.sock = None self.connected = False self.buffer = "" self.start_time = time.time() self.commands = { "!help": self.cmd_help, "!ping": self.cmd_ping, "!uptime": self.cmd_uptime, "!time": self.cmd_time, "!about": self.cmd_about, "!weather": self.cmd_weather, } def connect(self): """Establish an SSL connection to the IRC server.""" raw_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) raw_sock.settimeout(300) ctx = ssl.create_default_context() self.sock = ctx.wrap_socket(raw_sock, server_hostname=self.server) self.sock.connect((self.server, self.port)) self.connected = True print(f"[+] Connected to {self.server}:{self.port}") self.send(f"NICK {self.nick}") self.send(f"USER {self.nick} 0 * :TwistedNET Python Bot") def send(self, message): """Send a raw message to the server.""" if self.sock and self.connected: self.sock.send(f"{message}\r\n".encode("utf-8")) def say(self, target, message): """Send a PRIVMSG to a channel or user.""" self.send(f"PRIVMSG {target} :{message}") def parse(self, raw_line): """Parse a raw IRC line into components.""" prefix = nick = "" trailing = "" if raw_line.startswith(":"): prefix, raw_line = raw_line[1:].split(" ", 1) nick = prefix.split("!")[0] if " :" in raw_line: raw_line, trailing = raw_line.split(" :", 1) parts = raw_line.split() return { "prefix": prefix, "nick": nick, "command": parts[0], "params": parts[1:], "trailing": trailing } def handle_message(self, msg): """Process a parsed IRC message.""" cmd = msg["command"] if cmd == "PING": self.send(f"PONG :{msg['trailing']}") elif cmd == "001": for chan in self.channels: self.send(f"JOIN {chan}") elif cmd == "PRIVMSG": self.handle_privmsg(msg) elif cmd == "433": # Nickname in use - append underscore self.nick += "_" self.send(f"NICK {self.nick}") def handle_privmsg(self, msg): """Handle incoming PRIVMSG commands.""" sender = msg["nick"] target = msg["params"][0] if msg["params"] else return text = msg["trailing"].strip() reply_to = target if target.startswith("#") else sender # Check for registered commands cmd_word = text.split()[0].lower() if text else "" if cmd_word in self.commands: self.commands[cmd_word](sender, reply_to, text) # Admin commands if sender in self.admins: if cmd_word == "!quit": self.send("QUIT :Admin shutdown") self.connected = False elif cmd_word == "!join": args = text.split() if len(args) > 1: self.send(f"JOIN {args[1]}") # --- Command handlers --- def cmd_help(self, sender, reply_to, text): self.say(reply_to, "Available commands: !help, !ping, !uptime, " "!time, !about, !weather <city>") def cmd_ping(self, sender, reply_to, text): self.say(reply_to, f"{sender}: Pong!") def cmd_uptime(self, sender, reply_to, text): elapsed = int(time.time() - self.start_time) hours, remainder = divmod(elapsed, 3600) minutes, seconds = divmod(remainder, 60) self.say(reply_to, f"Uptime: {hours}h {minutes}m {seconds}s") def cmd_time(self, sender, reply_to, text): now = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC") self.say(reply_to, f"Current time: {now}") def cmd_about(self, sender, reply_to, text): self.say(reply_to, "I'm a Python IRC bot running on TwistedNET! " "Source: https://twistednet.org/blog/irc-bot-python") def cmd_weather(self, sender, reply_to, text): args = text.split(maxsplit=1) if len(args) < 2: self.say(reply_to, "Usage: !weather <city>") return city = args[1] try: url = f"https://wttr.in/{city}?format=3" req = urllib.request.Request(url, headers={ "User-Agent": "TwistedNET-Bot/1.0" }) resp = urllib.request.urlopen(req, timeout=5) self.say(reply_to, resp.read().decode().strip()) except Exception: self.say(reply_to, f"Could not fetch weather for '{city}'") def run(self): """Main loop with automatic reconnection.""" while True: try: self.connect() while self.connected: data = self.sock.recv(4096).decode( "utf-8", errors="replace" ) if not data: break self.buffer += data lines = self.buffer.split("\r\n") self.buffer = lines.pop() for line in lines: if line: msg = self.parse(line) self.handle_message(msg) except (socket.error, ssl.SSLError, OSError) as e: print(f"[!] Connection error: {e}") finally: if self.sock: self.sock.close() self.connected = False if not self.connected: print("[*] Reconnecting in 30 seconds...") time.sleep(30) if __name__ == "__main__": bot = IRCBot( server="irc.twistednet.org", port=6697, nick="PyBot", channels=["#twisted", "#bots"], admins=["YourNick"] ) bot.run()
Adding URL Title Fetching
// Automatically display page titles when URLs are shared
One of the most useful features you can add to a channel bot is URL title fetching. When someone pastes a link in the channel, the bot automatically fetches the page and displays its title. This saves everyone from having to click links just to see what they are about.
import re import html import urllib.request def fetch_url_title(url, timeout=5): """Fetch the <title> of a given URL.""" try: req = urllib.request.Request(url, headers={ "User-Agent": "Mozilla/5.0 (compatible; IRCBot/1.0)" }) resp = urllib.request.urlopen(req, timeout=timeout) # Only process HTML content content_type = resp.headers.get("Content-Type", "") if "text/html" not in content_type: return None # Read first 16KB only data = resp.read(16384).decode("utf-8", errors="replace") match = re.search(r"<title[^>]*>(.+?)</title>", data, re.IGNORECASE | re.DOTALL) if match: title = html.unescape(match.group(1)).strip() title = " ".join(title.split()) # Normalize whitespace return title[:200] except Exception: pass return None # In your handle_privmsg method, add: urls = re.findall(r"https?://\S+", text) for url in urls[:2]: # Limit to 2 URLs per message title = fetch_url_title(url) if title: self.say(reply_to, f"[ {title} ]")
Error Handling and Reconnection
// Making your bot resilient
A production IRC bot needs to handle network failures gracefully. Connections drop, servers restart, and netsplits happen. Your bot should detect these situations and automatically reconnect without crashing. The full-featured bot above already includes basic reconnection, but here are some additional hardening techniques.
Implement exponential backoff for reconnection attempts. Instead of waiting a fixed 30 seconds between retries, increase the delay each time the connection fails consecutively. This prevents hammering the server when there is a genuine outage.
def run_with_backoff(self): """Run the bot with exponential backoff on reconnection.""" retry_delay = 5 # Start at 5 seconds max_delay = 300 # Cap at 5 minutes while True: try: self.connect() retry_delay = 5 # Reset on successful connection self.loop() except Exception as e: print(f"[!] Error: {e}") finally: if self.sock: self.sock.close() print(f"[*] Reconnecting in {retry_delay}s...") time.sleep(retry_delay) retry_delay = min(retry_delay * 2, max_delay)
You should also handle the case where your bot's nickname is already in use. The IRC server will send numeric reply 433 (ERR_NICKNAMEINUSE). Our bot already handles this by appending an underscore, but you could also implement GHOST via NickServ if your nick is registered.
Deploying Your Bot on a VPS
// Keep it running 24/7
Running the bot on your local machine works for testing, but for a permanent presence on TwistedNET, you will want to deploy it on a VPS (Virtual Private Server). A small VPS with 512MB of RAM is more than enough. Here is how to set it up as a systemd service on a Linux server:
# Create a systemd service file
sudo nano /etc/systemd/system/ircbot.service
Add the following content:
[Unit] Description=TwistedNET IRC Bot After=network.target [Service] Type=simple User=botuser WorkingDirectory=/home/botuser/ircbot ExecStart=/usr/bin/python3 /home/botuser/ircbot/bot.py Restart=always RestartSec=10 [Install] WantedBy=multi-user.target
Then enable and start the service:
sudo systemctl daemon-reload sudo systemctl enable ircbot sudo systemctl start ircbot # Check status sudo systemctl status ircbot # View logs journalctl -u ircbot -f
Bot Etiquette on TwistedNET
// Be a good citizen
Running a bot on an IRC network comes with responsibility. TwistedNET welcomes well-behaved bots, but there are some guidelines you should follow to be a good network citizen:
Rate limit your output. Never flood a channel with rapid messages. Add a delay between messages if your bot sends multiple lines. A good rule is no more than 3 messages per 5 seconds.
Ask permission from channel ops. Before putting your bot in a channel, ask the channel operators if it is welcome. Not every channel wants bot activity.
Identify your bot clearly. Set a descriptive realname and make it obvious that your client is a bot, not a human user. Some networks have a bot user mode you can set.
Handle errors silently. If an external API call fails, do not spam the channel with error tracebacks. Send a brief, user-friendly error message or stay silent.
Respect user privacy. Do not log private messages or store user data without consent. TwistedNET values privacy above everything, and your bot should too.
Start Building Today
You now have everything you need to build, customize, and deploy an IRC bot on TwistedNET. Clone the code, modify the commands, add your own features, and bring your bot to life. The community is ready to see what you create.
Need help along the way? Join #twisted and ask. Our operators and community members are always happy to help fellow developers.
CONNECT TO TWISTEDNETirc.twistednet.org:6697 (SSL)