diff --git a/discord/channel.py b/discord/channel.py index 487e851b..e3484120 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -1814,7 +1814,11 @@ class VoiceChannel(VocalGuildChannel, abc.Messageable): .. versionadded:: 2.0 """ - __slots__ = ('last_message_id',) + __slots__ = ('last_message_id', '_status') + + def __init__(self, *, state: ConnectionState, guild: Guild, data: VoiceChannelData) -> None: + super().__init__(state=state, guild=guild, data=data) + self._status: Optional[str] = data.get('status') def __repr__(self) -> str: attrs = [ @@ -1840,6 +1844,11 @@ def type(self) -> ChannelType: """:class:`ChannelType`: The channel's Discord type.""" return ChannelType.voice + @property + def status(self) -> Optional[str]: + """Optional[:class:`str`]: The current channel "status" (0-500 characters) if any.""" + return self._status + @utils.copy_doc(abc.GuildChannel.clone) async def clone(self, *, name: Optional[str] = None, reason: Optional[str] = None) -> VoiceChannel: return await self._clone_impl({ @@ -1911,6 +1920,30 @@ async def edit(self, *, reason: Optional[str] = None, **options): await self._edit(options, reason=reason) + # TODO: Add/remove this when we got a statement from Discord why bots can't set the channel status + # async def set_status(self, status: Optional[str], reason: Optional[str] = None) -> None: + # """|coro| + # + # Sets the channels status (for example the current conversation topic or game being played). + # This requires being connected to the voice channel. + # + # Parameters + # ---------- + # status: Optional[:class:`str`] + # The new status of the channel. Pass ``None`` to remove the status. + # reason: Optional[:class:`str`] = None + # The reason for editing this channel. Shows up on the audit log. + # + # Raises + # ------ + # Forbidden + # You do not have permissions to edit the channel. + # HTTPException + # Editing the channel status failed. + # """ + # + # await self._state.http.set_voice_channel_status(self.id, status=status, reason=reason) + class StageChannel(VocalGuildChannel, abc.Messageable): """Represents a Discord guild stage channel. diff --git a/discord/enums.py b/discord/enums.py index fcaaa009..7483ce20 100644 --- a/discord/enums.py +++ b/discord/enums.py @@ -866,6 +866,8 @@ class AuditLogAction(Enum): onboarding_update = 167 server_guide_create = 190 server_guide_update = 191 + voice_channel_status_update = 192 + voice_channel_status_clear = 193 @property def category(self): @@ -932,6 +934,8 @@ def category(self): AuditLogAction.onboarding_update: AuditLogActionCategory.update, AuditLogAction.server_guide_create: AuditLogActionCategory.create, AuditLogAction.server_guide_update: AuditLogActionCategory.update, + AuditLogAction.voice_channel_status_update: AuditLogActionCategory.update, + AuditLogAction.voice_channel_status_clear: AuditLogActionCategory.delete, } return lookup[self] @@ -978,8 +982,11 @@ def target_type(self): return 'onboarding_question' elif v < 168: return 'onboarding' - elif 189 < v < 200: + elif 189 < v < 192: return 'server_guide' + elif v < 194: + return 'voice_channel_status' + class UserFlags(Enum): diff --git a/discord/http.py b/discord/http.py index 6a911975..da8d128a 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1143,6 +1143,19 @@ def edit_stage_instance(self, channel_id, topic, privacy_level=None, *, reason=N def delete_stage_instance(self, channel_id, *, reason=None): return self.request(Route('DELETE', '/stage-instances/{channel_id}', channel_id=channel_id), reason=reason) + # TODO: Add/remove this when we got a statement from Discord why bots can't set the channel status + # def set_voice_channel_status( + # self, + # channel_id: int, + # status: Optional[str], + # reason: Optional[str] = None + # ): + # return self.request( + # Route('PUT', '/channels/{channel_id}/voice-status', channel_id=channel_id), + # json={'status': status}, + # reason=reason + # ) + # Webhook management def create_webhook(self, channel_id, *, name, avatar=None, reason=None): diff --git a/discord/permissions.py b/discord/permissions.py index 2a07ca4f..c688e7c6 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -654,6 +654,13 @@ def send_voice_messages(self): .. versionadded:: 2.0""" return 1 << 46 + @flag_value + def set_voice_channel_status(self): + """:class:`bool`: Returns ``True`` if a user can set the status of a voice channel. + + .. versionadded:: 2.0""" + return 1 << 48 + def augment_from_permissions(cls): cls.VALID_NAMES = set(Permissions.VALID_FLAGS) @@ -763,6 +770,7 @@ class PermissionOverwrite: use_soundboard: Optional[bool] use_external_sounds: Optional[bool] send_voice_messages: Optional[bool] + set_voice_channel_status: Optional[bool] def __init__(self, **kwargs: Optional[bool]) -> None: self._values = {} diff --git a/discord/state.py b/discord/state.py index 84de9f6c..7d96b02f 100644 --- a/discord/state.py +++ b/discord/state.py @@ -833,6 +833,25 @@ def parse_voice_channel_effect_send(self, data: gw.VoiceChannelEffectSendEvent): else: log.debug('VOICE_CHANNEL_EFFECT_SEND referencing an unknown guild ID: %s. Discarding.', data['guild_id']) + def parse_voice_channel_status_update(self, data): + guild = self._get_guild(int(data['guild_id'])) + if guild is not None: + channel = guild.get_channel(int(data['id'])) + if channel is not None: + before = copy.copy(channel.status) + channel._status = status = data['status'] + self.dispatch('voice_channel_status_update', channel, before, status) + else: + log.debug( + 'VOICE_CHANNEL_STATUS_UPDATE referencing an unknown channel ID: %s. Discarding.', + data['channel_id'] + ) + else: + log.debug( + 'VOICE_CHANNEL_STATUS_UPDATE referencing an unknown guild ID: %s. Discarding.', + data['guild_id'] + ) + def parse_message_reaction_add(self, data): emoji = data['emoji'] emoji_id = utils._get_as_snowflake(emoji, 'id') diff --git a/discord/types/channel.py b/discord/types/channel.py index df613d40..a104b705 100644 --- a/discord/types/channel.py +++ b/discord/types/channel.py @@ -126,6 +126,7 @@ class VoiceChannel(GuildChannel, total=False): nsfw: bool icon_emoji: Optional[PartialEmoji] rtc_region: Optional[str] + status: Optional[str] class StageChannel(VoiceChannel, total=False): diff --git a/docs/api.rst b/docs/api.rst index d43d99cb..bdeb4c09 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -892,6 +892,19 @@ to handle it, which defaults to print a traceback and ignoring the exception. :param after: The voice state after the changes. :type after: :class:`VoiceState` +.. function:: on_voice_channel_status_update(channel, before, after) + + Called when the :attr:`~discord.VoiceChannel.status` gets changed by a member or cleared by discord automatically. + + .. versionadded:: 2.0 + + :param channel: The voice channel that had its status updated. + :type channel: :class:`VoiceChannel` + :param before: The voice channel's old status. + :type before: :class:`str` + :param after: The voice channel's new status. + :type after: :class:`str` + .. function:: on_member_ban(guild, user) Called when user gets banned from a :class:`Guild`.