Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add voice channel status events and permission #9604

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
27 changes: 25 additions & 2 deletions discord/audit_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
from .sticker import GuildSticker
from .threads import Thread
from .integrations import PartialIntegration
from .channel import ForumChannel, StageChannel, ForumTag
from .channel import ForumChannel, StageChannel, ForumTag, VoiceChannel

__all__ = (
'AuditLogDiff',
Expand Down Expand Up @@ -239,6 +239,13 @@ def _transform_default_emoji(entry: AuditLogEntry, data: str) -> PartialEmoji:
return PartialEmoji(name=data)


def _transform_status(entry: AuditLogEntry, data: Union[int, str]) -> Union[enums.EventStatus, str]:
if entry.action.name.startswith('scheduled_event_'):
return enums.try_enum(enums.EventStatus, data)
else:
return data # type: ignore # voice channel status is str


E = TypeVar('E', bound=enums.Enum)


Expand Down Expand Up @@ -332,7 +339,7 @@ class AuditLogChanges:
'communication_disabled_until': ('timed_out_until', _transform_timestamp),
'expire_behavior': (None, _enum_transformer(enums.ExpireBehaviour)),
'mfa_level': (None, _enum_transformer(enums.MFALevel)),
'status': (None, _enum_transformer(enums.EventStatus)),
'status': (None, _transform_status),
'entity_type': (None, _enum_transformer(enums.EntityType)),
'preferred_locale': (None, _enum_transformer(enums.Locale)),
'image_hash': ('cover_image', _transform_cover_image),
Expand Down Expand Up @@ -590,6 +597,11 @@ class _AuditLogProxyMemberKickOrMemberRoleUpdate(_AuditLogProxy):
integration_type: Optional[str]


class _AuditLogProxyVoiceChannelStatusAction(_AuditLogProxy):
status: Optional[str]
channel: abc.GuildChannel


class AuditLogEntry(Hashable):
r"""Represents an Audit Log entry.

Expand Down Expand Up @@ -677,6 +689,7 @@ def _from_data(self, data: AuditLogEntryPayload) -> None:
_AuditLogProxyMessageBulkDelete,
_AuditLogProxyAutoModAction,
_AuditLogProxyMemberKickOrMemberRoleUpdate,
_AuditLogProxyVoiceChannelStatusAction,
Member, User, None, PartialIntegration,
Role, Object
] = None
Expand Down Expand Up @@ -753,6 +766,13 @@ def _from_data(self, data: AuditLogEntryPayload) -> None:
app_id = int(extra['application_id'])
self.extra = self._get_integration_by_app_id(app_id) or Object(app_id, type=PartialIntegration)

elif self.action.name.startswith('voice_channel_status'):
channel_id = int(extra['channel_id'])
status = extra.get('status')
self.extra = _AuditLogProxyVoiceChannelStatusAction(
status=status, channel=self.guild.get_channel(channel_id) or Object(id=channel_id, type=VoiceChannel)
)

# this key is not present when the above is present, typically.
# It's a list of { new_value: a, old_value: b, key: c }
# where new_value and old_value are not guaranteed to be there depending
Expand Down Expand Up @@ -930,3 +950,6 @@ def _convert_target_webhook(self, target_id: int) -> Union[Webhook, Object]:
from .webhook import Webhook

return self._webhooks.get(target_id) or Object(target_id, type=Webhook)

def _convert_target_voice_channel_status(self, target_id: int) -> Union[abc.GuildChannel, Object]:
return self.guild.get_channel(target_id) or Object(id=target_id)
12 changes: 11 additions & 1 deletion discord/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -1479,9 +1479,19 @@ class VoiceChannel(VocalGuildChannel):
:attr:`~Permissions.manage_messages` bypass slowmode.

.. versionadded:: 2.2
status: Optional[:class:`str`]
The status of the voice channel. ``None`` if no status is set.
This is not available for the fetch methods such as :func:`Guild.fetch_channel`
or :func:`Client.fetch_channel`

.. versionadded:: 2.5
"""

__slots__ = ()
__slots__ = ('status',)

def __init__(self, *, state: ConnectionState, guild: Guild, data: VoiceChannelPayload) -> None:
super().__init__(state=state, guild=guild, data=data)
self.status: Optional[str] = data.get('status') or None # empty string -> None

def __repr__(self) -> str:
attrs = [
Expand Down
6 changes: 6 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ class AuditLogAction(Enum):
automod_timeout_member = 145
creator_monetization_request_created = 150
creator_monetization_terms_accepted = 151
voice_channel_status_update = 192
voice_channel_status_delete = 193
# fmt: on

@property
Expand Down Expand Up @@ -455,6 +457,8 @@ def category(self) -> Optional[AuditLogActionCategory]:
AuditLogAction.soundboard_sound_create: AuditLogActionCategory.create,
AuditLogAction.soundboard_sound_update: AuditLogActionCategory.update,
AuditLogAction.soundboard_sound_delete: AuditLogActionCategory.delete,
AuditLogAction.voice_channel_status_update: AuditLogActionCategory.update,
AuditLogAction.voice_channel_status_delete: AuditLogActionCategory.delete,
}
# fmt: on
return lookup[self]
Expand Down Expand Up @@ -500,6 +504,8 @@ def target_type(self) -> Optional[str]:
return 'user'
elif v < 152:
return 'creator_monetization'
elif v < 194:
return 'voice_channel_status'


class UserFlags(Enum):
Expand Down
18 changes: 15 additions & 3 deletions discord/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def all(cls) -> Self:
permissions set to ``True``.
"""
# Some of these are 0 because we don't want to set unnecessary bits
return cls(0b0000_0000_0000_0110_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111)
return cls(0b0000_0000_0000_0111_0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_1111)

@classmethod
def _timeout_mask(cls) -> int:
Expand Down Expand Up @@ -314,8 +314,12 @@ def text(cls) -> Self:
@classmethod
def voice(cls) -> Self:
"""A factory method that creates a :class:`Permissions` with all
"Voice" permissions from the official Discord UI set to ``True``."""
return cls(0b0000_0000_0000_0000_0010_0100_1000_0000_0000_0011_1111_0000_0000_0011_0000_0000)
"Voice" permissions from the official Discord UI set to ``True``.

.. versionchanged:: 2.5
Added :attr:`set_voice_channel_status` permission.
"""
return cls(0b0000_0000_0000_0001_0010_0100_1000_0000_0000_0011_1111_0000_0000_0011_0000_0000)

@classmethod
def stage(cls) -> Self:
Expand Down Expand Up @@ -761,6 +765,13 @@ def send_voice_messages(self) -> int:
return 1 << 46

@flag_value
def set_voice_channel_status(self) -> int:
""":class:`bool`: Returns ``True`` if a user can set the status of voice channels.

.. versionadded:: 2.5
"""
return 1 << 48

def send_polls(self) -> int:
""":class:`bool`: Returns ``True`` if a user can send poll messages.

Expand Down Expand Up @@ -907,6 +918,7 @@ class PermissionOverwrite:
send_polls: Optional[bool]
create_polls: Optional[bool]
use_external_apps: Optional[bool]
set_voice_channel_status: Optional[bool]

def __init__(self, **kwargs: Optional[bool]):
self._values: Dict[str, Optional[bool]] = {}
Expand Down
31 changes: 30 additions & 1 deletion discord/raw_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from typing import TYPE_CHECKING, Literal, Optional, Set, List, Tuple, Union

from .enums import ChannelType, try_enum, ReactionType
from .utils import _get_as_snowflake
from .utils import _get_as_snowflake, MISSING
from .app_commands import AppCommandPermissions
from .colour import Colour

Expand All @@ -50,6 +50,7 @@
TypingStartEvent,
GuildMemberRemoveEvent,
PollVoteActionEvent,
VoiceChannelStatusUpdate,
)
from .types.command import GuildApplicationCommandPermissions
from .message import Message
Expand Down Expand Up @@ -79,6 +80,7 @@
'RawMemberRemoveEvent',
'RawAppCommandPermissionsUpdateEvent',
'RawPollVoteActionEvent',
'RawVoiceChannelStatusUpdateEvent',
)


Expand Down Expand Up @@ -557,3 +559,30 @@ def __init__(self, data: PollVoteActionEvent) -> None:
self.message_id: int = int(data['message_id'])
self.guild_id: Optional[int] = _get_as_snowflake(data, 'guild_id')
self.answer_id: int = int(data['answer_id'])


class RawVoiceChannelStatusUpdateEvent(_RawReprMixin):
"""Represents the payload for a :func:`on_raw_voice_channel_status_update` event.

.. versionadded:: 2.5

Attributes
----------
channel_id: :class:`int`
The id of the voice channel whose status was updated.
guild_id: :class:`int`
The id of the guild the voice channel is in.
status: Optional[:class:`str`]
The newly updated status of the voice channel. ``None`` if no status is set.
cached_status: Optional[:class:`str`]
The cached status, if the voice channel is found in the internal channel cache otherwise :attr:`utils.MISSING`.
Represents the status before it is modified. ``None`` if no status was set.
"""

__slots__ = ('channel_id', 'guild_id', 'status', 'cached_status')

def __init__(self, data: VoiceChannelStatusUpdate):
self.channel_id: int = int(data['id'])
self.guild_id: int = int(data['guild_id'])
self.status: Optional[str] = data['status'] or None
self.cached_status: Optional[str] = MISSING
11 changes: 11 additions & 0 deletions discord/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -1690,6 +1690,17 @@ def parse_typing_start(self, data: gw.TypingStartEvent) -> None:

self.dispatch('raw_typing', raw)

def parse_voice_channel_status_update(self, data: gw.VoiceChannelStatusUpdate) -> None:
raw = RawVoiceChannelStatusUpdateEvent(data)
guild = self._get_guild(raw.guild_id)
if guild is not None:
channel = guild.get_channel(raw.channel_id)
if channel is not None:
raw.cached_status = channel.status # type: ignore # must be a voice channel
channel.status = raw.status # type: ignore # must be a voice channel

self.dispatch('raw_voice_channel_status_update', raw)

def parse_entitlement_create(self, data: gw.EntitlementCreateEvent) -> None:
entitlement = Entitlement(data=data, state=self)
self.dispatch('entitlement_create', entitlement)
Expand Down
2 changes: 2 additions & 0 deletions discord/types/audit_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
145,
150,
151,
192,
193,
]


Expand Down
1 change: 1 addition & 0 deletions discord/types/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class VoiceChannel(_BaseTextChannel):
user_limit: int
rtc_region: NotRequired[Optional[str]]
video_quality_mode: NotRequired[VideoQualityMode]
status: NotRequired[Optional[str]]


VoiceChannelEffectAnimationType = Literal[0, 1]
Expand Down
6 changes: 6 additions & 0 deletions discord/types/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,12 @@ class GuildAuditLogEntryCreate(AuditLogEntry):
guild_id: Snowflake


class VoiceChannelStatusUpdate(TypedDict):
id: Snowflake
guild_id: Snowflake
status: Optional[str]


EntitlementCreateEvent = EntitlementUpdateEvent = EntitlementDeleteEvent = Entitlement


Expand Down
52 changes: 52 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,16 @@ Channels
:param payload: The raw event payload data.
:type payload: :class:`RawTypingEvent`

.. function:: on_raw_voice_channel_status_update(payload)

Called whenever the status of a voice channel has changed.
This is called regardless of the voice channel being in the internal cache.

.. versionadded:: 2.5

:param payload: The raw event payload data.
:type payload: :class:`RawVoiceChannelStatusUpdateEvent`

Connection
~~~~~~~~~~~

Expand Down Expand Up @@ -3052,6 +3062,40 @@ of :class:`enum.Enum`.

.. versionadded:: 2.5

.. attribute:: voice_channel_status_update

The status of a voice channel was updated.

When this is the action, the type of :attr:`~AuditLogEntry.target` is
a :class:`VoiceChannel`.

When this is the action, the type of :attr:`~AuditLogEntry.extra` is
set to an unspecified proxy object with 2 attributes:

- ``status``: The status of the voice channel.
- ``channel``: The channel of which the status was updated.

When this is the action, :attr:`AuditLogEntry.changes` is empty.

.. versionadded:: 2.5

.. attribute:: voice_channel_status_delete

The status of a voice channel was deleted.

When this is the action, the type of :attr:`~AuditLogEntry.target` is
a :class:`VoiceChannel`.

When this is the action, the type of :attr:`~AuditLogEntry.extra` is
set to an unspecified proxy object with 2 attributes:

- ``status``: The status of the voice channel. For this action this is ``None``.
- ``channel``: The channel of which the status was updated.

When this is the action, :attr:`AuditLogEntry.changes` is empty.

.. versionadded:: 2.5

.. class:: AuditLogActionCategory

Represents the category that the :class:`AuditLogAction` belongs to.
Expand Down Expand Up @@ -5321,6 +5365,14 @@ RawPollVoteActionEvent
.. autoclass:: RawPollVoteActionEvent()
:members:

RawVoiceChannelStatusUpdateEvent
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. attributetable:: RawVoiceChannelStatusUpdateEvent

.. autoclass:: RawVoiceChannelStatusUpdateEvent()
:members:

PartialWebhookGuild
~~~~~~~~~~~~~~~~~~~~

Expand Down
Loading