CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-sopel

Simple and extensible IRC bot framework written in Python with plugin architecture and database support

Pending
Overview
Eval results
Files

plugin-development.mddocs/

Plugin Development

Sopel's plugin system is the core of its extensibility, providing a decorator-based approach for creating bot functionality. Plugins are Python modules that use decorators to register functions as commands, rules, events, and other bot behaviors.

Capabilities

Command Decorators

Register functions to respond to specific IRC commands triggered by a command prefix (typically . or !).

def command(*command_names: str):
    """
    Register a function as a command.
    
    Args:
        *command_names (str): One or more command names that trigger this function
        
    Example:
        @plugin.command('hello')
        def hello_cmd(bot, trigger): ...
        
        @plugin.command('hi', 'hello', 'hey')
        def greet_cmd(bot, trigger): ...
    """

commands = command
    """
    Alias to command() decorator.
    
    This is an alias for the command() decorator, not a separate function.
    Use @plugin.command() instead.
    """

def action_command(command_name: str):
    """
    Register a function as an action command (CTCP ACTION).
    
    Args:
        command_name (str): The command name for ACTION messages
    """

def action_commands(*command_names: str):
    """
    Register a function for multiple action commands.
    
    Args:
        *command_names (str): Multiple action command names
    """

def nickname_command(command_name: str):
    """
    Register a function triggered when bot's nick is used as command.
    
    Args:
        command_name (str): The command name after bot's nickname
        
    Example:
        @plugin.nickname_command('hello')
        def nick_hello(bot, trigger): ...
        # Triggered by: "BotName: hello"
    """

def nickname_commands(*command_names: str):
    """
    Register a function for multiple nickname commands.
    
    Args:
        *command_names (str): Multiple nickname command names
    """

Rule-Based Pattern Matching

Register functions to respond to messages matching regular expression patterns.

def rule(pattern: str):
    """
    Register a function to trigger on regex pattern matches.
    
    Args:
        pattern (str): Regular expression pattern to match
        
    Example:
        @plugin.rule(r'.*\b(hello|hi)\b.*')
        def greet_rule(bot, trigger): ...
    """

def rule_lazy(lazy_loader):
    """
    Register a function with a lazy-loaded regex pattern.
    
    Args:
        lazy_loader (callable): Function returning list of regex patterns
    """

def find(pattern: str):
    """
    Register a function to find pattern anywhere in message.
    
    Args:
        pattern (str): Pattern to search for in messages
    """

def find_lazy(lazy_loader):
    """
    Register a function with lazy-loaded find patterns.
    
    Args:
        lazy_loader (callable): Function returning list of patterns
    """

def search(pattern: str):
    """
    Register a function for pattern search with match groups.
    
    Args:
        pattern (str): Search pattern with capture groups
    """

def search_lazy(lazy_loader):
    """
    Register a function with lazy-loaded search patterns.
    
    Args:
        lazy_loader (callable): Function returning list of search patterns
    """

Event Handling

Register functions to respond to specific IRC events and protocol messages.

def event(*event_types: str):
    """
    Register a function to handle specific IRC events.
    
    Args:
        *event_types (str): IRC event types to handle (JOIN, PART, QUIT, etc.)
        
    Example:
        @plugin.event('JOIN')
        def on_join(bot, trigger): ...
    """

def ctcp(ctcp_command: str):
    """
    Register a function to handle CTCP commands.
    
    Args:
        ctcp_command (str): CTCP command to handle (VERSION, PING, etc.)
        
    Example:
        @plugin.ctcp('VERSION')
        def ctcp_version(bot, trigger): ...
    """

URL Handling

Register functions to process URLs in messages with automatic URL detection and processing.

def url(url_pattern: str):
    """
    Register a function to handle URLs matching a pattern.
    
    Args:
        url_pattern (str): Regex pattern for URL matching
        
    Example:
        @plugin.url(r'https?://example\.com/.*')
        def handle_example_url(bot, trigger): ...
    """

def url_lazy(lazy_loader):
    """
    Register a function with lazy-loaded URL patterns.
    
    Args:
        lazy_loader (callable): Function returning list of URL patterns
    """

