Framework Documentation
This document provides an overview and a functional guide for the nIRC Bot Framework. It is designed for high-performance, non-blocking interaction with IRC servers. The framework abstracts raw IRC protocol handling, offering an elegant, decorator-based interface for defining commands and reacting to server events.
Core Configuration
The following constants manage the bot's identity and connection parameters. In a production environment, these should be loaded from secure environment variables or a configuration file.
Constant | Type | Description | Example |
---|---|---|---|
BOT_NICK | str | The nickname the bot will use on the server. | "ServiceBot" |
BOT_USERNAME | str | The bot's ident/username. | "svc_agent" |
BOT_REALNAME | str | The bot's "real name" shown in WHOIS. | "IRC Service Agent v1.0" |
BOT_PASSWORD | Optional[str] | Password for NickServ identification. Set to None if not required. |
"MySecurePass" |
COMMAND_PREFIX | str | The character(s) used to trigger bot commands. | "!" |
CHANNELS_TO_JOIN | Dict[str, Optional[str]] | Map of channels to join on connection with optional keys. | {"#public": None, "#admin": "key123"} |
Core Abstractions
The framework is built on a few key classes that abstract away the complexity of the IRC protocol, allowing you to focus on building features.
The Bot
Class (The Engine)
The main class responsible for managing the connection, registering handlers, dispatching events, and managing background tasks.
Constructor Arguments (__init__
)
Argument | Type | Description | Required |
---|---|---|---|
prefix | str | The command prefix (e.g., ! or . ). | Yes |
conn | IRCConnection | The pre-configured IRCConnection object used for I/O. | Yes |
nick | str | The bot's primary nickname. | Yes |
username | str | The bot's username (IDENT). | Yes |
realname | str | The bot's real name (GECOS). | Yes |
password | Optional[str] | Password for NickServ identification, if required. (Default: None ) | No |
downloads_dir | str | Directory for saving files from DCC send requests. (Default: downloads ) | No |
General Methods
Method / Property | Type | Description |
---|---|---|
start(channel_map) | async method | Establishes the connection, performs initial setup, and enters the main event loop. |
send_message(...) | method | Sends a raw PRIVMSG to a specified channel or user. |
send_raw(message) | async method | Sends a raw IRC protocol command directly to the server. |
start_task(task_func, *args) | method | Starts a background task defined with @bot.task . |
Decorators for Event Binding
Decorator | Contextual Event | Context Type | Description |
---|---|---|---|
@bot.command(name) | Standard Command | Context | Binds a function to a command triggered via COMMAND_PREFIX (e.g., !help ). |
@bot.on_ready() | Connection Ready | Bot instance | Fires **once** after successful IRC registration (numeric 001 /376 ). Ideal for initialization. |
@bot.on_raw() | All Raw Lines | Context | Fires for every single, unfiltered line received from the IRC server. Use ctx.full_line . |
@bot.on_message() | Any Channel Message | Context | Fires on all PRIVMSG events that are **not** commands. Essential for keyword checks. |
@bot.on_join() | User joins channel | Context | Binds a function to the JOIN IRC event. |
@bot.on_leave() | User parts or quits | Context | Binds a function to the PART or QUIT IRC event. |
@bot.on_dcc() | DCC File Transfer | DCCFile instance | Fires when the bot receives a DCC SEND request and initiates the file transfer process. |
@bot.prefix_command(prefix) | Prefixless Command | Context | Binds to a message starting with prefix (e.g., > ). |
The DCCFile
Class (DCC File Transfer)
This class is passed when the @bot.on_dcc()
event fires. It represents an incoming Direct Client-to-Client (DCC) SEND request and provides access to file metadata and transfer progress.
Key Attributes (Metadata and Status)
Attribute | Type | Description |
---|---|---|
context | Context | The context object. |
filename | str | The name of the file being sent, as reported in the DCC command. |
extension | str | The file extension. |
filesize | int | The total expected size of the file in bytes. |
ip_address | str | The dotted IPv4 address (e.g., 12.34.56.78 ) of the sending client. |
port | int | The port number the sender is listening on for the DCC connection. |
is_done | bool | Whether the file is done sending. |
is_good | bool | Whether the file operation completed without errors. |
percent | float | The percentage of the operation. |
progress | int | The progress in bytes of how much data got transfered. |
sender | str | The nickname of the user who initiated the file transfer. |
full_path | str | The full local path where the file is being saved. |
safe_filename | str | The filename but with underscores instead of spaces. |
save_dir | str | The parent directory to where the file will be saved into. |
Helper Methods
Method / Property | Type | Description | Example |
---|---|---|---|
start_transfer | property (None) | Starts the transfer process and returns a bool depending on whether it failed or succeeded. | dcc_file.start_transfer() |
The IRCConnection
Class (Network I/O)
This class manages the asynchronous socket connection, handles raw reading and writing of IRC data, and buffers messages using asyncio
streams.
Constructor Arguments (__init__
)
Argument | Type | Description | Required |
---|---|---|---|
host | str | The IRC server hostname or IP address. | Yes |
port | int | The IRC server port (usually 6667 ). | Yes |
logger | Optional[Logger] | A Logger instance for logging network events. If None , a silent logger is used. (Default: None ) | No |
Key Methods
Method | Type | Description |
---|---|---|
connect() | async method | Establishes the socket connection to the IRC server. |
send_raw(message) | async method | Sends a raw IRC protocol message line to the server. |
read_line() | async method | Asynchronously reads a single, stripped line from the server. Returns None on connection close. |
close() | method | Closes the network connection writer and updates state. |
The Logger
Class (Logging Utility)
A utility class for structured logging output to the console and an optional file, with filtering based on configurable log levels.
Constructor Arguments (__init__
)
Argument | Type | Description | Default |
---|---|---|---|
file_path | Optional[str] | Path to the log file. If provided, all logs are appended to this file. | None |
min_level | int (LogLevel) | The minimum log level (e.g., LogLevel.INFO ) required for a message to be outputted. | LogLevel.INFO |
Logging Methods
Method | Log Level | Description |
---|---|---|
debug(...) | DEBUG (10) | For fine-grained debugging information, including raw network traffic. |
info(...) | INFO (20) | For standard confirmation messages and general bot state changes. |
error(...) | ERROR (40) | For critical, non-fatal errors that prevent certain operations. |
raw_recv(line) | DEBUG (10) | Specifically logs a raw line received from the IRC server. |
raw_send(message) | DEBUG (10) | Specifically logs a raw message being sent to the IRC server. |
Logging Constants: LogLevel
and LOG_PREFIX
The LogLevel
Class
Defines standard logging levels used for filtering output verbosity in the Logger
.
Constant | Value | Description |
---|---|---|
DEBUG | 10 | The lowest level; typically includes all raw network I/O. |
INFO | 20 | Standard status messages and successful operations. |
ERROR | 40 | Critical failures or issues preventing core functionality. |
The LOG_PREFIX
Dictionary
A dictionary mapping log categories to short prefixes for organized, human-readable log output.
Key | Prefix | Category |
---|---|---|
"NET" | "[NET]" | Network connection status and disconnects. |
"ERROR" | "[ERROR]" | Errors caught during processing. |
"RAW" | "[RAW]" | Unfiltered, raw IRC lines (typically DEBUG level). |
"DISPATCH" | "[DISPATCH]" | Event and command handler dispatch actions. |
"TASK" | "[TASK]" | Background task lifecycle events. |
"CORE" | "[CORE]" | Bot internal state changes (e.g., registration). |
"USER" | "[USER]" | User events like JOIN/PART/QUIT. |
"COMMAND" | "[COMMAND]" | When a user executes a command. |
"DCC" | "[DCC]" | Direct Client-to-Client (DCC) file transfer events, including connection and progress. |
The Context
Class (The Data Carrier)
An object passed to all command and event handlers. It contains the message payload, high-level response methods, and access to channel and user objects.
Attribute | Type | Description |
---|---|---|
bot | Bot | A direct reference to the main Bot instance. Useful for accessing global properties or methods. |
logger | Logger | A convenience reference to bot.logger for quick logging within a context. |
author | str | Nickname of the user who triggered the event. |
target | str | Destination where the bot should reply. |
message | str | The full raw message text received. |
command_name | str | The invoked command name (only for standard/prefix commands). |
arg | str | The full argument string following the command/prefix. |
args | List[str] | The arguments split by whitespace. |
full_line | str | The complete, raw IRC line from the server (used primarily with @bot.on_raw ). |
Response and Interaction Methods
Method / Property | Type | Description | Example |
---|---|---|---|
send(text) | method | Sends a message to ctx.target . | ctx.send("Reply in channel.") |
reply(text) | async method | Alias for send() for improved readability. | await ctx.reply("Confirmation received.") |
author_obj | property (Member) | Returns a Member object for the message author. | ctx.author_obj.send("Sent privately.") |
channel_obj | property (Channel) | Returns a Channel object representing the target of the message. | ctx.channel_obj.unban('BadUser') |
get_member(nick) | method (Member) | Retrieves a Member object for any specified nickname. | ctx.get_member('Admin') |
unban(target_user) | method | Removes ban for a target_user from the current ctx.target channel. | ctx.unban('BannedUser') |
The Member
Class (User Interaction & Moderation)
A utility class representing a user, focused on private communication and moderator actions.
Attributes
Attribute | Type | Description |
---|---|---|
bot | Bot | A direct reference to the main Bot instance. |
Methods
Method | Type | Description | Example |
---|---|---|---|
send(text) | method | Sends a direct private message (PRIVMSG) to the member's nickname. | user.send("Your query is complete.") |
kick(channel, reason) | method | Kicks the member from the specified channel . | user.kick('#general', 'Spam') |
ban(channel, reason) | method | Bans the member's hostmask from the specified channel . | user.ban('#general', 'Flood') |
mute(channel) | method | Mutes the member (server-side mute/voice removal in channel). | user.mute('#general') |
unmute(channel) | method | Unmutes the member. | user.unmute('#general') |
is_muted(channel) | method (bool) | Checks the local cache if the user is currently muted/voiced down in the channel. | if user.is_muted('#general'): ... |
The Channel
Class (Target Abstraction)
A class representing an IRC channel, providing methods for channel-specific management.
Attributes
Attribute | Type | Description |
---|---|---|
bot | Bot | A direct reference to the main Bot instance. |
Methods
Method | Type | Description |
---|---|---|
get_topic() | method (str) | Returns the current topic of the channel. |
set_topic(new_topic) | method | Sets a new topic for the channel. |
unban(user_mask) | method | Removes a ban by user_mask (e.g., *!*@host ). |
Asynchronous Task Runner
The framework includes a simple mechanism for defining and running background, repeating asynchronous tasks without blocking the main event loop. **Tasks must be explicitly started using bot.start_task(...)
, usually within an @bot.on_ready()
handler.**
Task Definition and Control
Decorator / Method | Parameters | Description |
---|---|---|
@bot.task(...) | interval (float), max_repeat (int, optional) | Decorator to mark an async function as a repeating task. max_repeat sets a limit, after which the task stops. |
bot.start_task(func, *args) | task_func , args (optional) | Initiates the task defined by @bot.task , passing any arguments required by the function. |
Example Bot Script
This script demonstrates command handling, prefix commands, asynchronous tasks, and event handlers.
from nIRC.irc import Bot, Context, IRCConnection, Logger, DCCFile
from typing import Optional
import asyncio, time
BOT_NICK = "nIRC"
COMMAND_PREFIX = "!"
CHANNELS_TO_JOIN = {
"#chat": None,
"#logs": None,
}
SERVER = "127.0.0.1"
PORT = 6667
SERVER_PASSWORD: Optional[str] = None
@Bot.prefix_command(">")
async def ai_query_command(ctx: Context):
"""
Fires on any message starting with '>' (e.g., >what is my status).
This is great for clean, non-standard bot interactions.
"""
if ctx.arg:
response = f"AI Service received query from {ctx.author}: '{ctx.arg}'. Processing..."
await ctx.reply(response)
await ctx.send(str(ctx.args))
await ctx.send(str(ctx.full_line))
else:
await ctx.reply("Please provide a query after the '>'.")
@Bot.command("calc")
async def calculate_command(ctx: Context):
"""
Demonstrates using ctx.arg and ctx.args.
A simplified math evaluator.
"""
if not ctx.arg:
await ctx.reply("Usage: !calc (e.g., 5 + 3 * 2)")
return
try:
# Warning: Using eval() is generally unsafe in production bots.
# This is for demonstration purposes only.
result = eval(ctx.arg)
await ctx.reply(f"Result for '{ctx.arg}': {result}")
except Exception:
await ctx.reply(f"Could not calculate the expression: {ctx.arg}")
@Bot.command("mod")
async def mod_command(ctx: Context):
"""
Handles moderation actions using Member and Channel abstractions.
Usage: !mod [reason/value]
"""
if len(ctx.args) < 2:
await ctx.reply("Usage: !mod [value]")
return
action = ctx.args[0].lower()
target_nick_or_mask = ctx.args[1]
value_or_reason = " ".join(ctx.args[2:]) or "No reason provided."
if action == "kick":
member = ctx.get_member(target_nick_or_mask)
await member.kick(ctx.target, value_or_reason)
await ctx.reply(f"Attempted to kick {target_nick_or_mask} from {ctx.target}.")
elif action == "ban":
member = ctx.get_member(target_nick_or_mask)
await member.ban(ctx.target, value_or_reason)
await ctx.reply(f"Attempted to ban and kick {target_nick_or_mask} from {ctx.target}.")
elif action == "topic" and ctx.target.startswith('#'):
await ctx.channel_obj.set_topic(value_or_reason)
await ctx.reply(f"New topic set to: {value_or_reason}")
elif action == "unban" and ctx.target.startswith('#'):
await ctx.channel_obj.unban(target_nick_or_mask)
await ctx.reply(f"Attempted to remove ban mask: {target_nick_or_mask}")
else:
await ctx.reply(f"Unknown moderation action: {action}. Use kick, ban, topic, or unban.")
@Bot.command("pmuser")
async def pm_user_command(ctx: Context):
"""Sends a private message to a specified user."""
if len(ctx.args) < 2:
await ctx.reply("Usage: !pmuser ")
return
recipient_nick = ctx.args[0]
message_text = " ".join(ctx.args[1:])
await ctx._bot.send_message(recipient_nick, f"PM from {ctx.author}: {message_text}")
await ctx.reply(f"PM sent to {recipient_nick}.")
@Bot.on_message()
async def keyword_responder(ctx: Context):
"""Responds to specific keywords in a channel message."""
message = ctx.message.lower()
if "nirc status" in message.lower():
await ctx.reply(f"I am nIRC exampleBot, running on {SERVER}. My command prefix is '{COMMAND_PREFIX}'.")
elif "help" == message.strip().lower():
await ctx.reply(f"Available commands: !calc, !mod, !pmuser. See console for raw logs (type ERROR).")
@Bot.on_join()
async def greet_joiner(ctx: Context):
"""Sends a friendly greeting when a new user joins."""
if ctx.author != BOT_NICK:
await ctx.reply(f"Welcome, {ctx.author}! Type '{COMMAND_PREFIX}calc 1+1' to test a command.")
@Bot.on_raw()
async def raw_logger(ctx: Context):
"""Logs the raw line to the console (for demonstration only, triggers on all lines)."""
if "ERROR" in ctx.full_line or "NOTICE" in ctx.full_line:
print(f"[RAW LOG] IMPORTANT LINE: {ctx.full_line}")
@Bot.task(interval=10, max_repeat=5)
async def status_announcement(bot_instance: Bot, channel_name: str):
"""
A non-blocking task that posts a system status message to the #logs channel.
Shows the power of context-free scheduled actions.
"""
count = status_announcement.current_repeat
await bot_instance.send_message(
channel_name,
f"[HEARTBEAT] Task iteration {count}/5. Time: {time.strftime('%H:%M:%S')}. (Interval: 10s)"
)
@Bot.on_dcc()
async def get_file(file: DCCFile):
logger.info("USER", f"Accepting file '{file.filename}' from {file.sender}.")
await file.start_transfer()
@Bot.on_ready()
async def initialization_setup(bot: Bot):
"""
Runs once after connection and IRC registration is complete (after 376).
Starts tasks and can perform one-time setup actions.
"""
print("[NET] Running specialized initialization setup.")
if "#logs" in bot.channel_map:
bot.start_task(status_announcement, "#logs")
else:
print("[WARN] #logs channel not configured. Heartbeat task skipped.")
if "#chat" in bot.channel_map:
await bot.conn.send_raw(f"MODE #chat +m")
print("[NET] Set +m (moderated) on #chat as an example one-time setup.")
async def run_bot():
"""Main entry point to initialize and run the bot."""
global logger
logger= Logger(file_path= "log.txt", min_level= 0)
irc_connection = IRCConnection(SERVER, PORT, logger)
bot = Bot(
prefix=COMMAND_PREFIX,
conn=irc_connection,
nick=BOT_NICK,
username="nirc_bot",
realname="neko IRC bot framework example client",
password=SERVER_PASSWORD
)
try:
await bot.start(CHANNELS_TO_JOIN)
except Exception as e:
print(f"FATAL BOT ERROR: {e}")
finally:
irc_connection.close()
if __name__ == "__main__":
print("--- Starting Live Asynchronous nIRC Bot Framework Example ---")
print(f"Connecting as NICK: {BOT_NICK} to {SERVER}:{PORT}")
print(f"Channels configured: {', '.join(CHANNELS_TO_JOIN.keys())}")
try:
asyncio.run(run_bot())
except KeyboardInterrupt:
print("\n[BOT] Shutting down gracefully via Ctrl+C.")
except RuntimeError as e:
if "cannot run" in str(e):
print("\n[ERROR] Event loop error. If running in an interactive session, this is normal.")
else:
raise