Callable decorators#

sopel.plugin#

This contains decorators and other tools for creating Sopel plugins.

sopel.plugin.NOLIMIT = 1#

Return value for callables, which suppresses rate limiting.

Returning this value means the triggering user will not be prevented from triggering the same callable again within the rate limit. This can be used, for example, to allow a user to retry a failed command immediately.

New in version 4.0.

sopel.plugin.action_command(*command_list: str) Callable#

Decorate a function to trigger on CTCP ACTION lines.

Parameters:

command_list – one or more command name(s) to match

This decorator can be used to add multiple commands to one callable in a single line. The resulting match object will have the command as the first group; the rest of the line, excluding leading whitespace, as the second group; and parameters 1 through 4, separated by whitespace, as groups 3-6.

Example:

@action_command("hello!")
    # Would trigger on "/me hello!"

New in version 7.0.

Note

The command name will be escaped for use in a regular expression. As such it is not possible to use something like /me command\d+ to catch something like /me command1 or /me command2.

You have several options at your disposal to replace a regex in the command name:

  • use a command alias

  • parse the arguments with your own regex within your plugin callable

  • use a rule()

The rule() must be used with the ctcp() decorator:

@rule(r'hello!?')
@ctcp('ACTION')
    # Would trigger on "/me hello!" and "/me hello"
sopel.plugin.action_commands(*command_list: str) Callable#

Alias to action_command().

sopel.plugin.allow_bots(function: Callable | None = None) Callable#

Decorate a function to specify that it should receive events from bots.

On networks implementing the Bot Mode specification, messages and other events from other clients that have identified themselves as bots will be tagged as such, and Sopel will ignore them by default. This decorator allows a function to opt into receiving these events.

sopel.plugin.command(*command_list: str) Callable#

Decorate a function to set one or more commands that should trigger it.

Parameters:

command_list – one or more command name(s) to match

This decorator can be used to add multiple commands to one callable in a single line. The resulting match object will have the command as the first group; the rest of the line, excluding leading whitespace, as the second group; and parameters 1 through 4, separated by whitespace, as groups 3-6.

Example:

@command("hello")
    # If the command prefix is "\.", this would trigger on lines
    # starting with ".hello".

@command('j', 'join')
    # If the command prefix is "\.", this would trigger on lines
    # starting with either ".j" or ".join".

You can use a space in the command name to implement subcommands:

@command('main sub1', 'main sub2')
    # For ".main sub1", trigger.group(1) will return "main sub1"
    # For ".main sub2", trigger.group(1) will return "main sub2"

But in that case, be careful with the order of the names: if a more generic pattern is defined first, it will have priority over less generic patterns. So for instance, to have .main and .main sub working properly, you need to declare them like this:

@command('main sub', 'main')
    # This command will react properly to ".main sub" and ".main"

Then, you can check trigger.group(1) to know if it was used as main sub or just main in your callable. If you declare them in the wrong order, .main will have priority and you won’t be able to take advantage of that.

Another option is to declare command with subcommands only, like this:

@command('main sub1')
    # this command will be triggered on .main sub1

@command('main sub2')
    # this other command will be triggered on .main sub2

In that case, .main won’t trigger anything, and you won’t have to inspect the trigger’s groups to know which subcommand is triggered.

Note

If you use this decorator multiple times, remember that the decorators are invoked in the reverse order of appearance:

# These two decorators...
@command('hi')
@command('hello')

# ...are equivalent to this single decorator
@command('hello', 'hi')

See also the Function Definitions chapter from the Python documentation for more information about functions and decorators.

Note

The command name will be escaped for use in a regular expression. As such it is not possible to use something like .command\d+ to catch something like .command1 or .command2.

You have several options at your disposal to replace a regex in the command name:

  • use a command alias

  • parse the arguments with your own regex within your plugin callable

  • use a rule() instead

sopel.plugin.commands(*command_list: str) Callable#

Alias to command().

sopel.plugin.ctcp(
function: Callable | str | None = None,
*command_list: str,
) Callable#

Decorate a callable to trigger on CTCP commands (mostly, ACTION).

Parameters:

command_list – one or more CTCP command(s) on which to trigger

