A Python IRC bot with reconnect logic, a plugin-based command and trigger system, URL sniffing, weather lookup, and optional YouTube metadata.
- Quick Start
- Features
- Plugin System
- Example Basic Plugin
- Example Trigger Plugin
- Requirements
- Installation (Linux)
- Installation (Windows)
- Running the Bot
- Windows Example Function (PowerShell)
- Commands
- Admin PM Commands
- URL and YouTube Behavior
- Weather Behavior
- Database Setup
- Nickname Handling
- Configuration Example
- License
- Copy
config.example.jsontoconfig.json. - Set at least one enabled network with
server,nick, andchannels. - Install dependencies:
- with
venv:python -m pip install -r requirements.txt - without
venvon Linux: installPyMySQLfor the system Python interpreter
- Start the bot:
- Linux:
./bot.pyorpython3 bot.py - Windows:
python bot.py
- Use Admin PM Commands in a private message if you need administrative setup.
- TLS connection support (
use_tls, enabled by default) - Multi-network support via required
networksarray (one bot connection per entry) - Built-in plugin system with one plugin per command/trigger under
plugins/ - Administrative PM commands without prefix, protected by hostmask + password authentication
- Per-network plugin activation via
enabled_plugins/disabled_plugins - Per-network
enabledflag and reconnect delay (reconnect_delay_seconds) - Automatic reconnect loop on network errors
- Responds to server
PINGwithPONG - Joins and tracks multiple channels
- Optional oidentd.conf generation for ident spoofing (
oidentd_confpath) - Saves joined channels in MySQL and restores them on restart
- Optional per-network flood protection for outgoing chat messages
- Optional SASL PLAIN authentication (
CAPnegotiation) - Optional NickServ identify command and nickname reclaiming
- Optional
performcommands after successful connect (for example: user mode) - Role-based channel rights (
+q +a +o +h +v) according to server-advertisedPREFIXsupport, applied on login with the highest configured level - Optional admin raw command forwarding via PM trigger
- Optional raw logging per network to
log/chat-<network_key>.log - URL sniffing in channel messages:
- Detects posted
http/httpslinks - Fetches HTML title / first heading topic
- Filters common spam patterns
- Flags blocked/dead links in database
- Detects posted
- RSS/Atom feed reader via
!rss <feed|url>with optional configured feed aliases - Optional RSS auto-posting every 10 minutes for new feed entries to a DB-configured channel (
rssannounceadmin command) - Weather command using the OpenWeatherMap-style lookup
- Postal code fallback (German ZIP code lookup)
- Optional YouTube link parsing via YouTube Data API
- German/English output (
language: "de"or"en")
- Built-in plugins live in
plugins/<name>/plugin.py. - Commands and triggers are loaded dynamically on bot startup.
- If
enabled_pluginsis empty or omitted, all built-in plugins are loaded except those listed indisabled_plugins. - If
enabled_pluginscontains entries, only these plugins are loaded for that network.
Commands:
help— Displays available commands and their usage (sent as NOTICE)version— Shows the bot version and GitHub repository URL (sent as NOTICE, also works in private messages asversion/verwithout login)ping [nick]— Sends a ping responsepong [nick]— Sends a pong responselag— Measures latency in millisecondsecho <text>— Echoes text back to the user (sent as NOTICE)slap <nick>— Performs a slap action on a userdart [nick]— Throws a dart at the nickdarttop10— Displays the top 10 dart playersmydartstats— Shows your personal dart statistics (sent as NOTICE)weather <location|plz>— Looks up weather forecast using postal code or location namerss <feed|url>— Reads the latest entry from a configured RSS/Atom feed alias or a direct feed URLurl <id>— Retrieves a stored URL from the databaserandomurl— Retrieves a random URL from the databaseadmin— Provides administrative PM commands (user management, roles, channel modes, raw IRC commands)
Triggers:
unreal— Replies with an action when a message contains the word "unreal"urlsniffer— Automatically scans posted URLs, fetches HTML titles, and stores them in the database
A minimal command plugin consists of:
- a folder under
plugins/ - a
plugin.pyfile - one or more handler functions with the signature
handler(bot, context, arg) - a final
PLUGIN = PluginSpec(...)export
Example structure:
plugins/
hello/
plugin.py
Example implementation:
from plugin_system import CommandSpec, PluginSpec
def handle_hello(bot, context, arg: str) -> None:
target = arg.strip() if arg.strip() else context.source_nick
bot.send_privmsg(context.reply_target, f"Hello {target}!")
PLUGIN = PluginSpec(
name="hello",
commands=(
CommandSpec(canonical="hello", handler=handle_hello, help_sort=30),
),
)How it works:
nameis the internal plugin name used byenabled_plugins/disabled_plugins.canonicalis the command token the bot resolves after the normal prefix, so the example becomes!hello.context.source_nickis the nickname of the user who sent the command.context.reply_targetis automatically the channel or the sender nick in a private message.argcontains everything after the command name.bot.send_privmsg(...)sends the response back to IRC.
Useful context fields:
context.source_nickcontext.targetcontext.messagecontext.reply_targetcontext.command_prefixcontext.is_private_message
Useful bot helpers:
bot.send_privmsg(target, message)bot.send_notice(target, message)bot.send_action(target, message)bot.primary_command_name("...")bot.tr("...")for translated plugin messages
For a very small real example, see the existing ping plugin in plugins/ping/plugin.py.
A trigger plugin reacts to normal chat messages without requiring the command prefix.
Example structure:
plugins/
cheer/
plugin.py
Example implementation:
import re
from plugin_system import MessageHandlerSpec, PluginSpec
def handle_cheer(bot, context) -> None:
if re.search(r"\bgg\b", context.message, re.IGNORECASE):
bot.send_action(context.reply_target, "cheers loudly!")
PLUGIN = PluginSpec(
name="cheer",
message_handlers=(
MessageHandlerSpec(handler=handle_cheer),
),
)How it works:
message_handlersare called for every incomingPRIVMSG.- A trigger plugin decides on its own whether it wants to react.
context.messagecontains the full incoming text.context.reply_targetis the channel in public chat and the sender nick in private chat.bot.send_action(...)sends a CTCP ACTION, similar to/mein IRC clients.
Typical uses:
- keyword reactions
- automatic helper replies
- moderation or logging hooks
- URL or content sniffing
For a small real example, see plugins/unreal/plugin.py.
- Python 3.10+
- MySQL/MariaDB (required for dart stats, URL storage, and persistent channels)
Setup paths:
- Use a virtual environment if you want isolated Python packages.
- Use the system Python only if
PyMySQLis installed for that exact interpreter.
- Open the project folder:
cd ./ircbot-python
- Create your config file:
cp config.example.json config.json
- Create and activate a virtual environment:
python3 -m venv .venvsource .venv/bin/activate
- Edit
config.json:
- Define
networks(required) and set per-network values (server,port,use_tls,nick,channels, ...) - Top-level values act as defaults for all entries in
networks - Database settings:
mysql_host,mysql_port,mysql_user,mysql_password,mysql_database - Required for weather lookups:
weather_appidfrom your OpenWeatherMap account/API keys page - Optional:
weather_default_location,youtube_api_key,language,enabled_plugins,disabled_plugins,raw_chat_logging_enabled,flood_protection_enabled,rss_announce_channel, SASL/NickServ options,oidentd_conf(path to .oidentd.conf file, e.g.,~/.oidentd.conf)
Flood/spam delay behavior:
flood_protection_enabled: globally as default or per network override to fully enable/disable outgoing chat throttlingflood_protection_enabled: globally as default or per network override to fully enable/disable outgoing chat throttling and the startup delay for public triggersflood_min_interval_ms: minimum delay between outgoing chat messagesflood_burst+flood_window_seconds: burst/window rate limiting for outgoing chat messages
- Install dependencies:
python -m pip install -r requirements.txt
Linux without virtual environment:
- Ubuntu / Debian:
sudo apt updatesudo apt install python3-pymysql
- Fedora:
sudo dnf install python3-PyMySQL
- Then run the bot with your system Python, for example:
./bot.py
Note:
- If you run the bot without
venv,PyMySQLmust be installed for the same Python interpreter that startsbot.py. - If you prefer
pipwithoutvenv, usepython3 -m pip install --user -r requirements.txtinstead of the distro package.
- Open the project folder:
cd .\ircbot-python
- Create your config file:
Copy-Item config.example.json config.json
- Create and activate a virtual environment:
python -m venv .venv.\.venv\Scripts\Activate.ps1
- Install dependencies:
python -m pip install -r requirements.txt
- Linux foreground (default):
python bot.py
- Linux background control with PID file:
- Start:
python bot.py --start - Stop:
python bot.py --stop - Restart:
python bot.py --restart
- Start:
- Windows foreground (default):
python bot.py
- Windows background control with PID file:
- Start:
python bot.py --start - Stop:
python bot.py --stop - Restart:
python bot.py --restart
- Start:
You can add this helper function to your PowerShell profile and control the bot with one command:
function Invoke-IrcBot {
param(
[ValidateSet('start','stop','restart','run')]
[string]$Action = 'run',
[string]$BotPath = '.'
)
Push-Location $BotPath
try {
switch ($Action) {
'start' { python .\bot.py --start }
'stop' { python .\bot.py --stop }
'restart' { python .\bot.py --restart }
'run' { python .\bot.py }
}
}
finally {
Pop-Location
}
}Examples:
Invoke-IrcBot -Action runInvoke-IrcBot -Action startInvoke-IrcBot -Action restartInvoke-IrcBot -Action stop
Default prefix: !
The following commands are available when the corresponding plugins are enabled:
!help/!hilfe!ping [nick]!pong [nick]!lag!echo <text>!slap <nick>!dart [nick]!dart top10!darttop10!mydartstats/!meinedartstats!weather <location>/!wetter <ort|plz>!rss <feed|url>!url <id>!randomurl/!zufallsurl
Notes:
!help,!echo, and!mydartstatsare sent as NOTICE to the requesting user.- If no admin user exists yet, the bot asks in the terminal for an initial
ident@hostand password before a foreground run and also before--start/--restart. - Administrative commands only work in a private message to the bot and must be sent without the normal command prefix.
- Admin rights require
login <password>from the matchingident@hostfirst. - New users default to the non-admin
userrole; channel rights can be assigned per role or per user and are only applied while the matching user is logged in. - If multiple rights are configured for the same channel, the bot only applies the highest supported mode.
- Supported member modes are limited to what the IRC server advertises via
005 PREFIX=.... - If
raw_chat_logging_enabledis enabled for a network, all incoming and outgoing IRC raw lines are appended tolog/chat-<network_key>.login the format<unix_timestamp_ms> <raw_irc_line>, for example1539452142405 NOTICE AUTH :*** Looking up your hostname. - If
!darthas no argument, the caller nickname is used. !lagmeasures latency in nanoseconds and displays a readable millisecond value (for sub-millisecond latency as decimal, e.g.0.123 ms) plus rawnsin parentheses.- If
weather_default_locationis set, weather can be requested without arguments. - If
rss_feedscontains aliases,!rsswithout arguments shows the available feed names. - If RSS announce channels are configured in the database (
rssannounce), the RSS plugin checks configured feeds every 10 minutes and posts only new entries to all configured channels (tracked in the database).
Trigger plugins:
unreal: replies with an action when a message containsunrealurlsniffer: scans posted URLs and stores/sniffs them automatically
Send these as a private message to the bot, without the normal command prefix:
helphelp authhelp usershelp roleshelp modeshelp rawlogin <password>authenticates your current hostmask for admin commands.logoutends the current admin session.whoamishows your current admin role and rights.listroleslists all stored roles.listuserslists all stored admin users.roleadd <role> [admin=on] [raw=on]creates a new role.roleflag <role> <admin|raw> <on|off>enables or disables one role flag.adduser <name> <ident@host> <password> [role]creates an admin user.deluser <ident@host>deletes an admin user.setrole <ident@host> <role>assigns a different role to a user.rolemode <role> <#channel> <mode>allows a channel mode for one role.rolemode-del <role> <#channel> <mode>removes a role/channel mode rule.usermode <ident@host> <#channel> <mode>adds a user-specific channel mode rule.usermode-del <ident@host> <#channel> <mode>removes a user-specific rule.apply <nick> <#channel> <ident@host>applies stored mode rules to a nick immediately.rssannounce [#channel[,#channel...]|+#channel[,#channel...]|-#channel[,#channel...]|off]shows/sets RSS announce channels in the database for this network.rssannounce +#chan3adds a single channelrssannounce -#chan2disables a single channel from the current list- after changing channels, the bot also prints the currently configured RSS feed aliases
- after changing channels, the bot also posts the configured RSS feed aliases directly into the target channel(s)
- after changing channels, the bot also posts the current/latest entry per configured RSS feed into the target channel(s)
raw <IRC RAW LINE>sends one raw IRC line directly to the server.
Examples:
login geheimroleadd opsrolemode ops #mychan oadduser alice ident@example.org geheim opsapply Alice #mychan ident@example.org
- Posted URLs are normalized and processed once per runtime session.
- Non-HTML links are marked as dead links.
- Suspicious URLs/topics are blocked by simple spam keyword/domain checks.
!url <id>loads a URL from thebot_urltable.!randomurlpicks a random non-blocked, non-dead URL frombot_url.- If
youtube_api_keyis configured and a YouTube URL is detected, the bot can show:- Title
- Channel
- Duration
- Publish date
- Views / likes / comments (if available)
- Uses the OpenWeatherMap lookup flow.
- Supports city names as well as postal code queries like
12345or12345,de. - Uses
weather_appidfrom config; this is the OpenWeatherMap API key/app id you get from your OpenWeatherMap account. - Weather lookups fail with a clear message if it is missing.
- Place names and decimal formatting follow the configured bot language (
de/en). - Returns temperature, "feels like", humidity, precipitation, and wind when the API provides them.
- In channels without mode
+c, the bot can use IRC control codes (bold/color) for richer output.
On startup, the bot tries to:
- create the configured database (if missing)
- create required tables:
bot_dartbot_urlbot_channels
If MySQL is unavailable, startup continues, but DB-backed features may not work.
- If the nickname is already in use (
433), the bot appends_. - With nickname protection enabled, the bot periodically tries to reclaim the preferred nickname.
See config.example.json for all available options, including:
- Multi-network mode (
networks):- required (legacy single-network root fields are no longer supported)
- top-level values are defaults for every network entry
- each network object can override any setting
enabledcan disable a network without deleting its config (recommended instead of commenting, since JSON has no comments)reconnect_delay_secondscontrols reconnect interval per networknetwork_keycan be used to control channel persistence key inbot_channels.network(must be unique)
- Plugin control:
enabled_pluginsloads only the listed plugins for a network when not emptydisabled_pluginsexcludes listed plugins whenenabled_pluginsis empty
- Optional raw chat logging:
raw_chat_logging_enabledenables per-network raw logs for all IRC lines- the bot creates a
logdirectory automatically when needed - log files are written as
log/chat-<network_key>.log - top-level value acts as default; each network entry can override it
- Optional RSS aliases:
rss_feedsmaps alias names to RSS/Atom feed URLs- top-level value acts as default; each network entry can override it
- Optional RSS auto-post default:
rss_announce_channelis only used as initial fallback/default- the active per-network value is stored in the database and managed via
rssannounce [#channel[,#channel...]|+#channel[,#channel...]|-#channel[,#channel...]|off] - if neither DB value nor fallback is set, automatic RSS posting is disabled
This project is licensed under the MIT License. See LICENSE for details.