From bdca39b257ac3be2bff9d8244a49fa63978d3ff7 Mon Sep 17 00:00:00 2001 From: mAxYoLo01 Date: Sat, 26 Aug 2023 22:35:05 +0200 Subject: [PATCH 1/4] fix: non-breaking auto mod fix --- interactions/api/events/discord.py | 2 + interactions/models/discord/auto_mod.py | 98 ++++++++++++++++--------- 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/interactions/api/events/discord.py b/interactions/api/events/discord.py index 35fe5c783..c621fd01f 100644 --- a/interactions/api/events/discord.py +++ b/interactions/api/events/discord.py @@ -132,6 +132,8 @@ class AutoModExec(BaseEvent): @attrs.define(eq=False, order=False, hash=False, kw_only=False) class AutoModCreated(BaseEvent): + """Dispatched when an auto mod rule is created""" + guild: "Guild" = attrs.field(repr=False, metadata=docs("The guild the rule was modified in")) rule: "AutoModRule" = attrs.field(repr=False, metadata=docs("The rule that was modified")) diff --git a/interactions/models/discord/auto_mod.py b/interactions/models/discord/auto_mod.py index c9be2d142..8d075b125 100644 --- a/interactions/models/discord/auto_mod.py +++ b/interactions/models/discord/auto_mod.py @@ -1,22 +1,30 @@ -from typing import Any, Optional, TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Optional, Union import attrs -from interactions.client.const import get_logger, MISSING, Absent +from interactions.client.const import MISSING, Absent, get_logger from interactions.client.mixins.serialization import DictSerializationMixin from interactions.client.utils import list_converter, optional from interactions.client.utils.attr_utils import docs from interactions.models.discord.base import ClientObject, DiscordObject from interactions.models.discord.enums import ( - AutoModTriggerType, AutoModAction, AutoModEvent, AutoModLanuguageType, + AutoModTriggerType, ) -from interactions.models.discord.snowflake import to_snowflake_list, to_snowflake +from interactions.models.discord.snowflake import to_snowflake, to_snowflake_list if TYPE_CHECKING: - from interactions import Snowflake_Type, Guild, GuildText, Message, Client, Member, User + from interactions import ( + Client, + Guild, + GuildText, + Member, + Message, + Snowflake_Type, + User, + ) __all__ = ("AutoModerationAction", "AutoModRule") @@ -71,7 +79,7 @@ def _process_dict(cls, data: dict[str, Any]) -> dict[str, Any]: return data @classmethod - def from_dict_factory(cls, data: dict) -> "BaseAction": + def from_dict_factory(cls, data: dict) -> "TYPE_ALL_TRIGGER": trigger_class = TRIGGER_MAPPING.get(data.get("trigger_type")) meta = data.get("trigger_metadata", {}) if not trigger_class: @@ -97,16 +105,22 @@ def _keyword_converter(filter: str | list[str]) -> list[str]: class KeywordTrigger(BaseTrigger): """A trigger that checks if content contains words from a user defined list of keywords""" - type: AutoModTriggerType = attrs.field( - default=AutoModTriggerType.KEYWORD, - converter=AutoModTriggerType, + keyword_filter: list[str] = attrs.field( + factory=list, repr=True, - metadata=docs("The type of trigger"), + metadata=docs("Substrings which will be searched for in content"), + converter=_keyword_converter, ) - keyword_filter: str | list[str] = attrs.field( + regex_patterns: list[str] = attrs.field( + factory=list, + repr=True, + metadata=docs("Regular expression patterns which will be matched against content"), + converter=_keyword_converter, + ) + allow_list: list[str] = attrs.field( factory=list, repr=True, - metadata=docs("What words will trigger this"), + metadata=docs("Substrings which should not trigger the rule"), converter=_keyword_converter, ) @@ -127,47 +141,60 @@ class HarmfulLinkFilter(BaseTrigger): class KeywordPresetTrigger(BaseTrigger): """A trigger that checks if content contains words from internal pre-defined wordsets""" - type: AutoModTriggerType = attrs.field( - default=AutoModTriggerType.KEYWORD_PRESET, - converter=AutoModTriggerType, - repr=True, - metadata=docs("The type of trigger"), - ) keyword_lists: list[AutoModLanuguageType] = attrs.field( factory=list, converter=list_converter(AutoModLanuguageType), repr=True, - metadata=docs("The preset list of keywords that will trigger this"), + metadata=docs("The internally pre-defined wordsets which will be searched for in content"), ) @attrs.define(eq=False, order=False, hash=False, kw_only=True) class MentionSpamTrigger(BaseTrigger): - """A trigger that checks if content contains more mentions than allowed""" + """A trigger that checks if content contains more unique mentions than allowed""" mention_total_limit: int = attrs.field( default=3, repr=True, metadata=docs("The maximum number of mentions allowed") ) + mention_raid_protection_enabled: bool = attrs.field( + repr=True, metadata=docs("Whether to automatically detect mention raids") + ) @attrs.define(eq=False, order=False, hash=False, kw_only=True) class MemberProfileTrigger(BaseTrigger): + """A trigger that checks if member profile contains words from a user defined list of keywords""" + regex_patterns: list[str] = attrs.field( - factory=list, repr=True, metadata=docs("The regex patterns to check against") + factory=list, + repr=True, + metadata=docs("Regular expression patterns which will be matched against content"), + converter=_keyword_converter, ) - keyword_filter: str | list[str] = attrs.field( - factory=list, repr=True, metadata=docs("The keywords to check against") + keyword_filter: list[str] = attrs.field( + factory=list, + repr=True, + metadata=docs("Substrings which will be searched for in content"), + converter=_keyword_converter, ) - allow_list: list["Snowflake_Type"] = attrs.field( - factory=list, repr=True, metadata=docs("The roles exempt from this rule") + allow_list: list[str] = attrs.field( + factory=list, + repr=True, + metadata=docs("Substrings which should not trigger the rule"), + converter=_keyword_converter, ) +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class SpamTrigger(BaseTrigger): + """A trigger that checks if content represents generic spam""" + + @attrs.define(eq=False, order=False, hash=False, kw_only=True) class BlockMessage(BaseAction): - """blocks the content of a message according to the rule""" + """Blocks the content of a message according to the rule""" - type: AutoModAction = attrs.field(repr=False, default=AutoModAction.BLOCK_MESSAGE, converter=AutoModAction) + custom_message: Optional[str] = attrs.field(repr=True, default=None) @attrs.define(eq=False, order=False, hash=False, kw_only=True) @@ -175,7 +202,6 @@ class AlertMessage(BaseAction): """logs user content to a specified channel""" channel_id: "Snowflake_Type" = attrs.field(repr=True) - type: AutoModAction = attrs.field(repr=False, default=AutoModAction.ALERT_MESSAGE, converter=AutoModAction) @attrs.define(eq=False, order=False, hash=False, kw_only=False) @@ -183,7 +209,6 @@ class TimeoutUser(BaseAction): """timeout user for a specified duration""" duration_seconds: int = attrs.field(repr=True, default=60) - type: AutoModAction = attrs.field(repr=False, default=AutoModAction.TIMEOUT_USER, converter=AutoModAction) @attrs.define(eq=False, order=False, hash=False, kw_only=False) @@ -204,13 +229,13 @@ class AutoModRule(DiscordObject): enabled: bool = attrs.field(repr=False, default=False) """whether the rule is enabled""" - actions: list[BaseAction] = attrs.field(repr=False, factory=list) + actions: list["TYPE_ALL_ACTION"] = attrs.field(repr=False, factory=list) """the actions which will execute when the rule is triggered""" event_type: AutoModEvent = attrs.field( repr=False, ) """the rule event type""" - trigger: BaseTrigger = attrs.field( + trigger: "TYPE_ALL_TRIGGER" = attrs.field( repr=False, ) """The trigger for this rule""" @@ -262,10 +287,10 @@ async def modify( self, *, name: Absent[str] = MISSING, - trigger: Absent[BaseTrigger] = MISSING, + trigger: Absent["TYPE_ALL_TRIGGER"] = MISSING, trigger_type: Absent[AutoModTriggerType] = MISSING, trigger_metadata: Absent[dict] = MISSING, - actions: Absent[list[BaseAction]] = MISSING, + actions: Absent[list["TYPE_ALL_ACTION"]] = MISSING, exempt_channels: Absent[list["Snowflake_Type"]] = MISSING, exempt_roles: Absent[list["Snowflake_Type"]] = MISSING, event_type: Absent[AutoModEvent] = MISSING, @@ -318,7 +343,7 @@ class AutoModerationAction(ClientObject): repr=False, ) - action: BaseAction = attrs.field(default=MISSING, repr=True) + action: "TYPE_ALL_ACTION" = attrs.field(default=MISSING, repr=True) matched_keyword: str = attrs.field(repr=True) matched_content: Optional[str] = attrs.field(repr=False, default=None) @@ -369,7 +394,12 @@ def member(self) -> "Optional[Member]": TRIGGER_MAPPING = { AutoModTriggerType.KEYWORD: KeywordTrigger, AutoModTriggerType.HARMFUL_LINK: HarmfulLinkFilter, + AutoModTriggerType.SPAM: SpamTrigger, AutoModTriggerType.KEYWORD_PRESET: KeywordPresetTrigger, AutoModTriggerType.MENTION_SPAM: MentionSpamTrigger, AutoModTriggerType.MEMBER_PROFILE: MemberProfileTrigger, } + +TYPE_ALL_TRIGGER = Union[KeywordTrigger, SpamTrigger, KeywordPresetTrigger, MentionSpamTrigger, MemberProfileTrigger] + +TYPE_ALL_ACTION = Union[BlockMessage, AlertMessage, TimeoutUser, BlockMemberInteraction] From 3c75a3987a23a1c9f7932cad3502e7c673b83fc2 Mon Sep 17 00:00:00 2001 From: mAxYoLo01 Date: Sun, 27 Aug 2023 10:23:01 +0200 Subject: [PATCH 2/4] fix: re-add update/delete auto mod fixes --- interactions/api/events/processors/auto_mod.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/interactions/api/events/processors/auto_mod.py b/interactions/api/events/processors/auto_mod.py index 31f08476f..dfee26c2d 100644 --- a/interactions/api/events/processors/auto_mod.py +++ b/interactions/api/events/processors/auto_mod.py @@ -1,8 +1,9 @@ from typing import TYPE_CHECKING from interactions.models.discord.auto_mod import AutoModerationAction, AutoModRule -from ._template import EventMixinTemplate, Processor + from ... import events +from ._template import EventMixinTemplate, Processor if TYPE_CHECKING: from interactions.api.events import RawGatewayEvent @@ -25,13 +26,13 @@ async def raw_auto_moderation_rule_create(self, event: "RawGatewayEvent") -> Non self.dispatch(events.AutoModCreated(guild, rule)) @Processor.define() - async def raw_auto_moderation_rule_delete(self, event: "RawGatewayEvent") -> None: + async def raw_auto_moderation_rule_update(self, event: "RawGatewayEvent") -> None: rule = AutoModRule.from_dict(event.data, self) guild = self.get_guild(event.data["guild_id"]) self.dispatch(events.AutoModUpdated(guild, rule)) @Processor.define() - async def raw_auto_moderation_rule_update(self, event: "RawGatewayEvent") -> None: + async def raw_auto_moderation_rule_delete(self, event: "RawGatewayEvent") -> None: rule = AutoModRule.from_dict(event.data, self) guild = self.get_guild(event.data["guild_id"]) self.dispatch(events.AutoModDeleted(guild, rule)) From 24bfc431fe2c5bbc2bd1c5aa0f6d94128c67f658 Mon Sep 17 00:00:00 2001 From: mAxYoLo01 Date: Sun, 27 Aug 2023 14:39:40 +0200 Subject: [PATCH 3/4] re-add type properties --- interactions/models/discord/auto_mod.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/interactions/models/discord/auto_mod.py b/interactions/models/discord/auto_mod.py index 8d075b125..94ccc848b 100644 --- a/interactions/models/discord/auto_mod.py +++ b/interactions/models/discord/auto_mod.py @@ -105,6 +105,12 @@ def _keyword_converter(filter: str | list[str]) -> list[str]: class KeywordTrigger(BaseTrigger): """A trigger that checks if content contains words from a user defined list of keywords""" + type: AutoModTriggerType = attrs.field( + default=AutoModTriggerType.KEYWORD, + converter=AutoModTriggerType, + repr=True, + metadata=docs("The type of trigger"), + ) keyword_filter: list[str] = attrs.field( factory=list, repr=True, @@ -141,6 +147,12 @@ class HarmfulLinkFilter(BaseTrigger): class KeywordPresetTrigger(BaseTrigger): """A trigger that checks if content contains words from internal pre-defined wordsets""" + type: AutoModTriggerType = attrs.field( + default=AutoModTriggerType.KEYWORD_PRESET, + converter=AutoModTriggerType, + repr=True, + metadata=docs("The type of trigger"), + ) keyword_lists: list[AutoModLanuguageType] = attrs.field( factory=list, converter=list_converter(AutoModLanuguageType), @@ -194,6 +206,7 @@ class SpamTrigger(BaseTrigger): class BlockMessage(BaseAction): """Blocks the content of a message according to the rule""" + type: AutoModAction = attrs.field(repr=False, default=AutoModAction.BLOCK_MESSAGE, converter=AutoModAction) custom_message: Optional[str] = attrs.field(repr=True, default=None) @@ -202,6 +215,7 @@ class AlertMessage(BaseAction): """logs user content to a specified channel""" channel_id: "Snowflake_Type" = attrs.field(repr=True) + type: AutoModAction = attrs.field(repr=False, default=AutoModAction.ALERT_MESSAGE, converter=AutoModAction) @attrs.define(eq=False, order=False, hash=False, kw_only=False) @@ -209,6 +223,7 @@ class TimeoutUser(BaseAction): """timeout user for a specified duration""" duration_seconds: int = attrs.field(repr=True, default=60) + type: AutoModAction = attrs.field(repr=False, default=AutoModAction.TIMEOUT_USER, converter=AutoModAction) @attrs.define(eq=False, order=False, hash=False, kw_only=False) From e8ab4e9b98ca719a6b15f2559b5629471985e832 Mon Sep 17 00:00:00 2001 From: mAxYoLo01 Date: Wed, 30 Aug 2023 13:40:39 +0200 Subject: [PATCH 4/4] feat: add TYPE_ALL_TRIGGER and TYPE_ALL_ACTION to interactions namespace --- interactions/__init__.py | 4 ++++ interactions/models/__init__.py | 4 ++++ interactions/models/discord/__init__.py | 4 +++- interactions/models/discord/auto_mod.py | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/interactions/__init__.py b/interactions/__init__.py index e85e5c0b6..bb9b70b2a 100644 --- a/interactions/__init__.py +++ b/interactions/__init__.py @@ -310,7 +310,9 @@ to_optional_snowflake, to_snowflake, to_snowflake_list, + TYPE_ALL_ACTION, TYPE_ALL_CHANNEL, + TYPE_ALL_TRIGGER, TYPE_CHANNEL_MAPPING, TYPE_COMPONENT_MAPPING, TYPE_DM_CHANNEL, @@ -659,7 +661,9 @@ "to_optional_snowflake", "to_snowflake", "to_snowflake_list", + "TYPE_ALL_ACTION", "TYPE_ALL_CHANNEL", + "TYPE_ALL_TRIGGER", "TYPE_CHANNEL_MAPPING", "TYPE_COMPONENT_MAPPING", "TYPE_DM_CHANNEL", diff --git a/interactions/models/__init__.py b/interactions/models/__init__.py index 25df73447..41ccfc7e0 100644 --- a/interactions/models/__init__.py +++ b/interactions/models/__init__.py @@ -167,7 +167,9 @@ to_optional_snowflake, to_snowflake, to_snowflake_list, + TYPE_ALL_ACTION, TYPE_ALL_CHANNEL, + TYPE_ALL_TRIGGER, TYPE_CHANNEL_MAPPING, TYPE_COMPONENT_MAPPING, TYPE_DM_CHANNEL, @@ -576,7 +578,9 @@ "to_optional_snowflake", "to_snowflake", "to_snowflake_list", + "TYPE_ALL_ACTION", "TYPE_ALL_CHANNEL", + "TYPE_ALL_TRIGGER", "TYPE_CHANNEL_MAPPING", "TYPE_COMPONENT_MAPPING", "TYPE_DM_CHANNEL", diff --git a/interactions/models/discord/__init__.py b/interactions/models/discord/__init__.py index e7c25618b..035932e62 100644 --- a/interactions/models/discord/__init__.py +++ b/interactions/models/discord/__init__.py @@ -2,7 +2,7 @@ from .app_perms import ApplicationCommandPermission from .application import Application from .asset import Asset -from .auto_mod import AutoModerationAction, AutoModRule +from .auto_mod import AutoModerationAction, AutoModRule, TYPE_ALL_ACTION, TYPE_ALL_TRIGGER from .channel import ( BaseChannel, ChannelHistory, @@ -337,7 +337,9 @@ "to_optional_snowflake", "to_snowflake", "to_snowflake_list", + "TYPE_ALL_ACTION", "TYPE_ALL_CHANNEL", + "TYPE_ALL_TRIGGER", "TYPE_CHANNEL_MAPPING", "TYPE_COMPONENT_MAPPING", "TYPE_DM_CHANNEL", diff --git a/interactions/models/discord/auto_mod.py b/interactions/models/discord/auto_mod.py index 94ccc848b..404791867 100644 --- a/interactions/models/discord/auto_mod.py +++ b/interactions/models/discord/auto_mod.py @@ -26,7 +26,7 @@ User, ) -__all__ = ("AutoModerationAction", "AutoModRule") +__all__ = ("AutoModerationAction", "AutoModRule", "TYPE_ALL_ACTION", "TYPE_ALL_TRIGGER") @attrs.define(eq=False, order=False, hash=False, kw_only=True)