diff options
| -rw-r--r-- | bot/exts/filtering/_filter_lists/domain.py | 4 | ||||
| -rw-r--r-- | bot/exts/filtering/_filter_lists/filter_list.py | 119 | ||||
| -rw-r--r-- | bot/exts/filtering/_filter_lists/invite.py | 4 | ||||
| -rw-r--r-- | bot/exts/filtering/_filter_lists/token.py | 4 | ||||
| -rw-r--r-- | bot/exts/filtering/_ui/filter.py | 6 | ||||
| -rw-r--r-- | bot/exts/filtering/_ui/filter_list.py | 2 | ||||
| -rw-r--r-- | bot/exts/filtering/filtering.py | 4 | 
7 files changed, 54 insertions, 89 deletions
| diff --git a/bot/exts/filtering/_filter_lists/domain.py b/bot/exts/filtering/_filter_lists/domain.py index 34ab5670c..17984e276 100644 --- a/bot/exts/filtering/_filter_lists/domain.py +++ b/bot/exts/filtering/_filter_lists/domain.py @@ -54,9 +54,7 @@ class DomainsList(FilterList):          urls = {match.group(1).lower().rstrip("/") for match in URL_RE.finditer(text)}          new_ctx = ctx.replace(content=urls) -        triggers = self.filter_list_result( -            new_ctx, self[ListType.DENY].filters, self[ListType.DENY].defaults.validations -        ) +        triggers = self[ListType.DENY].filter_list_result(new_ctx)          ctx.notification_domain = new_ctx.notification_domain          actions = None          messages = [] diff --git a/bot/exts/filtering/_filter_lists/filter_list.py b/bot/exts/filtering/_filter_lists/filter_list.py index daab45b81..9eb907fc1 100644 --- a/bot/exts/filtering/_filter_lists/filter_list.py +++ b/bot/exts/filtering/_filter_lists/filter_list.py @@ -1,7 +1,6 @@  from abc import abstractmethod -from collections.abc import Iterator  from enum import Enum -from typing import Any, ItemsView, NamedTuple +from typing import Any, NamedTuple  from discord.ext.commands import BadArgument @@ -62,35 +61,53 @@ class AtomicList(NamedTuple):          """Provide a short description identifying the list with its name and type."""          return f"{past_tense(self.list_type.name.lower())} {self.name.lower()}" +    def filter_list_result(self, ctx: FilterContext) -> list[Filter]: +        """ +        Sift through the list of filters, and return only the ones which apply to the given context. -class FilterList(FieldRequiring): -    """Dispatches events to lists of _filters, and aggregates the responses into a single list of actions to take.""" - -    # Each subclass must define a name matching the filter_list name we're expecting to receive from the database. -    # Names must be unique across all filter lists. -    name = FieldRequiring.MUST_SET_UNIQUE +        The strategy is as follows: +        1. The default settings are evaluated on the given context. The default answer for whether the filter is +        relevant in the given context is whether there aren't any validation settings which returned False. +        2. For each filter, its overrides are considered: +            - If there are no overrides, then the filter is relevant if that is the default answer. +            - Otherwise it is relevant if there are no failed overrides, and any failing default is overridden by a +            successful override. -    def __init__(self): -        self._filter_lists: dict[ListType, AtomicList] = {} +        If the filter is relevant in context, see if it actually triggers. +        """ +        passed_by_default, failed_by_default = self.defaults.validations.evaluate(ctx) +        default_answer = not bool(failed_by_default) -    def __iter__(self) -> Iterator[ListType]: -        return iter(self._filter_lists) +        relevant_filters = [] +        for filter_ in self.filters.values(): +            if not filter_.validations: +                if default_answer and filter_.triggered_on(ctx): +                    relevant_filters.append(filter_) +            else: +                passed, failed = filter_.validations.evaluate(ctx) +                if not failed and failed_by_default < passed: +                    if filter_.triggered_on(ctx): +                        relevant_filters.append(filter_) -    def __getitem__(self, list_type: ListType) -> AtomicList: -        return self._filter_lists[list_type] +        return relevant_filters -    def __contains__(self, list_type: ListType) -> bool: -        return list_type in self._filter_lists +    def default(self, setting: str) -> Any: +        """Get the default value of a specific setting.""" +        missing = object() +        value = self.defaults.actions.get_setting(setting, missing) +        if value is missing: +            value = self.defaults.validations.get_setting(setting, missing) +            if value is missing: +                raise ValueError(f"Couldn't find a setting named {setting!r}.") +        return value -    def __bool__(self) -> bool: -        return bool(self._filter_lists) -    def __len__(self) -> int: -        return len(self._filter_lists) +class FilterList(FieldRequiring, dict[ListType, AtomicList]): +    """Dispatches events to lists of _filters, and aggregates the responses into a single list of actions to take.""" -    def items(self) -> ItemsView[ListType, AtomicList]: -        """Return an iterator for the lists' types and values.""" -        return self._filter_lists.items() +    # Each subclass must define a name matching the filter_list name we're expecting to receive from the database. +    # Names must be unique across all filter lists. +    name = FieldRequiring.MUST_SET_UNIQUE      def add_list(self, list_data: dict) -> AtomicList:          """Add a new type of list (such as a whitelist or a blacklist) this filter list.""" @@ -102,24 +119,8 @@ class FilterList(FieldRequiring):          for filter_data in list_data["filters"]:              filters[filter_data["id"]] = self._create_filter(filter_data) -        self._filter_lists[list_type] = AtomicList(list_data["id"], self.name, list_type, defaults, filters) -        return self._filter_lists[list_type] - -    def remove_list(self, list_type: ListType) -> None: -        """Remove the list associated with the given type from the FilterList object.""" -        if list_type not in self._filter_lists: -            return -        self._filter_lists.pop(list_type) - -    def default(self, list_type: ListType, setting: str) -> Any: -        """Get the default value of a specific setting.""" -        missing = object() -        value = self._filter_lists[list_type].defaults.actions.get_setting(setting, missing) -        if value is missing: -            value = self._filter_lists[list_type].defaults.validations.get_setting(setting, missing) -            if value is missing: -                raise ValueError(f"Could find a setting named {setting}.") -        return value +        self[list_type] = AtomicList(list_data["id"], self.name, list_type, defaults, filters) +        return self[list_type]      def add_filter(self, list_type: ListType, filter_data: dict) -> Filter:          """Add a filter to the list of the specified type.""" @@ -140,39 +141,6 @@ class FilterList(FieldRequiring):      async def actions_for(self, ctx: FilterContext) -> tuple[ActionSettings | None, list[str]]:          """Dispatch the given event to the list's filters, and return actions to take and messages to relay to mods.""" -    @staticmethod -    def filter_list_result( -        ctx: FilterContext, filters: dict[int, Filter], defaults: ValidationSettings -    ) -> list[Filter]: -        """ -        Sift through the list of filters, and return only the ones which apply to the given context. - -        The strategy is as follows: -        1. The default settings are evaluated on the given context. The default answer for whether the filter is -        relevant in the given context is whether there aren't any validation settings which returned False. -        2. For each filter, its overrides are considered: -            - If there are no overrides, then the filter is relevant if that is the default answer. -            - Otherwise it is relevant if there are no failed overrides, and any failing default is overridden by a -            successful override. - -        If the filter is relevant in context, see if it actually triggers. -        """ -        passed_by_default, failed_by_default = defaults.evaluate(ctx) -        default_answer = not bool(failed_by_default) - -        relevant_filters = [] -        for filter_ in filters.values(): -            if not filter_.validations: -                if default_answer and filter_.triggered_on(ctx): -                    relevant_filters.append(filter_) -            else: -                passed, failed = filter_.validations.evaluate(ctx) -                if not failed and failed_by_default < passed: -                    if filter_.triggered_on(ctx): -                        relevant_filters.append(filter_) - -        return relevant_filters -      def _create_filter(self, filter_data: dict) -> Filter:          """Create a filter from the given data."""          try: @@ -182,3 +150,6 @@ class FilterList(FieldRequiring):              log.warning(e)          else:              return new_filter + +    def __hash__(self): +        return hash(id(self)) diff --git a/bot/exts/filtering/_filter_lists/invite.py b/bot/exts/filtering/_filter_lists/invite.py index 5bb4549ae..d35fdd4a4 100644 --- a/bot/exts/filtering/_filter_lists/invite.py +++ b/bot/exts/filtering/_filter_lists/invite.py @@ -97,9 +97,7 @@ class InviteList(FilterList):          # Add the allowed by default only if they're blacklisted.          guilds_for_inspection = {invite.guild.id for invite in allowed_by_default.values()}          new_ctx = ctx.replace(content=guilds_for_inspection) -        triggered = self.filter_list_result( -            new_ctx, self[ListType.ALLOW].filters, self[ListType.DENY].defaults.validations -        ) +        triggered = self[ListType.ALLOW].filter_list_result(new_ctx)          disallowed_invites.update({              invite_code: invite for invite_code, invite in allowed_by_default.items()              if invite.guild.id in {filter_.content for filter_ in triggered} diff --git a/bot/exts/filtering/_filter_lists/token.py b/bot/exts/filtering/_filter_lists/token.py index c80ccfd68..4b161d9b7 100644 --- a/bot/exts/filtering/_filter_lists/token.py +++ b/bot/exts/filtering/_filter_lists/token.py @@ -55,9 +55,7 @@ class TokensList(FilterList):          text = clean_input(text)          ctx = ctx.replace(content=text) -        triggers = self.filter_list_result( -            ctx, self[ListType.DENY].filters, self[ListType.DENY].defaults.validations -        ) +        triggers = self[ListType.DENY].filter_list_result(ctx)          actions = None          messages = []          if triggers: diff --git a/bot/exts/filtering/_ui/filter.py b/bot/exts/filtering/_ui/filter.py index e6330329d..38eef3ca6 100644 --- a/bot/exts/filtering/_ui/filter.py +++ b/bot/exts/filtering/_ui/filter.py @@ -278,7 +278,7 @@ class FilterEditView(EditBaseView):                  default_value = self.filter_type.extra_fields_type().dict()[setting_name]              else:                  dict_to_edit = self.settings_overrides -                default_value = self.filter_list.default(self.list_type, setting_name) +                default_value = self.filter_list[self.list_type].default(setting_name)              # Update the setting override value or remove it              if setting_value is not self._REMOVE:                  if not repr_equals(setting_value, default_value): @@ -405,7 +405,7 @@ def description_and_settings_converter(              type_ = loaded_settings[setting][2]              try:                  parsed_value = parse_value(settings.pop(setting), type_) -                if not repr_equals(parsed_value, filter_list.default(list_type, setting)): +                if not repr_equals(parsed_value, filter_list[list_type].default(setting)):                      settings[setting] = parsed_value              except (TypeError, ValueError) as e:                  raise BadArgument(e) @@ -431,7 +431,7 @@ def filter_overrides(filter_: Filter, filter_list: FilterList, list_type: ListTy          if settings:              for _, setting in settings.items():                  for setting_name, value in to_serializable(setting.dict()).items(): -                    if not repr_equals(value, filter_list.default(list_type, setting_name)): +                    if not repr_equals(value, filter_list[list_type].default(setting_name)):                          overrides_values[setting_name] = value      if filter_.extra_fields_type: diff --git a/bot/exts/filtering/_ui/filter_list.py b/bot/exts/filtering/_ui/filter_list.py index b072e293e..15d81322b 100644 --- a/bot/exts/filtering/_ui/filter_list.py +++ b/bot/exts/filtering/_ui/filter_list.py @@ -234,7 +234,7 @@ class FilterListEditView(EditBaseView):          if not setting_name:  # Obligatory check to match the signature in the parent class.              return -        default_value = self.filter_list.default(self.list_type, setting_name) +        default_value = self.filter_list[self.list_type].default(setting_name)          if not repr_equals(setting_value, default_value):              self.settings[setting_name] = setting_value          # If there's already a new value, remove it, since the new value is the same as the default. diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index c47ba653f..563bdacb5 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -646,7 +646,7 @@ class Filtering(Cog):              file = discord.File(BytesIO(json.dumps(list_data, indent=4).encode("utf-8")), f"{list_description}.json")              message = await ctx.send("⏳ Annihilation in progress, please hold...", file=file)              # Unload the filter list. -            filter_list.remove_list(list_type) +            filter_list.pop(list_type)              if not filter_list:  # There's nothing left, remove from the cog.                  self.filter_lists.pop(filter_list.name)                  self.unsubscribe(filter_list) @@ -950,7 +950,7 @@ class Filtering(Cog):          response = await bot.instance.api_client.patch(              f'bot/filter/filter_lists/{list_id}', json=to_serializable(settings)          ) -        filter_list.remove_list(list_type) +        filter_list.pop(list_type, None)          filter_list.add_list(response)          await msg.reply(f"✅ Edited filter list: {filter_list[list_type].label}") | 
