User privileges#

IRC users can have privileges in a channel, given by MODE messages such as:

MODE #example +ov Nickname Nickname

This will give both OP and Voice privileges to the user named “Nickname” in the “#example” channel (and only in this channel). When Sopel receives a MODE message it registers and updates its knowledge of a user’s privileges in a channel, which can be used by plugins in various ways.

Historically, these two privilege levels (“op” and “voiced”) were the only channel privileges available:

  • OP: channel operator, set and unset by modes +o and -o

  • VOICE: the privilege to send messages to a channel with the +m mode, set and unset by modes +v and -v

Over time, IRC servers and clients have adopted various combinations of nonstandard, less formally defined privilege levels:

  • HALFOP: intermediate level between VOICE and OP, set and unset by modes +h and -h

  • ADMIN: channel admin, above OP and below OWNER, set and unset by modes +a and -a

  • OWNER: channel owner, above ADMIN and OP, set and unset by modes +q and -q

It’s important to note that not all IRC networks support these nonstandard privileges, and the ones that do may not support all of them. If you are writing a plugin for public distribution, ensure your code behaves sensibly when only the standardized +v (voice) and +o (op) modes exist.

Access rights#

Privileged users#

A plugin can limit who can trigger its callables using the require_privilege() decorator:

from sopel import plugin
from sopel.privileges import AccessLevel

@plugin.require_privilege(AccessLevel.OP)
@plugin.require_chanmsg
@plugin.command('chanopcommand')
def chanop_command(bot, trigger):
    # only a channel operator can use this command

This way, only users with OP privileges or above in a channel can use the command chanopcommand in that channel: other users will be ignored by the bot. It is possible to tell these users why with the message parameter:

@plugin.require_privilege(AccessLevel.OP, 'You need +o privileges.')

Important

A command that requires channel privileges will always execute if called from a private message to the bot. You can use the sopel.plugin.require_chanmsg() decorator to ignore the command if it’s called in PMs.

The bot is a user too#

Sometimes, you may want the bot to be a privileged user in a channel to allow a command. For that, there is the require_bot_privilege() decorator:

@plugin.require_bot_privilege(AccessLevel.OP)
@plugin.require_chanmsg
@plugin.command('opbotcommand')
def change_topic(bot, trigger):
    # only if the bot has OP privileges

This way, this command cannot be used if the bot doesn’t have the right privileges in the channel where it is used, independent from the privileges of the user who invokes the command.

As with require_privilege, you can provide an error message:

@plugin.require_bot_privilege(
    AccessLevel.OP, 'The bot needs +o privileges.')

And you can use both require_privilege and require_bot_privilege on the same plugin callable:

@plugin.require_privilege(AccessLevel.VOICE)
@plugin.require_bot_privilege(AccessLevel.OP)
@plugin.require_chanmsg
@plugin.command('special')
def special_command(bot, trigger):
    # only if the user has +v and the bot has +o (or above)

This way, you can allow a less privileged user to access a command for a more privileged bot (this works for any combination of privileges).

Important

A command that requires channel privileges will always execute if called from a private message to the bot. You can use the sopel.plugin.require_chanmsg() decorator to ignore the command if it’s called in PMs.

Restrict to user account#

Sometimes, a command should be used only by users who are authenticated via IRC services. On IRC networks that provide such information to IRC clients, this is possible with the require_account() decorator:

@plugin.require_privilege(AccessLevel.VOICE)
@plugin.require_account
@plugin.require_chanmsg
@plugin.command('danger')
def dangerous_command(bot, trigger):
    # only if the user has +v and has a registered account

This has two consequences:

  1. this command cannot be used by users who are not authenticated

  2. this command cannot be used on an IRC network that doesn’t allow authentication or doesn’t expose that information

It makes your plugin safer to use and prevents the possibility to use it on insecure IRC networks.

Getting user privileges in a channel#

Within a plugin callable, you can get access to a user’s privileges in a channel to check privileges manually. For example, you could adapt the level of information your callable provides based on said privileges.

First you need a user’s nick and a channel (e.g. from the trigger parameter), then you can get that user’s privileges through the channel’s privileges attribute:

user_privileges = channel.privileges['Nickname']
user_privileges = channel.privileges[trigger.nick]

You can check the user’s privileges manually using bitwise operators. Here for example, we check if the user is voiced (+v) or above:

from sopel.privileges import AccessLevel

if user_privileges & AccessLevel.VOICE:
    # user is voiced
elif user_privileges > AccessLevel.VOICE:
    # not voiced, but higher privileges
    # like AccessLevel.HALFOP or AccessLevel.OP
else:
    # no privilege

Another option is to use dedicated methods from the channel object:

if channel.is_voiced('Nickname'):
    # user is voiced
elif channel.has_privilege('Nickname', AccessLevel.VOICE):
    # not voiced, but higher privileges
    # like AccessLevel.HALFOP or AccessLevel.OP
else:
    # no privilege

You can also iterate over the list of users and filter them by privileges:

# get users with the OP privilege
op_users = [
    user
    for nick, user in channel.users
    if channel.is_op(nick, AccessLevel.OP)
]

# get users with OP privilege or above
op_or_higher_users = [
    user
    for nick, user in channel.users
    if channel.has_privileges(nick, AccessLevel.OP)
]

See also

Read about the Channel and User classes for more details.

sopel.privileges#

Constants for user privileges in channels.

class sopel.privileges.AccessLevel(value)#

Enumeration of available user privilege levels.

This class represents privileges as comparable, combinable flags. Lower privilege levels compare as less than (<) higher ones:

>>> from sopel.privileges import AccessLevel
>>> AccessLevel.VOICE < AccessLevel.HALFOP < AccessLevel.OP \
... < AccessLevel.ADMIN < AccessLevel.OWNER
True

A user’s privileges are represented as a combination of privilege levels:

>>> priv = AccessLevel.VOICE | AccessLevel.OP

This allows using comparators and bitwise operators to compare privileges. Here, priv contains both VOICE and OP privileges, but not HALFOP:

>>> priv >= AccessLevel.OP
True
>>> bool(priv & AccessLevel.HALFOP)
False

Important

Do not hard-code the value of a privilege level in your code; the values may change. Always reference or compare to the appropriate member of this class directly.

VOICE = 1#

Privilege level for the +v channel permission

New in version 4.1.

Changed in version 8.0: Constant moved from sopel.plugin to sopel.privileges.AccessLevel.

HALFOP = 2#

Privilege level for the +h channel permission

New in version 4.1.

Changed in version 8.0: Constant moved from sopel.plugin to sopel.privileges.AccessLevel.

Important

Beware: This is one of the nonstandard privilege levels.

OP = 4#

Privilege level for the +o channel permission

New in version 4.1.

Changed in version 8.0: Constant moved from sopel.plugin to sopel.privileges.AccessLevel.

ADMIN = 8#

Privilege level for the +a channel permission

New in version 4.1.

Changed in version 8.0: Constant moved from sopel.plugin to sopel.privileges.AccessLevel.

Important

Beware: This is one of the nonstandard privilege levels.

OWNER = 16#

Privilege level for the +q channel permission

New in version 4.1.

Changed in version 8.0: Constant moved from sopel.plugin to sopel.privileges.AccessLevel.

Important

Beware: This is one of the nonstandard privilege levels.

OPER = 32#

Privilege level for the +y/+Y channel permission

Note: Except for these (non-standard) channel modes, Sopel does not monitor or store any user’s OPER status.

New in version 7.0.

Changed in version 8.0: Constant moved from sopel.plugin to sopel.privileges.AccessLevel.

Important

Beware: This is one of the nonstandard privilege levels.