diff --git a/docs/toolset.rst b/docs/toolset.rst index 06b36bf..c7808d2 100644 --- a/docs/toolset.rst +++ b/docs/toolset.rst @@ -164,6 +164,9 @@ and the following methods: `: checks the page like :py:meth:`~earwigbot.wiki.copyvios.CopyvioMixIn.copyvio_check`, but against a specific URL +- :py:meth:`check_exclusion(username=None, optouts=None) + `: checks whether or not we are + allowed to edit the page per ``{{bots}}``/``{{nobots}}`` Additionally, :py:class:`~earwigbot.wiki.category.Category` objects (created with :py:meth:`site.get_category(name) ` diff --git a/earwigbot/wiki/page.py b/earwigbot/wiki/page.py index 58efe50..3e8c29a 100644 --- a/earwigbot/wiki/page.py +++ b/earwigbot/wiki/page.py @@ -68,6 +68,8 @@ class Page(CopyrightMixIn): - :py:meth:`parse`: parses the page content for templates, links, etc - :py:meth:`edit`: replaces the page's content or creates a new page - :py:meth:`add_section`: adds a new section at the bottom of the page + - :py:meth:`check_exclusion`: checks whether or not we are allowed to edit + the page, per ``{{bots}}``/``{{nobots}}`` - :py:meth:`~earwigbot.wiki.copyvios.CopyrightMixIn.copyvio_check`: checks the page for copyright violations @@ -723,3 +725,55 @@ class Page(CopyrightMixIn): """ self._edit(text=text, summary=title, minor=minor, bot=bot, force=force, section="new") + + def check_exclusion(self, username=None, optouts=None): + """Check whether or not we are allowed to edit the page. + + Return ``True`` if we *are* allowed to edit this page, and ``False`` if + we aren't. + + *username* is used to determine whether we are part of a specific list + of allowed or disallowed bots (e.g. ``{{bots|allow=EarwigBot}}`` or + ``{{bots|deny=FooBot,EarwigBot}}``). It's ``None`` by default, which + will swipe our username from :py:meth:`site.get_user() + `.\ + :py:attr:`~earwigbot.wiki.user.User.name`. + + *optouts* is a list of messages to consider this check as part of for + the purpose of opt-out; it defaults to ``None``, which ignores the + parameter completely. For example, if *optouts* is ``["nolicense"]``, + we'll return ``False`` on ``{{bots|optout=nolicense}}`` or + ``{{bots|optout=all}}``, but `True` on + ``{{bots|optout=orfud,norationale,replaceable}}``. + """ + def parse_param(template, param): + value = template.get_param(param).value + return [item.strip().lower() for item in value.split(",")] + + if not username: + username = self.site.get_user().name + + # Lowercase everything: + username = username.lower() + optouts = [optout.lower() for optout in optouts] if optouts else [] + + re_bots = "\{\{\s*(no)?bots\s*(\||\}\})" + filter = self.parse().filter_templates(matches=re_bots, recursive=True) + for template in filter: + if template.has_param("deny"): + denies = parse_param(template, "deny") + if "all" in denies or username in denies: + return False + if template.has_param("allow"): + allows = parse_param(template, "allow") + if "all" in allows or username in allows: + continue + if optouts and template.has_param("optout"): + tasks = parse_param(template, "optout") + matches = [optout in tasks for optout in optouts] + if "all" in tasks or any(matches): + return False + if template.name.strip().lower() == "nobots": + return False + + return True