Skip to content

Commit e5ca2d1

Browse files
committed
feat: add incident management functionality to guilds
1 parent 1d67b64 commit e5ca2d1

File tree

6 files changed

+165
-2
lines changed

6 files changed

+165
-2
lines changed

discord/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from .monetization import *
5757
from .object import *
5858
from .onboarding import *
59+
from .incidents import *
5960
from .partial_emoji import *
6061
from .permissions import *
6162
from .player import *

discord/guild.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from __future__ import annotations
2727

2828
import copy
29+
import datetime
2930
import unicodedata
3031
from typing import (
3132
TYPE_CHECKING,
@@ -80,6 +81,7 @@
8081
from .mixins import Hashable
8182
from .monetization import Entitlement
8283
from .onboarding import Onboarding
84+
from .incidents import IncidentsData
8385
from .permissions import PermissionOverwrite
8486
from .role import Role, RoleColours
8587
from .scheduled_events import ScheduledEvent, ScheduledEventLocation
@@ -111,7 +113,10 @@
111113
from .state import ConnectionState
112114
from .template import Template
113115
from .types.guild import Ban as BanPayload
114-
from .types.guild import Guild as GuildPayload
116+
from .types.guild import (
117+
Guild as GuildPayload,
118+
IncidentsData as IncidentsDataPayload,
119+
)
115120
from .types.guild import GuildFeature, MFALevel
116121
from .types.member import Member as MemberPayload
117122
from .types.threads import Thread as ThreadPayload
@@ -290,6 +295,7 @@ class Guild(Hashable):
290295
"approximate_member_count",
291296
"approximate_presence_count",
292297
"_sounds",
298+
"incidents",
293299
)
294300

