From 2a72adf61dd81c4cc66fa2871b19552644b0d7c3 Mon Sep 17 00:00:00 2001 From: dgw Date: Mon, 3 Oct 2022 19:52:43 -0500 Subject: [PATCH 1/2] privileges: set __all__ to make exported names explicit Happens to be the most convenient way I could think of to expose a list of supported privilege constants programmatically. Using `dir(sopel.privileges)` (or `var()`) and filtering for non-dunder names still left `annotations` as part of the list. --- sopel/privileges.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sopel/privileges.py b/sopel/privileges.py index 1970d79c88..553db307a3 100644 --- a/sopel/privileges.py +++ b/sopel/privileges.py @@ -56,6 +56,16 @@ from __future__ import annotations +__all__ = [ + 'VOICE', + 'HALFOP', + 'OP', + 'ADMIN', + 'OWNER', + 'OPER', +] + + VOICE = 1 """Privilege level for the +v channel permission From 98c0afa1327116c10760c000de5dbb2fc2cfcb1b Mon Sep 17 00:00:00 2001 From: dgw Date: Mon, 3 Oct 2022 20:12:53 -0500 Subject: [PATCH 2/2] url: configurable channel access requirement for managing excludes Defaults to `sopel.privileges.OP`; auto-populates the `ChoiceAttribute` using exported names from the `privileges` submodule. Also allows bot admins, as is traditional. If we encounter a spacebar-heating* situation, the mechanism here can be revisited to allow explicitly setting no access requirement. * - https://xkcd.com/1172/ --- sopel/modules/url.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/sopel/modules/url.py b/sopel/modules/url.py index 86414c989f..47087c2248 100644 --- a/sopel/modules/url.py +++ b/sopel/modules/url.py @@ -20,7 +20,7 @@ import requests from urllib3.exceptions import LocationValueError # type: ignore[import] -from sopel import plugin, tools +from sopel import plugin, privileges, tools from sopel.config import types from sopel.tools import web @@ -59,6 +59,12 @@ class UrlSection(types.StaticSection): # TODO some validation rules maybe? exclude = types.ListAttribute('exclude') """A list of regular expressions to match URLs for which the title should not be shown.""" + exclude_required_access = types.ChoiceAttribute( + 'exclude_required_access', + choices=privileges.__all__, + default='OP', + ) + """Minimum channel access level required to edit ``exclude`` list using chat commands.""" exclusion_char = types.ValidatedAttribute('exclusion_char', default='!') """A character (or string) which, when immediately preceding a URL, will stop that URL's title from being shown.""" shorten_url_length = types.ValidatedAttribute( @@ -146,6 +152,20 @@ def shutdown(bot: Sopel): pass +def _user_can_change_excludes(bot: SopelWrapper, trigger: Trigger): + if trigger.admin: + return True + + required_access = bot.config.url.exclude_required_access + channel = bot.channels[trigger.sender] + user_access = channel.privileges[trigger.nick] + + if user_access >= getattr(privileges, required_access): + return True + + return False + + @plugin.command('urlexclude', 'urlpexclude', 'urlban', 'urlpban') @plugin.example('.urlpexclude example\\.com/\\w+', user_help=True) @plugin.example('.urlexclude example.com/path', user_help=True) @@ -161,6 +181,12 @@ def url_ban(bot: SopelWrapper, trigger: Trigger): bot.reply('This command requires a URL to exclude.') return + if not _user_can_change_excludes(bot, trigger): + bot.reply( + 'Only admins and channel members with %s access or higher may ' + 'modify URL excludes.' % bot.config.url.exclude_required_access) + return + if trigger.group(1) in ['urlpexclude', 'urlpban']: # validate regex pattern try: @@ -206,6 +232,12 @@ def url_unban(bot: SopelWrapper, trigger: Trigger): bot.reply('This command requires a URL to allow.') return + if not _user_can_change_excludes(bot, trigger): + bot.reply( + 'Only admins and channel members with %s access or higher may ' + 'modify URL excludes.' % bot.config.url.exclude_required_access) + return + if trigger.group(1) in ['urlpallow', 'urlpunban']: # validate regex pattern try: