Skip to content

Commit 9ad859d

Browse files
authored
feat: Implement barebones AutoMod support. (#768)
* docs, feat: Implement barebones AutoMod support. * feat: Document AutoMod Gateway event(s), intents * feat: Implement AutoMod HTTP requests. * docs: Tweak documentation according to ddevs' 88b95ce. * chore: Remove extra print statement. * chore: Post localisation space changes (?) Not sure why this was unstaged on rebase/merge. * feat: Implement AutoMod Action Metadata as dedicated object. This also fill out other Automod attribute fields via attrs. * chore: Resync to latest Discord Automod PR.
1 parent aed6ce7 commit 9ad859d

File tree

10 files changed

+353
-12
lines changed

10 files changed

+353
-12
lines changed

interactions/api/http/guild.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,3 +652,140 @@ async def get_guild_auditlog(
652652
return await self._req.request(
653653
Route("GET", f"/guilds/{guild_id}/audit-logs"), params=payload
654654
)
655+
656+
async def list_auto_moderation_rules(self, guild_id: int) -> List[dict]:
657+
"""
658+
Returns a list of all AutoMod rules in a guild.
659+
:poram guild_id: Guild ID snowflake.
660+
:return: A list of dictionaries containing the automod rules.
661+
"""
662+
663+
return await self._req.request(Route("GET", f"/guilds/{guild_id}/auto-moderation/rules"))
664+
665+
async def get_auto_moderation_rule(self, guild_id: int, rule_id: int) -> dict:
666+
"""
667+
Get a single AutoMod rule in a guild.
668+
:param guild_id: Guild ID snowflake.
669+
:param rule_id: Rule ID snowflake.
670+
:return: A dictionary containing the automod rule.
671+
"""
672+
673+
return await self._req.request(
674+
Route("GET", f"/guilds/{guild_id}/auto-moderation/rules/{rule_id}")
675+
)
676+
677+
async def create_auto_moderation_rule(
678+
self,
679+
guild_id: int,
680+
name: str,
681+
event_type: int,
682+
trigger_type: int,
683+
actions: List[dict],
684+
trigger_metadata: Optional[dict] = None,
685+
enabled: Optional[bool] = False,
686+
exempt_roles: Optional[List[str]] = None,
687+
exempt_channels: Optional[List[str]] = None,
688+
reason: Optional[str] = None,
689+
) -> dict:
690+
"""
691+
Create a new AutoMod rule in a guild.
692+
693+
:param guild_id: Guild ID snowflake.
694+
:param name: The name of the new rule.
695+
:param event_type: The event type of the new rule.
696+
:param trigger_type: The trigger type of the new rule.
697+
:param trigger_metadata: The trigger metadata payload representation. This can be omitted based on the trigger type.
698+
:param actions: The actions that will execute when the rule is triggered.
699+
:param enabled: Whether the rule will be enabled upon creation. False by default.
700+
:param exempt_roles: The role IDs that are whitelisted by the rule, if given. The maximum is 20.
701+
:param exempt_channels: The channel IDs that are whitelisted by the rule, if given. The maximum is 20
702+
:param reason: Reason to send to audit log, if any.
703+
:return: A dictionary containing the new automod rule.
704+
"""
705+
706+
params = {
707+
"name": name,
708+
"event_type": event_type,
709+
"trigger_type": trigger_type,
710+
"actions": actions,
711+
"enabled": enabled,
712+
}
713+
if trigger_metadata:
714+
params["trigger_metadata"] = trigger_metadata
715+
if exempt_roles:
716+
params["exempt_roles"] = exempt_roles
717+
if exempt_channels:
718+
params["exempt_channels"] = exempt_channels
719+
720+
return await self._req.request(
721+
Route(
722+
"POST", f"/guilds/{guild_id}/auto-moderation/rules/", params=params, reason=reason
723+
)
724+
)
725+
726+
async def modify_auto_moderation_rule(
727+
self,
728+
guild_id: int,
729+
rule_id: int,
730+
name: Optional[str] = None,
731+
event_type: Optional[int] = None,
732+
trigger_metadata: Optional[dict] = None,
733+
actions: Optional[List[dict]] = None,
734+
enabled: Optional[bool] = None,
735+
exempt_roles: Optional[List[str]] = None,
736+
exempt_channels: Optional[List[str]] = None,
737+
reason: Optional[str] = None,
738+
) -> dict:
739+
"""
740+
Modify an existing AutoMod rule in a guild.
741+
742+
.. note ::
743+
All parameters besides guild and rule ID are optional.
744+
745+
:param guild_id: Guild ID snowflake.
746+
:param rule_id: Rule ID snowflake.
747+
:param name: The new name of the rule.
748+
:param event_type: The new event type of the rule.
749+
:param trigger_metadata: The new trigger metadata payload representation. This can be omitted based on the trigger type.
750+
:param actions: The new actions that will execute when the rule is triggered.
751+
:param enabled: Whether the rule will be enabled upon creation.
752+
:param exempt_roles: The role IDs that are whitelisted by the rule, if given. The maximum is 20.
753+
:param exempt_channels: The channel IDs that are whitelisted by the rule, if given. The maximum is 20
754+
:param reason: Reason to send to audit log, if any.
755+
:return: A dictionary containing the updated automod rule.
756+
"""
757+
payload = {}
758+
if name:
759+
payload["name"] = name
760+
if event_type:
761+
payload["event_type"] = event_type
762+
if trigger_metadata:
763+
payload["trigger_metadata"] = trigger_metadata
764+
if actions:
765+
payload["actions"] = actions
766+
if enabled:
767+
payload["enabled"] = enabled
768+
if exempt_roles:
769+
payload["exempt_roles"] = exempt_roles
770+
if exempt_channels:
771+
payload["exempt_channels"] = exempt_channels
772+
773+
return await self._req.request(
774+
Route("PATCH", f"/guilds/{guild_id}/auto-moderation/rules/{rule_id}"),
775+
json=payload,
776+
reason=reason,
777+
)
778+
779+
async def delete_auto_moderation_rule(
780+
self, guild_id: int, rule_id: int, reason: Optional[str] = None
781+
) -> None:
782+
"""
783+
Deletes an AutoMod rule.
784+
:param guild_id: Guild ID snowflake.
785+
:param rule_id: Rule ID snowflake.
786+
:param reason: Reason to send to audit log, if any.
787+
"""
788+
789+
return await self._req.request(
790+
Route("DELETE", f"/guilds/{guild_id}/auto-moderation/rules/{rule_id}"), reason=reason
791+
)

interactions/api/http/guild.pyi

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,37 @@ class GuildRequest:
115115
before: Optional[int] = None,
116116
limit: int = 50,
117117
) -> dict: ...
118+
119+
async def list_auto_moderation_rules(self, guild_id: int) -> List[dict]: ...
120+
121+
async def get_auto_moderation_rule(self, guild_id: int, rule_id: int) -> dict: ...
122+
123+
async def create_auto_moderation_rule(
124+
self,
125+
guild_id: int,
126+
name: str,
127+
event_type: int,
128+
trigger_type: int,
129+
actions: List[dict],
130+
trigger_metadata: Optional[dict] = None,
131+
enabled: Optional[bool] = False,
132+
exempt_roles: Optional[List[str]] = None,
133+
exempt_channels: Optional[List[str]] = None,
134+
reason: Optional[str] = None,
135+
) -> dict: ...
136+
137+
async def modify_auto_moderation_rule(
138+
self,
139+
guild_id: int,
140+
rule_id: int,
141+
name: Optional[str] = None,
142+
event_type: Optional[int] = None,
143+
trigger_metadata: Optional[dict] = None,
144+
actions: Optional[List[dict]] = None,
145+
enabled: Optional[bool] = None,
146+
exempt_roles: Optional[List[str]] = None,
147+
exempt_channels: Optional[List[str]] = None,
148+
reason: Optional[str] = None
149+
) -> dict: ...
150+
151+
async def delete_auto_moderation_rule(self, guild_id: int, rule_id: int, reason: Optional[str] = None) -> None: ...

interactions/api/http/invite.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ async def get_invite(
2222
guild_scheduled_event_id: int = None,
2323
) -> dict:
2424
"""
25-
Gets an invite using its code.
25+
Gets a Discord invite using its code.
2626
27-
.. note:: with_expiration is currently broken, the API will always return expiration_date.
27+
.. note:: with_expiration is currently broken, the API will always return expiration_date.
2828
2929
:param invite_code: A string representing the invite code.
3030
:param with_counts: Whether approximate_member_count and approximate_presence_count are returned.
@@ -48,8 +48,8 @@ async def delete_invite(self, invite_code: str, reason: Optional[str] = None) ->
4848
"""
4949
Delete an invite.
5050
51-
:param invite_code: The code of the invite to delete
51+
:param invite_code: The code of the invite to delete.
5252
:param reason: Reason to show in the audit log, if any.
53-
:return: The deleted invite object
53+
:return: The deleted invite object.
5454
"""
5555
return await self._req.request(Route("DELETE", f"/invites/{invite_code}"), reason=reason)

interactions/api/models/flags.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class Intents(IntFlag):
2323
DIRECT_MESSAGE_TYPING = 1 << 14
2424
GUILD_MESSAGE_CONTENT = 1 << 15
2525
GUILD_SCHEDULED_EVENTS = 1 << 16
26+
AUTO_MODERATION_CONFIGURATION = 1 << 20
27+
AUTO_MODERATION_EXECUTION = 1 << 21
2628

2729
PRIVILEGED = GUILD_PRESENCES | GUILD_MEMBERS | GUILD_MESSAGE_CONTENT
2830
DEFAULT = (
@@ -40,6 +42,8 @@ class Intents(IntFlag):
4042
| DIRECT_MESSAGE_REACTIONS
4143
| DIRECT_MESSAGE_TYPING
4244
| GUILD_SCHEDULED_EVENTS
45+
| AUTO_MODERATION_CONFIGURATION
46+
| AUTO_MODERATION_EXECUTION
4347
)
4448
ALL = DEFAULT | PRIVILEGED
4549

interactions/api/models/gw.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
from .guild import EventMetadata
1616
from .member import Member
1717
from .message import Embed, Emoji, Message, MessageInteraction, Sticker
18-
from .misc import ClientStatus, File, Snowflake
18+
from .misc import AutoModAction, AutoModTriggerMetadata, ClientStatus, File, Snowflake
1919
from .presence import PresenceActivity
2020
from .role import Role
2121
from .team import Application
2222
from .user import User
2323

2424
__all__ = (
25+
"AutoModerationAction",
26+
"AutoModerationRule",
2527
"ApplicationCommandPermissions",
2628
"EmbeddedActivity",
2729
"Integration",
@@ -45,6 +47,72 @@
4547
)
4648

4749

50+
@define()
51+
class AutoModerationAction(DictSerializerMixin):
52+
"""
53+
A class object representing the gateway event ``AUTO_MODERATION_ACTION_EXECUTION``.
54+
55+
:ivar Snowflake guild_id: The ID of the guild in which the action was executed.
56+
:ivar AutoModAction action: The action which was executed.
57+
:ivar Snowflake rule_id: The rule ID that the action belongs to.
58+
:ivar int rule_trigger_type: The trigger rule type.
59+
:ivar Optional[Snowflake] channel_id: The id of the channel in which user content was posted.
60+
:ivar Optional[Snowflake] message_id: The id of any user message which content belongs to.
61+
:ivar Optional[Snowflake] alert_system_message_id: The id of any system automoderation messages posted as a result of this action.
62+
:ivar str content: The user-generated text content in question.
63+
:ivar Optional[str] matched_keyword: The word/phrase configured in rule that triggered rule.
64+
:ivar Optional[str] matched_content: The substring in content that triggered rule.
65+
"""
66+
67+
guild_id: Snowflake = field(converter=Snowflake)
68+
action: AutoModAction = field(converter=AutoModAction)
69+
rule_id: Snowflake = field(converter=Snowflake)
70+
rule_trigger_type: int = field()
71+
channel_id: Optional[Snowflake] = field(converter=Snowflake, default=None)
72+
message_id: Optional[Snowflake] = field(converter=Snowflake, default=None)
73+
alert_system_message_id: Optional[Snowflake] = field(converter=Snowflake, default=None)
74+
content: str = field()
75+
matched_keyword: Optional[str] = field(default=None)
76+
matched_content: Optional[str] = field(default=None)
77+
78+
79+
@define()
80+
class AutoModerationRule(DictSerializerMixin):
81+
"""
82+
A class object representing the gateway events ``AUTO_MODERATION_RULE_CREATE``, ``AUTO_MODERATION_RULE_UPDATE``, and ``AUTO_MODERATION_RULE_DELETE``
83+
84+
.. note::
85+
This is undocumented by the Discord API, so these attribute docs may or may not be finalised.
86+
87+
.. note::
88+
``event_type`` at the moment is only ``1``, which represents message sending.
89+
90+
:ivar Snowflake id: The ID of the rule.
91+
:ivar Snowflake guild_id: The guild ID associated with the rule.
92+
:ivar str name: The rule name.
93+
:ivar Snowflake creator_id: The user ID that first created this rule.
94+
:ivar int event_type: The rule type in which automod checks.
95+
:ivar int trigger_type: The automod type. It characterises what type of information that is checked.
96+
:ivar Dict[str, List[str]] trigger_metadata: Additional data needed to figure out whether this rule should be triggered.
97+
:ivar List[AutoModerationAction] actions: The actions that will be executed when the rule is triggered.
98+
:ivar bool enabled: Whether the rule is enabled.
99+
:ivar List[Snowflake] exempt_roles: The role IDs that should not be affected by this rule. (Max 20)
100+
:ivar List[Snowflake] exempt_channels: The channel IDs that should not be affected by this rule. (Max 20)
101+
"""
102+
103+
id: Snowflake = field(converter=Snowflake)
104+
guild_id: Snowflake = field(converter=Snowflake)
105+
name: str = field()
106+
creator_id: Snowflake = field(converter=Snowflake)
107+
event_type: int = field()
108+
trigger_type: str = field()
109+
trigger_metadata: AutoModTriggerMetadata = field(converter=AutoModTriggerMetadata)
110+
actions: List[AutoModAction] = field(converter=convert_list(AutoModAction))
111+
enabled: bool = field()
112+
exempt_roles: List[Snowflake] = field(converter=convert_list(Snowflake))
113+
exempt_channels: List[Snowflake] = field(converter=convert_list(Snowflake))
114+
115+
48116
@define()
49117
class ApplicationCommandPermissions(ClientSerializerMixin):
50118
"""

interactions/api/models/gw.pyi

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,46 @@
11
from datetime import datetime
2-
from typing import Any, List, Optional, Union
2+
from typing import Any, Dict, List, Optional, Union
33

44
from ...client.models.component import ActionRow, Button, SelectMenu
55
from .attrs_utils import ClientSerializerMixin, DictSerializerMixin, define
66
from .channel import Channel, ThreadMember
77
from .guild import EventMetadata
88
from .member import Member
99
from .message import Embed, Emoji, Message, MessageInteraction, Sticker
10-
from .misc import ClientStatus, File, Snowflake
10+
from .misc import AutoModAction, ClientStatus, File, Snowflake, AutoModTriggerMetadata
1111
from .presence import PresenceActivity
1212
from .role import Role
1313
from .team import Application
1414
from .user import User
1515

16+
17+
class AutoModerationAction(DictSerializerMixin):
18+
guild_id: Snowflake
19+
action: AutoModAction
20+
rule_id: Snowflake
21+
rule_trigger_type: int
22+
channel_id: Optional[Snowflake]
23+
message_id: Optional[Snowflake]
24+
alert_system_message_id: Optional[Snowflake]
25+
content: str
26+
matched_keyword: Optional[str]
27+
matched_content: Optional[str]
28+
29+
class AutoModerationRule(DictSerializerMixin):
30+
_json: dict
31+
id: Snowflake
32+
guild_id: Snowflake
33+
name: str
34+
creator_id: str
35+
event_type: int
36+
trigger_type: int
37+
trigger_metadata: AutoModTriggerMetadata
38+
actions: List[AutoModAction]
39+
enabled: bool
40+
exempt_roles: List[Snowflake]
41+
exempt_channels: List[Snowflake]
42+
43+
1644
@define()
1745
class ApplicationCommandPermissions(ClientSerializerMixin):
1846
application_id: Snowflake

interactions/api/models/message.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class MessageType(IntEnum):
7070
THREAD_STARTER_MESSAGE = 21
7171
GUILD_INVITE_REMINDER = 22
7272
CONTEXT_MENU_COMMAND = 23
73+
AUTO_MODERATION_ACTION = 24
7374

7475

7576
@define()

0 commit comments

Comments
 (0)