295301
_PREMIUM_GUILD_LIMITS: ClassVar[dict[int | None, _GuildLimit]] = {
@@ -569,6 +575,13 @@ def _from_data(self, guild: GuildPayload) -> None:
569575
sound = SoundboardSound(state=state, http=state.http, data=sound)
570576
self._add_sound(sound)
571577

578+
incidents_payload = guild.get("incidents_data")
579+
self.incidents: IncidentsData | None = (
580+
IncidentsData(data=incidents_payload, guild=self)
581+
if incidents_payload is not None
582+
else None
583+
)
584+
572585
def _add_sound(self, sound: SoundboardSound) -> None:
573586
self._sounds[sound.id] = sound
574587
self._state._add_sound(sound)
@@ -4405,6 +4418,52 @@ async def edit_onboarding(
44054418
new = await self._state.http.edit_onboarding(self.id, fields, reason=reason)
44064419
return Onboarding(data=new, guild=self)
44074420

4421+
async def modify_incident_actions(
4422+
self,
4423+
*,
4424+
invites_disabled_until: datetime.datetime | None = MISSING,
4425+
dms_disabled_until: datetime.datetime | None = MISSING,
4426+
reason: str | None = MISSING,
4427+
) -> IncidentsData:
4428+
"""|coro|
4429+
4430+
Modify the guild's incident actions (invites or DMs disabled until a
4431+
given ISO8601 timestamp). Supplying ``None`` disables the respective
4432+
action. Requires :attr:`~Permissions.manage_guild`.
4433+
4434+
Parameters
4435+
----------
4436+
invites_disabled_until: Optional[:class:`str`]
4437+
ISO8601 timestamp indicating when invites will be enabled again, or
4438+
``None`` to disable.
4439+
dms_disabled_until: Optional[:class:`str`]
4440+
ISO8601 timestamp indicating when DMs will be enabled again, or
4441+
``None`` to disable.
4442+
reason: Optional[:class:`str`]
4443+
Audit log reason.
4444+
4445+
Returns
4446+
-------
4447+
:class:`IncidentsData`
4448+
The updated incidents data for the guild.
4449+
"""
4450+
4451+
fields: IncidentsDataPayload = {}
4452+
if invites_disabled_until is not MISSING:
4453+
fields["invites_disabled_until"] = (
4454+
invites_disabled_until and invites_disabled_until.isoformat()
4455+
)
4456+
4457+
if dms_disabled_until is not MISSING:
4458+
fields["dms_disabled_until"] = (
4459+
dms_disabled_until and dms_disabled_until.isoformat()
4460+
)
4461+
4462+
new = await self._state.http.modify_guild_incident_actions(
4463+
self.id, fields, reason=reason
4464+
)
4465+
return IncidentsData(data=new, guild=self)
4466+
44084467
async def delete_auto_moderation_rule(
44094468
self,
44104469
id: int,

discord/http.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
from .file import VoiceMessage
4848
from .gateway import DiscordClientWebSocketResponse
4949
from .soundboard import PartialSoundboardSound, SoundboardSound
50-
from .utils import MISSING, warn_deprecated
50+
from .utils import MISSING
5151

5252
_log = logging.getLogger(__name__)
5353

@@ -3135,6 +3135,19 @@ def edit_onboarding(
31353135
reason=reason,
31363136
)
31373137

3138+
def modify_guild_incident_actions(
3139+
self,
3140+
guild_id: Snowflake,
3141+
payload: guild.IncidentsData,
3142+
*,
3143+
reason: str | None = None,
3144+
) -> Response[guild.IncidentsData]:
3145+
return self.request(
3146+
Route("PUT", "/guilds/{guild_id}/incident-actions", guild_id=guild_id),
3147+
json=payload,
3148+
reason=reason,
3149+
)
3150+
31383151
# Polls
31393152

31403153
def expire_poll(

discord/incidents.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from __future__ import annotations
2+
3+
import datetime
4+
from typing import TYPE_CHECKING, Optional
5+
6+
if TYPE_CHECKING:
7+
from .guild import Guild
8+
from .types.guild import IncidentsData as IncidentsDataPayload
9+
10+
__all__ = ("IncidentsData",)
11+
12+
13+
class IncidentsData:
14+
"""Represents the incidents data object for a guild.
15+
16+
Attributes
17+
----------
18+
invites_disabled_until: Optional[datetime.datetime]
19+
When invites will be enabled again as a :class:`datetime.datetime`, or ``None``.
20+
dms_disabled_until: Optional[datetime.datetime]
21+
When direct messages will be enabled again as a :class:`datetime.datetime`, or ``None``.
22+
dm_spam_detected_at: Optional[datetime.datetime]
23+
When DM spam was detected, or ``None``.
24+
raid_detected_at: Optional[datetime.datetime]
25+
When a raid was detected, or ``None``.
26+
"""
27+
28+
__slots__ = (
29+
"guild",
30+
"invites_disabled_until",
31+
"dms_disabled_until",
32+
"dm_spam_detected_at",
33+
"raid_detected_at",
34+
)
35+
36+
def __init__(self, data: IncidentsDataPayload, guild: Optional[Guild] = None):
37+
self.guild = guild
38+
39+
self.invites_disabled_until: datetime.datetime | None = (
40+
datetime.datetime.fromisoformat(s)
41+
if (s := data.get("invites_disabled_until"))
42+
else None
43+
)
44+
45+
self.dms_disabled_until: datetime.datetime | None = (
46+
datetime.datetime.fromisoformat(s)
47+
if (s := data.get("dms_disabled_until"))
48+
else None
49+
)
50+
51+
self.dm_spam_detected_at: datetime.datetime | None = (
52+
datetime.datetime.fromisoformat(s)
53+
if (s := data.get("dm_spam_detected_at"))
54+
else None
55+
)
56+
57+
self.raid_detected_at: datetime.datetime | None = (
58+
datetime.datetime.fromisoformat(s)
59+
if (s := data.get("raid_detected_at"))
60+
else None
61+
)
62+
63+
def to_dict(self) -> IncidentsDataPayload:
64+
"""Converts this object back to a raw payload suitable for API use."""
65+
return {
66+
"invites_disabled_until": self.invites_disabled_until
67+
and self.invites_disabled_until.isoformat(),
68+
"dms_disabled_until": self.dms_disabled_until
69+
and self.dms_disabled_until.isoformat(),
70+
"dm_spam_detected_at": self.dm_spam_detected_at
71+
and self.dm_spam_detected_at.isoformat(),
72+
"raid_detected_at": self.raid_detected_at
73+
and self.raid_detected_at.isoformat(),
74+
}

discord/types/guild.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ class Guild(_BaseGuildPreview):
164164
premium_tier: PremiumTier
165165
preferred_locale: str
166166
public_updates_channel_id: Snowflake | None
167+
incidents_data: IncidentsData | None
167168

168169

169170
class InviteGuild(Guild, total=False):
@@ -197,3 +198,15 @@ class GuildMFAModify(TypedDict):
197198
class GuildBulkBan(TypedDict):
198199
banned_users: list[Snowflake]
199200
failed_users: list[Snowflake]
201+
202+
203+
class IncidentsData(TypedDict, total=False):
204+
invites_disabled_until: str | None
205+
dms_disabled_until: str | None
206+
dm_spam_detected_at: str | None
207+
raid_detected_at: str | None
208+
209+
210+
class ModifyIncidents(TypedDict, total=False):
211+
invites_disabled_until: str | None
212+
dms_disabled_until: str | None

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,6 @@ max-line-length = 120
105105

106106
[tool.pytest.ini_options]
107107
asyncio_mode = "auto"
108+
109+
[tool.basedpyright]
110+
typeCheckingMode = "strict"

0 commit comments

Comments
 (0)