Scheduled Tasks

Register functions to run at regular intervals or specific times.

def interval(seconds: int):
    """
    Register a function to run at regular intervals.
    
    Args:
        seconds (int): Interval between executions in seconds
        
    Example:
        @plugin.interval(300)  # Every 5 minutes
        def periodic_task(bot): ...
    """

Access Control

Decorators to restrict plugin functions based on user privileges and message context.

def require_privmsg():
    """
    Restrict function to private messages only.
    
    Example:
        @plugin.require_privmsg()
        @plugin.command('secret')
        def secret_cmd(bot, trigger): ...
    """

def require_chanmsg():
    """
    Restrict function to channel messages only.
    """

def require_account():
    """
    Require user to be authenticated with services.
    """

def require_admin():
    """
    Require user to be a bot admin.
    """

def require_owner():
    """
    Require user to be a bot owner.
    """

def require_privilege(level: 'AccessLevel'):
    """
    Require specific channel privilege level.
    
    Args:
        level (AccessLevel): Minimum required privilege level
        
    Example:
        @plugin.require_privilege(plugin.OP)
        @plugin.command('kick')
        def kick_cmd(bot, trigger): ...
    """

def require_bot_privilege(level: 'AccessLevel'):
    """
    Require bot to have specific channel privileges.
    
    Args:
        level (AccessLevel): Required bot privilege level
    """

Rate Limiting

Control how frequently plugin functions can be triggered to prevent spam and abuse.

def rate(user: int = 0, channel: int = 0, server: int = 0, *, message: Optional[str] = None):
    """
    Apply rate limiting to plugin function.
    
    Args:
        user (int): Seconds between triggers per user (0 = no limit)
        channel (int): Seconds between triggers per channel (0 = no limit)  
        server (int): Seconds between any triggers (0 = no limit)
        message (str): Optional notice message when rate limit is reached
        
    Example:
        @plugin.rate(user=10, channel=30, message='Please wait!')
        @plugin.command('expensive')
        def expensive_cmd(bot, trigger): ...
    """

def rate_user(seconds: int):
    """
    Rate limit per user.
    
    Args:
        seconds (int): Seconds between triggers per user
    """

def rate_channel(seconds: int):
    """
    Rate limit per channel.
    
    Args:
        seconds (int): Seconds between triggers per channel
    """

def rate_global(seconds: int):
    """
    Global rate limit.
    
    Args:
        seconds (int): Seconds between any triggers
    """

Plugin Metadata and Behavior

Decorators to add metadata and control plugin function behavior.

def label(label_name: str):
    """
    Add a label to a plugin function for identification.
    
    Args:
        label_name (str): Label for the function
    """

class example:
    """
    Add usage example to plugin function documentation with testing support.
    
    Args:
        msg (str): Example command or message
        result (str or list, optional): Expected output for testing
        privmsg (bool, optional): If True, test as private message
        admin (bool, optional): If True, test as admin user
        owner (bool, optional): If True, test as owner user
        repeat (int, optional): Number of times to repeat test
        re (bool, optional): Use regex matching for result
        ignore (list, optional): Patterns to ignore in output
        user_help (bool, optional): Include in user help output
        online (bool, optional): Mark as online test
        vcr (bool, optional): Record HTTP requests for testing
        
    Example:
        @plugin.example('.weather London', 'Weather in London: 15°C')
        @plugin.command('weather')
        def weather_cmd(bot, trigger): ...
    """

def output_prefix(prefix: str):
    """
    Set output prefix for plugin function responses.
    
    Args:
        prefix (str): Prefix string for bot responses
    """

def priority(level: str):
    """
    Set execution priority for plugin function.
    
    Args:
        level (str): Priority level ('low', 'medium', 'high')
    """

def thread(threaded: bool = True):
    """
    Control whether function runs in separate thread.
    
    Args:
        threaded (bool): True to run in thread, False for main thread
    """

def echo():
    """
    Allow function to respond to bot's own messages.
    """

