diff --git a/discord/guild.py b/discord/guild.py index 82692ff73388..416530f3a43a 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -109,6 +109,7 @@ Guild as GuildPayload, RolePositionUpdate as RolePositionUpdatePayload, GuildFeature, + IncidentData, ) from .types.threads import ( Thread as ThreadPayload, @@ -320,6 +321,7 @@ class Guild(Hashable): 'premium_progress_bar_enabled', '_safety_alerts_channel_id', 'max_stage_video_users', + '_incidents_data', ) _PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = { @@ -509,6 +511,7 @@ def _from_data(self, guild: GuildPayload) -> None: self.owner_id: Optional[int] = utils._get_as_snowflake(guild, 'owner_id') self._large: Optional[bool] = None if self._member_count is None else self._member_count >= 250 self._afk_channel_id: Optional[int] = utils._get_as_snowflake(guild, 'afk_channel_id') + self._incidents_data: Optional[IncidentData] = guild.get('incidents_data') if 'channels' in guild: channels = guild['channels'] @@ -1843,6 +1846,8 @@ async def edit( mfa_level: MFALevel = MISSING, raid_alerts_disabled: bool = MISSING, safety_alerts_channel: TextChannel = MISSING, + invites_disabled_until: datetime.datetime = MISSING, + dms_disabled_until: datetime.datetime = MISSING, ) -> Guild: r"""|coro| @@ -1969,6 +1974,18 @@ async def edit( .. versionadded:: 2.3 + invites_disabled_until: Optional[:class:`datetime.datetime`] + The time when invites should be enabled again, or ``None`` to disable the action. + This must be a timezone-aware datetime object. Consider using :func:`utils.utcnow`. + + .. versionadded:: 2.4 + + dms_disabled_until: Optional[:class:`datetime.datetime`] + The time when direct messages should be allowed again, or ``None`` to disable the action. + This must be a timezone-aware datetime object. Consider using :func:`utils.utcnow`. + + .. versionadded:: 2.4 + Raises ------- Forbidden @@ -2157,6 +2174,30 @@ async def edit( await http.edit_guild_mfa_level(self.id, mfa_level=mfa_level.value) + incident_actions_payload: IncidentData = {} + if invites_disabled_until is not MISSING: + if invites_disabled_until is None: + incident_actions_payload['invites_disabled_until'] = None + else: + if invites_disabled_until.tzinfo is None: + raise TypeError( + 'invites_disabled_until must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' + ) + incident_actions_payload['invites_disabled_until'] = invites_disabled_until.isoformat() + + if dms_disabled_until is not MISSING: + if dms_disabled_until is None: + incident_actions_payload['dms_disabled_until'] = None + else: + if dms_disabled_until.tzinfo is None: + raise TypeError( + 'dms_disabled_until must be an aware datetime. Consider using discord.utils.utcnow() or datetime.datetime.now().astimezone() for local time.' + ) + incident_actions_payload['dms_disabled_until'] = dms_disabled_until.isoformat() + + if incident_actions_payload: + await http.edit_incident_actions(self.id, payload=incident_actions_payload) + data = await http.edit_guild(self.id, reason=reason, **fields) return Guild(data=data, state=self._state) @@ -4292,3 +4333,47 @@ async def create_automod_rule( ) return AutoModRule(data=data, guild=self, state=self._state) + + @property + def invites_paused_until(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: If invites are paused, returns when + invites will get enabled in UTC, otherwise returns None. + + .. versionadded:: 2.4 + """ + if not self._incidents_data: + return None + + return utils.parse_time(self._incidents_data.get('invites_disabled_until')) + + @property + def dms_paused_until(self) -> Optional[datetime.datetime]: + """Optional[:class:`datetime.datetime`]: If DMs are paused, returns when DMs + will get enabled in UTC, otherwise returns None. + + .. versionadded:: 2.4 + """ + if not self._incidents_data: + return None + + return utils.parse_time(self._incidents_data.get('dms_disabled_until')) + + def invites_paused(self) -> bool: + """:class:`bool`: Whether invites are paused in the guild. + + .. versionadded:: 2.4 + """ + if not self.invites_paused_until: + return False + + return self.invites_paused_until > utils.utcnow() + + def dms_paused(self) -> bool: + """:class:`bool`: Whether DMs are paused in the guild. + + .. versionadded:: 2.4 + """ + if not self.dms_paused_until: + return False + + return self.dms_paused_until > utils.utcnow() diff --git a/discord/http.py b/discord/http.py index 69c5c779952a..e97bb883e773 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1764,6 +1764,9 @@ def edit_widget( ) -> Response[widget.WidgetSettings]: return self.request(Route('PATCH', '/guilds/{guild_id}/widget', guild_id=guild_id), json=payload, reason=reason) + def edit_incident_actions(self, guild_id: Snowflake, payload: guild.IncidentData) -> Response[guild.IncidentData]: + return self.request(Route('PUT', '/guilds/{guild_id}/incident-actions', guild_id=guild_id), json=payload) + # Invite management def create_invite( diff --git a/discord/types/guild.py b/discord/types/guild.py index 44d51019a4fc..95fa2d56ea74 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -49,6 +49,11 @@ class UnavailableGuild(TypedDict): unavailable: NotRequired[bool] +class IncidentData(TypedDict): + invites_disabled_until: NotRequired[Optional[str]] + dms_disabled_until: NotRequired[Optional[str]] + + DefaultMessageNotificationLevel = Literal[0, 1] ExplicitContentFilterLevel = Literal[0, 1, 2] MFALevel = Literal[0, 1] @@ -97,6 +102,7 @@ class _BaseGuildPreview(UnavailableGuild): stickers: List[GuildSticker] features: List[GuildFeature] description: Optional[str] + incidents_data: Optional[IncidentData] class _GuildPreviewUnique(TypedDict):