There are various CTCP commands to handle with this decorator, such as ACTION, CLIENTINFO, TIME, and VERSION:

from sopel import plugin

@plugin.ctcp('TIME')
@plugin.rule('.*')
def ctcp_time(bot, trigger):
    bot.say('Sorry, not a clock.')

This decorator also works without parentheses, in which case it will trigger on CTCP ACTION:

from sopel import plugin

@plugin.ctcp
@plugin.rule('.*')
def ctcp_action(bot, trigger):
    bot.reply('Why would you do that?!')

New in version 7.1: This is now ctcp instead of intent, and it can be called without argument, in which case it will assume ACTION.

Note

This used to be @intent, for a long dead feature in the IRCv3 spec. It is now replaced by @ctcp, which can be used without arguments. In that case, Sopel will assume it should trigger on ACTION.

As sopel.module will be removed in Sopel 9, so will @intent.

sopel.plugin.echo(function: Callable | None = None) Callable#

Decorate a function to specify that it should receive echo messages.

This decorator can be used to listen in on the messages that Sopel is sending and react accordingly.

Important

The decorated callable will receive all matching messages that Sopel sends, including output from the same callable. Take care to avoid creating feedback loops when using this feature.

sopel.plugin.event(*event_list: str) Callable#

Decorate a function to be triggered on specific IRC events.

Parameters:

event_list – one or more event name(s) on which to trigger

This is one of a number of events, such as ‘JOIN’, ‘PART’, ‘QUIT’, etc. (More details can be found in RFC 1459.) When the Sopel bot is sent one of these events, the function will execute. Note that the default rule() (.*) will match any line of the correct event type(s). If any rule is explicitly specified, it overrides the default.

See also

sopel.tools.events provides human-readable names for many of the numeric events, which may help your code be clearer.

class sopel.plugin.example(
msg: str,
result: str | Iterable[str] | None = None,
privmsg: bool = False,
admin: bool = False,
owner: bool = False,
repeat: int = 1,
re: bool = False,
ignore: str | Iterable[str] | None = None,
user_help: bool = False,
online: bool = False,
vcr: bool = False,
)#

Decorate a function with an example, and optionally test output.

Parameters:
  • msg – the example command (required; see below)

  • result – the command’s expected output (optional; see below)

  • privmsg – if True, the example will be tested as if it was received in a private message to the bot; otherwise, in a channel (optional; default False)

  • admin – whether to treat the test message as having come from a bot admin (optional; default False)

  • owner – whether to treat the test message as having come from the bot’s owner (optional; default False)

  • repeat – how many times to repeat the test; useful for commands that return random results (optional; default 1)

  • re – if True, the result is interpreted as a regular expression and used to match the command’s output (optional; see below)

  • ignore – list of regular expression patterns to match ignored output (optional; see below)

  • user_help – whether this example should be included in user-facing help output such as .help command (optional; default False; see below)

  • online – if True, pytest will mark this example as “online” (optional; default False; see below)

  • vcr – if True, this example’s HTTP requests & responses will be recorded for later reuse (optional; default False; see below)

For compatibility with the built-in help plugin, msg must use the default help_prefix if it is a prefixed command. Other command types should give example invocations that work with Sopel’s default settings, especially if using the “example test” functionality to automatically generate a test(s) for the function.

The presence of a result will generate tests automatically when Sopel’s test suite is run, using msg as input. The exact behavior of the tests depends on the remaining optional example arguments.

Passing re=True, in particular, is useful for matching results that are random and/or dependent on an external API. This way, an example test can check the format of the result without caring about the exact data.

