Skip to content

Commit

Permalink
Merge pull request #2540 from sopel-irc/privileges-enum
Browse files Browse the repository at this point in the history
privileges, plugin, tools.target, docs: channel privileges as IntFlag
  • Loading branch information
dgw committed Nov 10, 2023
2 parents 89fec76 + 813651c commit 51300a1
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 133 deletions.
62 changes: 44 additions & 18 deletions docs/source/plugin/privileges.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,32 @@ IRC users can have privileges in a **channel**, given by MODE messages such as:
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 way.
channel, which can be used by plugins in various ways.

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

* :attr:`~sopel.privileges.AccessLevel.OP`: channel operator, set and unset by
modes ``+o`` and ``-o``
* :attr:`~sopel.privileges.AccessLevel.VOICE`: the privilege to send messages to
a channel with the ``+m`` mode, set and unset by modes ``+v`` and ``-v``

.. _nonstandard privilege levels:

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

* :attr:`~sopel.privileges.AccessLevel.HALFOP`: intermediate level between VOICE
and OP, set and unset by modes ``+h`` and ``-h``
* :attr:`~sopel.privileges.AccessLevel.ADMIN`: channel admin, above OP and below
OWNER, set and unset by modes ``+a`` and ``-a``
* :attr:`~sopel.privileges.AccessLevel.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
=============
Expand All @@ -22,9 +47,10 @@ Privileged users
A plugin can limit who can trigger its callables using the
:func:`~sopel.plugin.require_privilege` decorator::

from sopel import plugin, privileges
from sopel import plugin
from sopel.privileges import AccessLevel

@plugin.require_privilege(privileges.OP)
@plugin.require_privilege(AccessLevel.OP)
@plugin.require_chanmsg
@plugin.command('chanopcommand')
def chanop_command(bot, trigger):
Expand All @@ -34,7 +60,7 @@ 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(privileges.OP, 'You need +o privileges.')
@plugin.require_privilege(AccessLevel.OP, 'You need +o privileges.')

.. important::

Expand All @@ -50,7 +76,7 @@ Sometimes, you may want the bot to be a privileged user in a channel to allow a
command. For that, there is the :func:`~sopel.plugin.require_bot_privilege`
decorator::

@plugin.require_bot_privilege(privileges.OP)
@plugin.require_bot_privilege(AccessLevel.OP)
@plugin.require_chanmsg
@plugin.command('opbotcommand')
def change_topic(bot, trigger):
Expand All @@ -63,13 +89,13 @@ the user who invokes the command.
As with ``require_privilege``, you can provide an error message::

@plugin.require_bot_privilege(
privileges.OP, 'The bot needs +o privileges.')
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(privileges.VOICE)
@plugin.require_bot_privilege(privileges.OP)
@plugin.require_privilege(AccessLevel.VOICE)
@plugin.require_bot_privilege(AccessLevel.OP)
@plugin.require_chanmsg
@plugin.command('special')
def special_command(bot, trigger):
Expand All @@ -92,7 +118,7 @@ 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 :func:`~sopel.plugin.require_account` decorator::

@plugin.require_privilege(privileges.VOICE)
@plugin.require_privilege(AccessLevel.VOICE)
@plugin.require_account
@plugin.require_chanmsg
@plugin.command('danger')
Expand Down Expand Up @@ -130,25 +156,25 @@ then you can get that user's privileges through the **channel**'s
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::
for example, we check if the user is voiced (``+v``) or above::

from sopel import privileges
from sopel.privileges import AccessLevel

if user_privileges & privileges.VOICE:
if user_privileges & AccessLevel.VOICE:
# user is voiced
elif user_privileges > privileges.VOICE:
elif user_privileges > AccessLevel.VOICE:
# not voiced, but higher privileges
# like privileges.HALFOP or privileges.OP
# 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', privileges.VOICE):
elif channel.has_privilege('Nickname', AccessLevel.VOICE):
# not voiced, but higher privileges
# like privileges.HALFOP or privileges.OP
# like AccessLevel.HALFOP or AccessLevel.OP
else:
# no privilege

Expand All @@ -158,14 +184,14 @@ You can also iterate over the list of users and filter them by privileges::
op_users = [
user
for nick, user in channel.users
if channel.is_op(nick, privileges.OP)
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, privileges.OP)
if channel.has_privileges(nick, AccessLevel.OP)
]

.. seealso::
Expand Down
13 changes: 10 additions & 3 deletions sopel/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@
)

# import and expose privileges as shortcut
from sopel.privileges import ADMIN, HALFOP, OP, OPER, OWNER, VOICE
from sopel.privileges import AccessLevel

VOICE = AccessLevel.VOICE
HALFOP = AccessLevel.HALFOP
OP = AccessLevel.OP
ADMIN = AccessLevel.ADMIN
OWNER = AccessLevel.OWNER
OPER = AccessLevel.OPER


if TYPE_CHECKING:
Expand Down Expand Up @@ -1525,7 +1532,7 @@ def guarded(bot, trigger, *args, **kwargs):


def require_privilege(
level: int,
level: AccessLevel,
message: Optional[str] = None,
reply: bool = False,
) -> Callable:
Expand Down Expand Up @@ -1688,7 +1695,7 @@ def guarded(bot, trigger, *args, **kwargs):


def require_bot_privilege(
level: int,
level: AccessLevel,
message: Optional[str] = None,
reply: bool = False,
) -> Callable:
Expand Down

0 comments on commit 51300a1

Please sign in to comment.