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
prefixstrThe command prefix (e.g., ! or .).Yes
connIRCConnectionThe pre-configured IRCConnection object used for I/O.Yes
nickstrThe bot's primary nickname.Yes
usernamestrThe bot's username (IDENT).Yes
realnamestrThe bot's real name (GECOS).Yes
passwordOptional[str]Password for NickServ identification, if required. (Default: None)No
downloads_dirstrDirectory for saving files from DCC send requests. (Default: downloads)No

General Methods

Method / Property Type Description
start(channel_map)async methodEstablishes the connection, performs initial setup, and enters the main event loop.
send_message(...)methodSends a raw PRIVMSG to a specified channel or user.
send_raw(message)async methodSends a raw IRC protocol command directly to the server.
start_task(task_func, *args)methodStarts a background task defined with @bot.task.

Decorators for Event Binding

Decorator Contextual Event Context Type Description
@bot.command(name)Standard CommandContextBinds a function to a command triggered via COMMAND_PREFIX (e.g., !help).
@bot.on_ready()Connection ReadyBot instanceFires **once** after successful IRC registration (numeric 001/376). Ideal for initialization.
@bot.on_raw()All Raw LinesContextFires for every single, unfiltered line received from the IRC server. Use ctx.full_line.
@bot.on_message()Any Channel MessageContextFires on all PRIVMSG events that are **not** commands. Essential for keyword checks.
@bot.on_join()User joins channelContextBinds a function to the JOIN IRC event.
@bot.on_leave()User parts or quitsContextBinds a function to the PART or QUIT IRC event.
@bot.on_dcc()DCC File TransferDCCFile instanceFires when the bot receives a DCC SEND request and initiates the file transfer process.
@bot.prefix_command(prefix)Prefixless CommandContextBinds 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
contextContextThe context object.
filenamestrThe name of the file being sent, as reported in the DCC command.
extensionstrThe file extension.
filesizeintThe total expected size of the file in bytes.
ip_addressstrThe dotted IPv4 address (e.g., 12.34.56.78) of the sending client.
portintThe port number the sender is listening on for the DCC connection.
is_doneboolWhether the file is done sending.
is_goodboolWhether the file operation completed without errors.
percentfloatThe percentage of the operation.
progressintThe progress in bytes of how much data got transfered.
senderstrThe nickname of the user who initiated the file transfer.
full_pathstrThe full local path where the file is being saved.
safe_filenamestrThe filename but with underscores instead of spaces.
save_dirstrThe parent directory to where the file will be saved into.

Helper Methods

Method / Property Type Description Example
start_transferproperty (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
hoststrThe IRC server hostname or IP address.Yes
portintThe IRC server port (usually 6667).Yes
loggerOptional[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 methodEstablishes the socket connection to the IRC server.
send_raw(message)async methodSends a raw IRC protocol message line to the server.
read_line()async methodAsynchronously reads a single, stripped line from the server. Returns None on connection close.
close()methodCloses 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_pathOptional[str]Path to the log file. If provided, all logs are appended to this file.None
min_levelint (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
DEBUG10The lowest level; typically includes all raw network I/O.
INFO20Standard status messages and successful operations.
ERROR40Critical 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
botBotA direct reference to the main Bot instance. Useful for accessing global properties or methods.
loggerLoggerA convenience reference to bot.logger for quick logging within a context.
authorstrNickname of the user who triggered the event.
targetstrDestination where the bot should reply.
messagestrThe full raw message text received.
command_namestrThe invoked command name (only for standard/prefix commands).
argstrThe full argument string following the command/prefix.
argsList[str]The arguments split by whitespace.
full_linestrThe complete, raw IRC line from the server (used primarily with @bot.on_raw).

Response and Interaction Methods

Method / Property Type Description Example
send(text)methodSends a message to ctx.target.ctx.send("Reply in channel.")
reply(text)async methodAlias for send() for improved readability.await ctx.reply("Confirmation received.")
author_objproperty (Member)Returns a Member object for the message author.ctx.author_obj.send("Sent privately.")
channel_objproperty (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)methodRemoves 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
botBotA direct reference to the main Bot instance.

Methods

Method Type Description Example
send(text)methodSends a direct private message (PRIVMSG) to the member's nickname.user.send("Your query is complete.")
kick(channel, reason)methodKicks the member from the specified channel.user.kick('#general', 'Spam')
ban(channel, reason)methodBans the member's hostmask from the specified channel.user.ban('#general', 'Flood')
mute(channel)methodMutes the member (server-side mute/voice removal in channel).user.mute('#general')
unmute(channel)methodUnmutes 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
botBotA 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)methodSets a new topic for the channel.
unban(user_mask)methodRemoves 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