Giving a list of ignore``d patterns is helpful for commands that may return intermittent errors (mostly calls to an external API that isn't necessarily stable), especially when coupled with the ``repeat parameter.

By default, Sopel’s help plugin will display only one example (the one closest to the function’s def statement, due to how decorators work). You can override this choice or include multiple examples by passing user_help=True to one or more example decorator(s).

Passing online=True makes that particular example skippable if Sopel’s test suite is run in offline mode, which is mostly useful to make life easier for other developers working on Sopel without Internet access.

Finally, vcr=True records the example’s HTTP requests and responses for replaying during later test runs. It can be an alternative (or complement) to online, and is especially useful for testing plugin code that calls on inconsistent or flaky remote APIs. The recorded “cassettes” of responses can be committed alongside the code for use by CI services, etc. (See VCR.py & pytest-vcr)

sopel.plugin.find(*patterns: str | Pattern) Callable#

Decorate a function to be called for each time a pattern is found in a line.

Parameters:

patterns – one or more regular expression(s)

Each argument is a regular expression which will trigger the function:

@find('hello', 'here')
    # will trigger once on "hello you"
    # will trigger twice on "hello here"
    # will trigger once on "I'm right here!"

This decorator can be used multiple times to add more rules:

@find('here')
@find('hello')
    # will trigger once on "hello you"
    # will trigger twice on "hello here"
    # will trigger once on "I'm right here!"

If the Sopel instance is in a channel, or sent a PRIVMSG, the function will execute for each time a received message matches an expression. Each match will also contain the position of the instance it found.

Inside the regular expression, some special directives can be used. $nick will be replaced with the nick of the bot and , or :, and $nickname will be replaced with the nick of the bot:

@find('$nickname')
    # will trigger for each time the bot's nick is in a trigger

New in version 7.1.

Note

The regex rule will match once for each non-overlapping match, from left to right, and the function will execute for each of these matches.

To match only once from anywhere in the line, use the search() decorator instead. To match only once from the start of the line, use the rule() decorator instead.

sopel.plugin.find_lazy(*loaders: Callable) Callable#

Decorate a callable as a find rule with lazy loading.

Parameters:

loaders – one or more functions to generate a list of compiled regexes to match patterns in a line

Each loader function must accept a settings parameter and return a list (or tuple) of compiled regular expressions:

import re

def loader(settings):
    return [re.compile(r'<your_rule_pattern>')]

It will be called by Sopel when the bot parses the plugin to register the find rules to get its regexes. The settings argument will be the bot’s sopel.config.Config object.

If any of the loader functions raises a PluginError exception, the find rule will be ignored; it will not fail the plugin’s loading.

The decorated function will behave like any other callable():

from sopel import plugin

@plugin.find_lazy(loader)
def my_find_rule_handler(bot, trigger):
    bot.say('Rule triggered by: %s' % trigger.group(0))

New in version 7.1.

See also

When more than one loader is provided, they will be chained together with the sopel.tools.chain_loaders() function.

sopel.plugin.interval(*intervals: int | float) Callable#

Decorate a function to be called by the bot every n seconds.

Parameters:

intervals – one or more duration(s), in seconds

This decorator can be used multiple times for multiple intervals, or multiple intervals can be given in multiple arguments. The first time the function will be called is n seconds after the bot was started.

Plugin functions decorated by interval must only take bot as their argument; they do not get a trigger. The bot argument will not have a context, so functions like bot.say() will not have a default destination.

There is no guarantee that the bot is connected to a server or in any channels when the function is called, so care must be taken.

Example:

from sopel import plugin

@plugin.interval(5)
def spam_every_5s(bot):
    if "#here" in bot.channels:
        bot.say("It has been five seconds!", "#here")
sopel.plugin.label(value: str) Callable#

Decorate a function to add a rule label.

Parameters:

value – a label for the rule

The rule label allows the documentation and the logging system to refer to this function by its label. A function can have one and only one label:

@label('on_hello')
@rule('hello')
    # will trigger on hello, and be labelled as "on_hello"

Note

By default, the “label” of a callable will be its function name, which can be confusing for end-users: the goal of the label decorator is to make generic rules as user-friendly as commands are, by giving them some name that isn’t tied to an identifier in the source code.

sopel.plugin.nickname_command(*command_list: str) Callable#

Decorate a function to trigger on lines starting with “$nickname: command”.

Parameters:

command_list – one or more command name(s) to match

This decorator can be used to add multiple commands to one callable in a single line. The resulting match object will have the command as the first group; the rest of the line, excluding leading whitespace, as the second group; and parameters 1 through 4, separated by whitespace, as groups 3-6.

Example:

@nickname_command("hello!")
    # Would trigger on "$nickname: hello!", "$nickname,   hello!",
    # "$nickname hello!", "$nickname hello! parameter1" and
    # "$nickname hello! p1 p2 p3 p4 p5 p6 p7 p8 p9".

Note

The command name will be escaped to be used in a regex command. As such it is not possible to use something like command\d+ to catch something like Bot: command1 or Bot: command2.

You have several options at your disposal to replace a regex in the command name:

  • use a command alias

  • parse the arguments with your own regex within your plugin callable

  • use a rule()

The rule() can be used with a $nick variable:

@rule(r'$nick .*')
    # Would trigger on anything starting with "$nickname[:,]? ",
    # and would never have any additional parameters, as the
    # command would match the rest of the line.
sopel.plugin.nickname_commands(*command_list: str) Callable#

Alias to nickname_command().

sopel.plugin.output_prefix(prefix: str) Callable#

Decorate a function to add a prefix on its output.

Parameters:

prefix – the prefix to add (must include trailing whitespace if desired; Sopel does not assume it should add anything)

Prefix will be added to text sent through:

sopel.plugin.priority(value: Literal['low', 'medium', 'high']) Callable#

Decorate a function to be executed with higher or lower priority.

Parameters:

value – one of high, medium, or low

The priority allows you some control over the order of callable execution, if your plugin needs it. If a callable does not specify its priority, Sopel assumes medium.

sopel.plugin.rate(
user: int = 0,
channel: int = 0,
server: int = 0,
*,
message: str | None = None,
) Callable#

Decorate a function to be rate-limited.

Parameters:
  • user – seconds between permitted calls of this function by the same user

  • channel – seconds between permitted calls of this function in the same channel, regardless of triggering user

  • server – seconds between permitted calls of this function no matter who triggered it or where

  • message – optional keyword argument; default message sent as NOTICE when a rate limit is reached

How often a function can be triggered on a per-user basis, in a channel, or across the server (bot) can be controlled with this decorator. A value of 0 means no limit. If a function is given a rate of 20, that function may only be used once every 20 seconds in the scope corresponding to the parameter:

from sopel import plugin

@plugin.rate(10)
    # won't trigger if used more than once per 10s by a user

@plugin.rate(10, 10)
    # won't trigger if used more than once per 10s by a user/channel

@plugin.rate(10, 10, 2)
    # won't trigger if used more than once per 10s by a user/channel
    # and never more than once every 2s

If a message is provided, it will be used as the default message sent as a NOTICE to the user who hit the rate limit:

@rate(10, 10, 10, message='Hit the rate limit for this function.')
    # will send a NOTICE

The message can contain placeholders which will be filled in:

  • nick: the nick that hit the rate limit

  • channel: the channel in which the rate limit was hit (will be 'private-message' for private messages)

  • sender: the sender (nick or channel) of the message which hit the rate limit

  • plugin: the name of the plugin that hit the rate limit

  • label: the label of the plugin handler that hit the rate limit

  • time_left: the time remaining before the rate limit expires, as a string

  • time_left_sec: the time remaining before the rate limit expires, expressed in number of seconds

  • rate_limit: the rate limit, as a string

  • rate_limit_sec: the rate limit, expressed in number of seconds

  • rate_limit_type: the type of rate limit that was hit (one of user, group, global)

For example:

@rate(10, 10, 2, message='Sorry {nick}, you hit the {rate_limit_type} rate limit!')

Rate-limited functions that use scheduled future commands should import threading.Timer instead of sched, or rate limiting will not work properly.

Changed in version 8.0: Optional keyword argument message was added in Sopel 8.

Note

Users on the admin list in Sopel’s configuration are exempted from rate limits.

See also

You can control each rate limit separately, with their own custom message using rate_user(), rate_channel(), or rate_global().

sopel.plugin.rate_channel(rate: int, message: str | None = None) Callable#

Decorate a function to be rate-limited for a channel.

Parameters:
  • rate – seconds between permitted calls of this function in the same channel, regardless of triggering user

  • message – optional; message sent as NOTICE when a user hits the limit

This decorator can be used alone or with the rate() decorator, as it will always take precedence:

@rate(10, 10, 10)
@rate_channel(5, 'You hit the channel rate limit for this function.')
    # channel limit will be set to 5, other to 10
    # will send a NOTICE only when a user hits the channel limit
    # as other rate limits don't have any message set

If you don’t provide a message, the default message set by rate() (if any) will be used instead.

The message can contain the same placeholders supported by rate():

@rate_channel(
    5,
    'Sorry {nick}, you hit the {rate_limit_sec}s limit for the {channel} channel!',
)

New in version 8.0.

Note

Users on the admin list in Sopel’s configuration are exempted from rate limits.

sopel.plugin.rate_global(rate: int, message: str | None = None) Callable#

Decorate a function to be rate-limited for the whole server.

Parameters:
  • rate – seconds between permitted calls of this function no matter who triggered it or where

  • message – optional; message sent as NOTICE when a user hits the limit

This decorator can be used alone or with the rate() decorator, as it will always take precedence.

For example:

@rate(10, 10, 10)
@rate_global(5, 'You hit the global rate limit for this function.')
    # global limit will be set to 5, other to 10
    # will send a NOTICE only when a user hits the global limit
    # as other rate limits don't have any message set

If you don’t provide a message, the default message set by rate() (if any) will be used instead.

The message can contain the same placeholders supported by rate():

@rate_global(5, 'Sorry {nick}, you hit the 5s limit!')

New in version 8.0.

Note

Users on the admin list in Sopel’s configuration are exempted from rate limits.

sopel.plugin.rate_user(rate: int, message: str | None = None) Callable#

Decorate a function to be rate-limited for a user.

Parameters:
  • rate – seconds between permitted calls of this function by the same user

  • message – optional; message sent as NOTICE when a user hits the limit

This decorator can be used alone or with the rate() decorator, as it will always take precedence:

@rate(10, 10, 10)
@rate_user(20, 'You hit your rate limit for this function.')
    # user limit will be set to 20, other to 10
    # will send a NOTICE only when a user hits their own limit
    # as other rate limits don't have any message set

The message can contain the same placeholders supported by rate():

@rate_user(5, 'Sorry {nick}, you hit your {rate_limit_sec}s limit!')

If you don’t provide a message, the default message set by rate() (if any) will be used instead.

New in version 8.0.

Note

Users on the admin list in Sopel’s configuration are exempted from rate limits.

sopel.plugin.require_account(
message: Callable | str | None = None,
reply: bool = False,
) Callable#

Decorate a function to require services/NickServ authentication.

Parameters:
  • message – optional message to say if a user without authentication tries to trigger this function

  • reply – use reply() instead of say() when True; defaults to False

A decorated plugin callable will be triggered only if the triggering user is logged into a network services account:

from sopel import plugin

@plugin.command('.regonly')
@plugin.require_account('Registered users only.')
def logged_in_command(bot, trigger):
    # trigger only if user is logged in to services

If an unauthenticated user triggers the decorated function, the message will be said if given. By default, it uses bot.say(), but when reply is True it uses bot.reply() instead.

This decorator also works without parentheses, if you want its default (no arguments) behavior:

from sopel import plugin

@plugin.command('.regonly')
@plugin.require_account
def logged_in_command(bot, trigger):
    # trigger only if user is logged in to services

New in version 7.0.

Note

Only some networks support services authentication, and not all of those implement the standards required for clients like Sopel to determine authentication status. This decorator will block all use of functions it decorates on networks that lack the relevant features.

See also

The value of the trigger’s account property determines whether this requirement is satisfied, and the property’s documentation includes up-to-date details on what features a network must support to allow Sopel to fetch account information.

sopel.plugin.require_admin(
message: Callable | str | None = None,
reply: bool = False,
) Callable#

Decorate a function to require the triggering user to be a bot admin.

Parameters:
  • message – optional message said to non-admin user

  • reply – use reply() instead of say() when True; defaults to False

A decorated plugin callable will be triggered only if the triggering user is an admin of the bot, according to its configuration:

from sopel import plugin

@plugin.command('.adminonly')
@plugin.require_admin('Bot admin only command.')
def admin_command(bot, trigger):
    # trigger only if user is a bot admin

The bot’s owner is also an admin.

When the triggering user is not an admin, the command is not run, and the bot will say the message if given. By default, it uses bot.say(), but when reply is True it uses bot.reply() instead.

This decorator also works without parentheses, if you want its default (no arguments) behavior:

from sopel import plugin

@plugin.command('.adminonly')
@plugin.require_admin
def admin_command(bot, trigger):
    # trigger only if user is a bot admin

Changed in version 7.0: Added the reply parameter.

sopel.plugin.require_bot_privilege(
level: AccessLevel,
message: str | None = None,
reply: bool = False,
) Callable#

Decorate a function to require a minimum channel privilege for the bot.

Parameters:
  • level – minimum channel privilege the bot needs for this function

  • message – optional message said if the bot’s channel privilege level is insufficient

  • reply – use reply() instead of say() when True; defaults to False

level can be one of the privilege level constants defined in this module. If the bot does not have the privilege, the bot will say message if given. By default, it uses bot.say(), but when reply is True it uses bot.reply() instead.

Use of require_bot_privilege() implies require_chanmsg().

New in version 7.1.

Changed in version 8.0: Decorated callables no longer run in response to private messages.

sopel.plugin.require_chanmsg(
message: Callable | str | None = None,
reply: bool = False,
) Callable#

Decorate a function to only be triggerable from a channel message.

Parameters:
  • message – optional message said if triggered in private message

  • reply – use reply() instead of say() when True; defaults to False

A decorated plugin callable will be triggered only by messages from a channel:

from sopel import plugin

@plugin.command('.mtopic')
@plugin.require_chanmsg('Channel only command.')
def manage_topic(bot, trigger):
    # trigger on channel messages only

If the decorated function is triggered by a private message, the message will be said if given. By default, it uses bot.say(), but when reply is True it uses bot.reply() instead.

This decorator also works without parentheses, if you want its default (no arguments) behavior:

from sopel import plugin

@plugin.command('.mtopic')
@plugin.require_chanmsg
def manage_topic(bot, trigger):
    # trigger on channel messages only

Changed in version 7.0: Added the reply parameter.

sopel.plugin.require_owner(
message: Callable | str | None = None,
reply: bool = False,
) Callable#

Decorate a function to require the triggering user to be the bot owner.

Parameters:
  • message – optional message said to non-owner user

  • reply – use reply() instead of say() when True; defaults to False

A decorated plugin callable will be triggered only if the triggering user is recognized as the bot’s owner, according to its configuration:

from sopel import plugin

@plugin.command('.owneronly')
@plugin.require_owner('Bot owner only command.')
def owner_command(bot, trigger):
    # trigger only if user is the bot's owner

When the triggering user is not the bot’s owner, the command is not run, and the bot will say message if given. By default, it uses bot.say(), but when reply is True it uses bot.reply() instead.

This decorator also works without parentheses, if you want its default (no arguments) behavior:

from sopel import plugin

@plugin.command('.owneronly')
@plugin.require_owner
def owner_command(bot, trigger):
    # trigger only if user is the bot's owner

Changed in version 7.0: Added the reply parameter.

sopel.plugin.require_privilege(
level: AccessLevel,
message: str | None = None,
reply: bool = False,
) Callable#

Decorate a function to require at least the given channel permission.

Parameters:
  • level – required privilege level to use this command

  • message – optional message said to insufficiently privileged user

  • reply – use reply() instead of say() when True; defaults to False

level can be one of the privilege level constants defined in this module. If the user does not have at least that privilege, the bot will say message if given. By default, it uses bot.say(), but when reply is True it uses bot.reply() instead.

Use of require_privilege() implies require_chanmsg().

Changed in version 7.0: Added the reply parameter.

Changed in version 8.0: Decorated callables no longer run in response to private messages.

sopel.plugin.require_privmsg(
message: Callable | str | None = None,
reply: bool = False,
) Callable#

Decorate a function to only be triggerable from a private message.

Parameters:
  • message – optional message said if triggered in a channel

  • reply – use reply() instead of say() when True; defaults to False

A decorated plugin callable will be triggered only by messages sent to the bot in private:

from sopel import plugin

@plugin.command('.shh')
@plugin.require_privmsg('PM only command.')
def confidential_command(bot, trigger):
    # trigger on private messages only

If the decorated function is triggered by a channel message, the message will be said if given. By default, it uses bot.say(), but when reply is True, then it uses bot.reply() instead.

This decorator also works without parentheses, if you want its default (no arguments) behavior:

from sopel import plugin

@plugin.command('.shh')
@plugin.require_privmsg
def confidential_command(bot, trigger):
    # trigger on private messages only

Changed in version 7.0: Added the reply parameter.

sopel.plugin.rule(*patterns: str | Pattern) Callable#

Decorate a function to be called when a line matches the given pattern.

Parameters:

patterns – one or more regular expression(s)

Each argument is a regular expression which will trigger the function:

@rule('hello', 'how')
    # will trigger once on "how are you?"
    # will trigger once on "hello, what's up?"

This decorator can be used multiple times to add more rules:

@rule('how')
@rule('hello')
    # will trigger once on "how are you?"
    # will trigger once on "hello, what's up?"

If the Sopel instance is in a channel, or sent a PRIVMSG, where a string matching this expression is said, the function will execute. Note that captured groups here will be retrievable through the Trigger object later.

Inside the regular expression, some special directives can be used. $nick will be replaced with the nick of the bot and , or :, and $nickname will be replaced with the nick of the bot.

Changed in version 7.0: The rule() decorator can be called with multiple positional arguments, each used to add a rule. This is equivalent to decorating the same function multiple times with this decorator.

Note

The regex rule will match only once per line, starting at the beginning of the line only.

To match for each time an expression is found, use the find() decorator instead. To match only once from anywhere in the line, use the search() decorator instead.

sopel.plugin.rule_lazy(*loaders: Callable) Callable#

Decorate a callable as a rule with lazy loading.

Parameters:

loaders – one or more functions to generate a list of compiled regexes to match URLs

Each loader function must accept a settings parameter and return a list (or tuple) of compiled regular expressions:

import re

def loader(settings):
    return [re.compile(r'<your_rule_pattern>')]

It will be called by Sopel when the bot parses the plugin to register rules to get its regexes. The settings argument will be the bot’s sopel.config.Config object.

If any of the loader functions raises a PluginError exception, the rule will be ignored; it will not fail the plugin’s loading.

The decorated function will behave like any other callable():

from sopel import plugin

@plugin.rule_lazy(loader)
def my_rule_handler(bot, trigger):
    bot.say('Rule triggered by: %s' % trigger.group(0))

New in version 7.1.

See also

When more than one loader is provided, they will be chained together with the sopel.tools.chain_loaders() function.

sopel.plugin.search(*patterns: str | Pattern) Callable#

Decorate a function to be called when a pattern matches anywhere in a line.

Parameters:

patterns – one or more regular expression(s)

Each argument is a regular expression which will trigger the function:

@search('hello', 'here')
    # will trigger once on "hello you"
    # will trigger twice on "hello here"
    # will trigger once on "I'm right here!"

This decorator can be used multiple times to add more search rules:

@search('here')
@search('hello')
    # will trigger once on "hello you"
    # will trigger twice on "hello here" (once per expression)
    # will trigger once on "I'm right here!"

If the Sopel instance is in a channel, or sent a PRIVMSG, where a part of a string matching this expression is said, the function will execute. Note that captured groups here will be retrievable through the Trigger object later. The match will also contain the position of the first instance found.

Inside the regular expression, some special directives can be used. $nick will be replaced with the nick of the bot and , or :, and $nickname will be replaced with the nick of the bot:

@search('$nickname')
    # will trigger once when the bot's nick is in a trigger

New in version 7.1.

Note

The regex rule will match for the first instance only, starting from the left of the line, and the function will execute only once per regular expression.

To match for each time an expression is found, use the find() decorator instead. To match only once from the start of the line, use the rule() decorator instead.

sopel.plugin.search_lazy(*loaders: Callable) Callable#

Decorate a callable as a search rule with lazy loading.

Parameters:

loaders – one or more functions to generate a list of compiled regexes to match patterns in a line

Each loader function must accept a settings parameter and return a list (or tuple) of compiled regular expressions:

import re

def loader(settings):
    return [re.compile(r'<your_rule_pattern>')]

It will be called by Sopel when the bot parses the plugin to register the search rules to get its regexes. The settings argument will be the bot’s sopel.config.Config object.

If any of the loader functions raises a PluginError exception, the find rule will be ignored; it will not fail the plugin’s loading.

The decorated function will behave like any other callable():

from sopel import plugin

@plugin.search_lazy(loader)
def my_search_rule_handler(bot, trigger):
    bot.say('Rule triggered by: %s' % trigger.group(0))

New in version 7.1.

See also

When more than one loader is provided, they will be chained together with the sopel.tools.chain_loaders() function.

sopel.plugin.thread(value: bool) Callable#

Decorate a function to specify if it should be run in a separate thread.

Parameters:

value – if True, the function is called in a separate thread; otherwise, from the bot’s main thread

Functions run in a separate thread (as is the default) will not prevent the bot from executing other functions at the same time. Functions not run in a separate thread may be started while other functions are still running, but additional functions will not start until it is completed.

sopel.plugin.unblockable(function: Callable | None = None) Callable#

Decorate a function to exempt it from Sopel’s ignore system.

For example, this can be used to ensure that important events such as JOIN are always recorded even if the user’s nickname or hostname is ignored:

from sopel import plugin

@plugin.event('JOIN')
@plugin.unblockable
def on_join_callable(bot, trigger):
    # do something when a user JOINs a channel
    # a blocked nickname or hostname *will* trigger this
    pass

See also

Sopel’s dispatch() method.

sopel.plugin.url(*url_rules: str) Callable#

Decorate a function to handle URLs.

Parameters:

url_rules – one or more regex pattern(s) to match URLs

This decorator takes a regex string that will be matched against URLs in a message. The function it decorates is like any other callable:

from sopel import plugin

@plugin.url(r'https://example.com/bugs/([a-z0-9]+)')
@plugin.url(r'https://short.com/([a-z0-9]+)')
def handle_example_bugs(bot, trigger):
    bot.reply('Found bug ID #%s' % trigger.group(1))

The bot is an instance of SopelWrapper, and trigger is the usual Trigger object.

Under the hood, when Sopel collects the decorated handler it uses an instance of sopel.plugins.rules.URLCallback to register it to its rules manager and its register_url_callback() method.

Changed in version 7.0: The same function can be decorated multiple times with url() to register different URL patterns.

Changed in version 7.0: More than one pattern can be provided as positional argument at once.

Changed in version 7.1: The match parameter is obsolete and can be omitted. When present however, it represents the same match as the trigger argument.

This behavior will be kept for backward compatibility and will be removed in Sopel 9.

See also

To detect URLs, Sopel uses a matching pattern built from a list of URL schemes, configured by auto_url_schemes.

sopel.plugin.url_lazy(*loaders: Callable) Callable#

Decorate a function to handle URL, using lazy-loading for its regex.

Parameters:

loaders – one or more functions to generate a list of compiled regexes to match URLs.

Each loader function must accept a settings parameter and return a list (or tuple) of compiled regular expressions:

import re

def loader(settings):
    return [re.compile(r'<your_url_pattern>')]

It will be called by Sopel when the bot parses the plugin to register URL callbacks to get its regexes. The settings argument will be the bot’s sopel.config.Config object.

If any of the loader functions raises a PluginError exception, the URL callback will be ignored; it will not fail the plugin’s loading.

The decorated function will behave like any other callable():

from sopel import plugin

@plugin.url_lazy(loader)
def my_url_handler(bot, trigger):
    bot.say('URL found: %s' % trigger.group(0))

New in version 7.1.

See also

When more than one loader is provided, they will be chained together with the sopel.tools.chain_loaders() function.

About sopel.module#

Before Sopel 7.1, sopel.module was the preferred and only way to decorate callables for plugins. However, since the term module can be confusing (mostly because it already has a special meaning in Python), it has been replaced by plugin in most cases related to add-ons for Sopel.

The sopel.module sub-module is replaced by sopel.plugin.

Deprecated since version 7.1: Use sopel.plugin instead. This will be removed in Sopel 9.

sopel.module.intent(*intent_list)#

Decorate a callable to trigger on intent messages.

Parameters:

intent_list (str) – one or more intent(s) on which to trigger (really, the only useful value is ACTION)

New in version 5.2.0.

Deprecated since version 7.1.

Important

This will be removed in Sopel 9, as the IRCv3 intent specification is long dead. You can use @ctcp instead.