aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/exts/filtering/_filter_lists/domain.py4
-rw-r--r--bot/exts/filtering/_filter_lists/filter_list.py119
-rw-r--r--bot/exts/filtering/_filter_lists/invite.py4
-rw-r--r--bot/exts/filtering/_filter_lists/token.py4
-rw-r--r--bot/exts/filtering/_ui/filter.py6
-rw-r--r--bot/exts/filtering/_ui/filter_list.py2
-rw-r--r--bot/exts/filtering/filtering.py4
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}")