aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar mbaruh <[email protected]>2022-10-23 21:26:38 +0300
committerGravatar mbaruh <[email protected]>2022-10-23 21:26:38 +0300
commit1d628b533c68f87ede4129516bc7e239459e85f0 (patch)
treee2b4540c88d0ab9b326689f8e880b3c6b4a460be
parentMerge branch 'main' into new-filters (diff)
Add rich embed filter
-rw-r--r--bot/exts/filtering/_filter_context.py5
-rw-r--r--bot/exts/filtering/_filters/unique/rich_embed.py43
-rw-r--r--bot/exts/filtering/_filters/unique/webhook.py23
-rw-r--r--bot/exts/filtering/_settings.py10
4 files changed, 62 insertions, 19 deletions
diff --git a/bot/exts/filtering/_filter_context.py b/bot/exts/filtering/_filter_context.py
index da7ba0c77..bcbafe393 100644
--- a/bot/exts/filtering/_filter_context.py
+++ b/bot/exts/filtering/_filter_context.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from collections.abc import Coroutine
+from collections.abc import Callable, Coroutine
from dataclasses import dataclass, field, replace
from enum import Enum, auto
from typing import Optional, Union
@@ -35,7 +35,8 @@ class FilterContext:
action_descriptions: list = field(default_factory=list) # What actions were taken
matches: list = field(default_factory=list) # What exactly was found
notification_domain: str = field(default_factory=str) # A domain to send the user for context
- additional_actions: list[Coroutine] = field(default_factory=list) # Additional actions to perform
+ # Additional actions to perform
+ additional_actions: list[Callable[[FilterContext], Coroutine]] = field(default_factory=list)
def replace(self, **changes) -> FilterContext:
"""Return a new context object assigning new values to the specified fields."""
diff --git a/bot/exts/filtering/_filters/unique/rich_embed.py b/bot/exts/filtering/_filters/unique/rich_embed.py
new file mode 100644
index 000000000..75f578d3e
--- /dev/null
+++ b/bot/exts/filtering/_filters/unique/rich_embed.py
@@ -0,0 +1,43 @@
+import re
+
+from botcore.utils.logging import get_logger
+
+from bot.exts.filtering._filter_context import Event, FilterContext
+from bot.exts.filtering._filters.filter import UniqueFilter
+from bot.utils.helpers import remove_subdomain_from_url
+
+log = get_logger(__name__)
+
+URL_RE = re.compile(r"(https?://\S+)", flags=re.IGNORECASE)
+
+
+class RichEmbedFilter(UniqueFilter):
+ """Filter messages which contain rich embeds not auto-generated from a URL."""
+
+ name = "rich_embed"
+ events = (Event.MESSAGE, Event.MESSAGE_EDIT)
+
+ def triggered_on(self, ctx: FilterContext) -> bool:
+ """Determine if `msg` contains any rich embeds not auto-generated from a URL."""
+ if ctx.message.embeds:
+ for embed in ctx.message.embeds:
+ if embed.type == "rich":
+ urls = URL_RE.findall(ctx.content)
+ final_urls = set(urls)
+ # This is due to the way discord renders relative urls in Embeds
+ # if the following url is sent: https://mobile.twitter.com/something
+ # Discord renders it as https://twitter.com/something
+ for url in urls:
+ final_urls.add(remove_subdomain_from_url(url))
+ if not embed.url or embed.url not in final_urls:
+ # If `embed.url` does not exist or if `embed.url` is not part of the content
+ # of the message, it's unlikely to be an auto-generated embed by Discord.
+ ctx.alert_embeds.extend(ctx.message.embeds)
+ return True
+ else:
+ log.trace(
+ "Found a rich embed sent by a regular user account, "
+ "but it was likely just an automatic URL embed."
+ )
+
+ return False
diff --git a/bot/exts/filtering/_filters/unique/webhook.py b/bot/exts/filtering/_filters/unique/webhook.py
index 10f27a922..ee6b7135e 100644
--- a/bot/exts/filtering/_filters/unique/webhook.py
+++ b/bot/exts/filtering/_filters/unique/webhook.py
@@ -1,4 +1,5 @@
import re
+from collections.abc import Callable, Coroutine
from botcore.utils.logging import get_logger
@@ -41,18 +42,22 @@ class WebhookFilter(UniqueFilter):
for i, match in enumerate(matches, start=1):
extra = "" if len(matches) == 1 else f" ({i})"
# Queue the webhook for deletion.
- ctx.additional_actions.append(self._delete_webhook(ctx, match[0], extra))
+ ctx.additional_actions.append(self._delete_webhook_wrapper(match[0], extra))
# Don't show the full webhook in places such as the mod alert.
ctx.content = ctx.content.replace(match[0], match[1] + "xxx")
return True
@staticmethod
- async def _delete_webhook(ctx: FilterContext, webhook_url: str, extra_message: str) -> None:
- """Delete the given webhook and update the filter context."""
- async with bot.instance.http_session.delete(webhook_url) as resp:
- # The Discord API Returns a 204 NO CONTENT response on success.
- if resp.status == 204:
- ctx.action_descriptions.append("webhook deleted" + extra_message)
- else:
- ctx.action_descriptions.append("failed to delete webhook" + extra_message)
+ def _delete_webhook_wrapper(webhook_url: str, extra_message: str) -> Callable[[FilterContext], Coroutine]:
+ """Create the action to perform when a webhook should be deleted."""
+ async def _delete_webhook(ctx: FilterContext) -> None:
+ """Delete the given webhook and update the filter context."""
+ async with bot.instance.http_session.delete(webhook_url) as resp:
+ # The Discord API Returns a 204 NO CONTENT response on success.
+ if resp.status == 204:
+ ctx.action_descriptions.append("webhook deleted" + extra_message)
+ else:
+ ctx.action_descriptions.append("failed to delete webhook" + extra_message)
+
+ return _delete_webhook
diff --git a/bot/exts/filtering/_settings.py b/bot/exts/filtering/_settings.py
index d53334c1c..85a6f3d2b 100644
--- a/bot/exts/filtering/_settings.py
+++ b/bot/exts/filtering/_settings.py
@@ -197,14 +197,8 @@ class ActionSettings(Settings[ActionEntry]):
for entry in self.values():
await entry.action(ctx)
- _i = len(ctx.additional_actions)
- try:
- for _i, action in enumerate(ctx.additional_actions):
- await action
- except Exception:
- for action in ctx.additional_actions[_i+1:]:
- action.close()
- raise
+ for action in ctx.additional_actions:
+ await action(ctx)
def fallback_to(self, fallback: ActionSettings) -> ActionSettings:
"""Fill in missing entries from `fallback`."""