def unblockable():
    """
    Prevent function from being blocked by ignore lists.
    """

def allow_bots():
    """
    Allow function to respond to other bots.
    """

Capability Negotiation

Handle IRC capability negotiation for advanced protocol features.

def capability(*capabilities: str):
    """
    Register IRC capabilities that the plugin uses.
    
    Args:
        *capabilities (str): IRC capability names
        
    Example:
        @plugin.capability('account-tag')
        @plugin.command('whoami')
        def whoami_cmd(bot, trigger): ...
    """

Usage Examples

Basic Command Plugin

from sopel import plugin

@plugin.command('hello')
@plugin.example('.hello')
def hello_command(bot, trigger):
    """Greet a user."""
    bot.reply(f"Hello, {trigger.nick}!")

@plugin.command('roll')
@plugin.example('.roll 6')
@plugin.rate_user(5)  # 5 second cooldown per user
def roll_dice(bot, trigger):
    """Roll a die."""
    import random
    sides = 6
    if trigger.group(2):
        try:
            sides = int(trigger.group(2))
        except ValueError:
            bot.reply("Please provide a valid number.")
            return
    
    result = random.randint(1, sides)
    bot.reply(f"🎲 Rolled a {result} (1-{sides})")

Advanced Plugin with Configuration and Database

from sopel import plugin, config, db

class WeatherSection(config.types.StaticSection):
    api_key = config.types.ValidatedAttribute('api_key')
    default_location = config.types.ValidatedAttribute('default_location', default='London')

@plugin.command('weather')
@plugin.example('.weather London')
@plugin.rate_channel(30)  # 30 second channel cooldown
def weather_command(bot, trigger):
    """Get weather information."""
    location = trigger.group(2) or bot.settings.weather.default_location
    
    # Store user's last location query
    bot.db.set_nick_value(trigger.nick, 'last_weather_location', location)
    
    # Weather API call would go here
    bot.reply(f"Weather for {location}: Sunny, 25°C")

@plugin.rule(r'.*\b(hot|cold|weather)\b.*')
@plugin.require_chanmsg()
def weather_mention(bot, trigger):
    """Respond to weather mentions."""
    last_location = bot.db.get_nick_value(trigger.nick, 'last_weather_location')
    if last_location:
        bot.say(f"The weather in {last_location} is still nice!")

Event Handler Plugin

from sopel import plugin

@plugin.event('JOIN')
def welcome_user(bot, trigger):
    """Welcome new users to the channel."""
    if trigger.nick != bot.nick:  # Don't welcome ourselves
        bot.say(f"Welcome to {trigger.sender}, {trigger.nick}!")

@plugin.event('PART', 'QUIT')  
def goodbye_user(bot, trigger):
    """Say goodbye to leaving users."""
    if trigger.nick != bot.nick:
        bot.say(f"Goodbye, {trigger.nick}!")

Types

from typing import Optional

Plugin Function Parameters

# Standard plugin function signature
def plugin_function(bot: SopelWrapper, trigger: Trigger):
    """
    Standard plugin function.
    
    Args:
        bot (SopelWrapper): Bot interface for sending messages and accessing data
        trigger (Trigger): Context information about the triggering message
    """

# Interval function signature (no trigger)
def interval_function(bot: SopelWrapper):
    """
    Interval function for scheduled tasks.
    
    Args:
        bot (SopelWrapper): Bot interface
    """

Constants

# Rate limiting bypass constant
NOLIMIT: int = 1

# Access level constants (imported from sopel.privileges)
VOICE: AccessLevel
HALFOP: AccessLevel  
OP: AccessLevel
ADMIN: AccessLevel
OWNER: AccessLevel
OPER: AccessLevel

# Capability negotiation options
class CapabilityNegotiation(enum.Enum):
    NONE: str = "none"
    OPTIONAL: str = "optional" 
    REQUIRED: str = "required"

Install with Tessl CLI

npx tessl i tessl/pypi-sopel

docs

configuration.md

database.md

index.md

irc-protocol.md

plugin-development.md

utilities.md

tile.json