diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bfbc36a62..d0923c07b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2342](https://github.com/Pycord-Development/pycord/pull/2342)) - Added `invitable` and `slowmode_delay` to `Thread` creation methods. ([#2350](https://github.com/Pycord-Development/pycord/pull/2350)) +- Added support for voice channel statuses. + ([#2368](https://github.com/Pycord-Development/pycord/pull/2368)) ### Changed diff --git a/discord/channel.py b/discord/channel.py index afa6780515..2586e3eed7 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1519,6 +1519,10 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): Bots and users with :attr:`~Permissions.manage_channels` or :attr:`~Permissions.manage_messages` bypass slowmode. + .. versionadded:: 2.5 + status: Optional[:class:`str`] + The channel's status, if set. + .. versionadded:: 2.5 flags: :class:`ChannelFlags` Extra features of the channel. @@ -1526,13 +1530,26 @@ class VoiceChannel(discord.abc.Messageable, VocalGuildChannel): .. versionadded:: 2.0 """ + def __init__( + self, + *, + state: ConnectionState, + guild: Guild, + data: VoiceChannelPayload, + ): + self.status: str | None = None + super().__init__(state=state, guild=guild, data=data) + def _update(self, guild: Guild, data: VoiceChannelPayload): super()._update(guild, data) + if data.get("status"): + self.status = data.get("status") def __repr__(self) -> str: attrs = [ ("id", self.id), ("name", self.name), + ("status", self.status), ("rtc_region", self.rtc_region), ("position", self.position), ("bitrate", self.bitrate), @@ -1955,6 +1972,31 @@ async def create_activity_invite( **kwargs, ) + async def set_status( + self, status: str | None, *, reason: str | None = None + ) -> None: + """|coro| + + Sets the status of the voice channel. + + You must have the :attr:`~Permissions.set_voice_channel_status` permission to use this. + + Parameters + ---------- + status: Union[:class:`str`, None] + The new status. + reason: Optional[:class:`str`] + The reason for setting the status. Shows up on the audit log. + + Raises + ------ + Forbidden + You do not have proper permissions to set the status. + HTTPException + Setting the status failed. + """ + await self._state.http.set_voice_channel_status(self.id, status, reason=reason) + class StageChannel(discord.abc.Messageable, VocalGuildChannel): """Represents a Discord guild stage channel. diff --git a/discord/http.py b/discord/http.py index b5eadc031d..acf0807a3b 100644 --- a/discord/http.py +++ b/discord/http.py @@ -2185,6 +2185,13 @@ def move_member( guild_id=guild_id, user_id=user_id, channel_id=channel_id, reason=reason ) + def set_voice_channel_status( + self, channel_id: Snowflake, status: str | None, *, reason: str | None = None + ) -> Response[None]: + payload = {"status": status} + r = Route("PUT", "/channels/{channel_id}/voice-status", channel_id=channel_id) + return self.request(r, json=payload, reason=reason) + # Stage instance management def get_stage_instance( diff --git a/discord/permissions.py b/discord/permissions.py index e94e6c2116..75b71d57cd 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -180,7 +180,7 @@ def all(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all permissions set to ``True``. """ - return cls(0b11111111111111111111111111111111111111111) + return cls(0b1111111111111111111111111111111111111111111111111) @classmethod def all_channel(cls: type[P]) -> P: @@ -250,7 +250,7 @@ def voice(cls: type[P]) -> P: """A factory method that creates a :class:`Permissions` with all "Voice" permissions from the official Discord UI set to ``True``. """ - return cls(0b00000011111100000000001100000000) + return cls(0b1000000001000000000000011111100000000001100000000) @classmethod def stage(cls: type[P]) -> P: @@ -618,6 +618,14 @@ def send_voice_messages(self) -> int: """ return 1 << 46 + @flag_value + def set_voice_channel_status(self) -> int: + """:class:`bool`: Returns ``True`` if a member can set voice channel status. + + .. versionadded:: 2.5 + """ + return 1 << 48 + PO = TypeVar("PO", bound="PermissionOverwrite") @@ -736,6 +744,7 @@ class PermissionOverwrite: start_embedded_activities: bool | None moderate_members: bool | None send_voice_messages: bool | None + set_voice_channel_status: bool | None def __init__(self, **kwargs: bool | None): self._values: dict[str, bool | None] = {} diff --git a/discord/raw_models.py b/discord/raw_models.py index 79c8091d98..873c1fe0bd 100644 --- a/discord/raw_models.py +++ b/discord/raw_models.py @@ -56,6 +56,7 @@ ThreadMembersUpdateEvent, ThreadUpdateEvent, TypingEvent, + VoiceChannelStatusUpdateEvent, ) @@ -75,6 +76,7 @@ "AutoModActionExecutionEvent", "RawThreadMembersUpdateEvent", "RawAuditLogEntryEvent", + "RawVoiceChannelStatusUpdateEvent", ) @@ -441,6 +443,36 @@ def __init__(self, data: ThreadDeleteEvent) -> None: self.data: ThreadDeleteEvent = data +class RawVoiceChannelStatusUpdateEvent(_RawReprMixin): + """Represents the payload for an :func:`on_raw_voice_channel_status_update` event. + + .. versionadded:: 2.5 + + Attributes + ---------- + id: :class:`int` + The channel ID where the voice channel status update originated from. + guild_id: :class:`int` + The guild ID where the voice channel status update originated from. + status: Optional[:class:`str`] + The new new voice channel status. + data: :class:`dict` + The raw data sent by the `gateway `_. + """ + + __slots__ = ("id", "guild_id", "status", "data") + + def __init__(self, data: VoiceChannelStatusUpdateEvent) -> None: + self.id: int = int(data["id"]) + self.guild_id: int = int(data["guild_id"]) + + try: + self.status: str | None = data["status"] + except KeyError: + self.status: str | None = None + self.data: VoiceChannelStatusUpdateEvent = data + + class RawTypingEvent(_RawReprMixin): """Represents the payload for a :func:`on_raw_typing` event. diff --git a/discord/state.py b/discord/state.py index 011dd0e142..5b9aea0e35 100644 --- a/discord/state.py +++ b/discord/state.py @@ -1785,6 +1785,30 @@ def parse_voice_server_update(self, data) -> None: ) ) + def parse_voice_channel_status_update(self, data) -> None: + raw = RawVoiceChannelStatusUpdateEvent(data) + self.dispatch("raw_voice_channel_status_update", raw) + guild = self._get_guild(int(data["guild_id"])) + channel_id = int(data["id"]) + if guild is not None: + channel = guild.get_channel(channel_id) + if channel is not None: + old_status = channel.status + channel.status = data.get("status", None) + self.dispatch( + "voice_channel_status_update", channel, old_status, channel.status + ) + else: + _log.debug( + "VOICE_CHANNEL_STATUS_UPDATE referencing an unknown channel ID: %s. Discarding.", + channel_id, + ) + else: + _log.debug( + "VOICE_CHANNEL_STATUS_UPDATE referencing unknown guild ID: %s. Discarding.", + data["guild_id"], + ) + def parse_typing_start(self, data) -> None: raw = RawTypingEvent(data) diff --git a/discord/types/channel.py b/discord/types/channel.py index 79e7b9ebbf..0f4c044aed 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -108,6 +108,7 @@ class NewsChannel(_BaseGuildChannel, _TextChannelOptional): class VoiceChannel(_BaseGuildChannel): rtc_region: NotRequired[str | None] video_quality_mode: NotRequired[VideoQualityMode] + status: NotRequired[str | None] type: Literal[2] bitrate: int user_limit: int diff --git a/discord/types/raw_models.py b/discord/types/raw_models.py index db2f5dde5f..de2b3fbf2b 100644 --- a/discord/types/raw_models.py +++ b/discord/types/raw_models.py @@ -131,6 +131,12 @@ class MemberRemoveEvent(TypedDict): user: User +class VoiceChannelStatusUpdateEvent(TypedDict): + id: Snowflake + guild_id: Snowflake + status: NotRequired[str] + + class ThreadMembersUpdateEvent(TypedDict): id: Snowflake guild_id: Snowflake diff --git a/docs/api/events.rst b/docs/api/events.rst index 8a0b76d13b..25edc139b0 100644 --- a/docs/api/events.rst +++ b/docs/api/events.rst @@ -1299,3 +1299,28 @@ Typing :param payload: The raw typing payload. :type payload: :class:`RawTypingEvent` + + +Voice Channel Status Update +--------------------------- +.. function:: on_voice_channel_status_update(channel, before, after) + + Called when someone updates a voice channel status. + + .. versionadded:: 2.5 + + :param channel: The channel where the voice channel status update originated from. + :type channel: :class:`abc.GuildChannel` + :param before: The old voice channel status. + :type before: Optional[:class:`str`] + :param after: The new voice channel status. + :type after: Optional[:class:`str`] + +.. function:: on_raw_voice_channel_status_update(payload) + + Called when someone updates a voice channels status. + + .. versionadded:: 2.5 + + :param payload: The raw voice channel status update payload. + :type payload: :class:`RawVoiceChannelStatusUpdateEvent` diff --git a/docs/api/models.rst b/docs/api/models.rst index 4fce609baf..5fec2f0dd0 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -556,6 +556,11 @@ Events .. autoclass:: RawAuditLogEntryEvent() :members: +.. attributetable:: RawVoiceChannelStatusUpdateEvent + +.. autoclass:: RawVoiceChannelStatusUpdateEvent() + :members: + Webhooks