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

feat: implement voice_channel_effect event #993

Merged
merged 27 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2820ed9
feat: implement `voice_channel_effect` event
shiftinv Mar 30, 2023
e37a22e
feat: add cached fields to raw event
shiftinv Mar 30, 2023
c63a166
docs: add/update documentation
shiftinv Mar 30, 2023
23dfb20
docs: add changelog entry
shiftinv Mar 30, 2023
5d6ff11
chore(types): use `total=False` for effect typeddict
shiftinv Mar 30, 2023
c8bdfd7
chore: run autotyping
shiftinv Mar 30, 2023
4cce725
chore: add another todo
shiftinv Mar 30, 2023
b46070a
refactor: move `VoiceChannelEffect` to `disnake.channel`
shiftinv Apr 1, 2023
ce08c34
feat: add repr
shiftinv Apr 1, 2023
1b6ba55
Merge remote-tracking branch 'upstream/master' into feature/voice-cha…
shiftinv Apr 9, 2023
4e8a8e3
Merge remote-tracking branch 'upstream/master' into feature/voice-cha…
shiftinv May 2, 2023
0264c76
Merge remote-tracking branch 'upstream/master' into feature/voice-cha…
shiftinv Jul 1, 2023
378de67
chore(docs): update versionadded
shiftinv Jul 1, 2023
b6e3608
feat: add soundboard effect fields
shiftinv Jul 1, 2023
1ee704a
chore: remove obsolete todos
shiftinv Jul 1, 2023
4202e58
docs: link new events on page
shiftinv Jul 2, 2023
b27a6de
revert: remove soundboard fields, moved to other PR
shiftinv Jul 3, 2023
ee4f70c
Merge remote-tracking branch 'upstream/master' into feature/voice-cha…
shiftinv Jun 4, 2024
f09a4fb
Merge remote-tracking branch 'upstream/master' into feature/voice-cha…
shiftinv Aug 9, 2024
1a0f017
Merge remote-tracking branch 'upstream/master' into feature/voice-cha…
shiftinv Aug 16, 2024
1d1291e
lint: make pylance happy
shiftinv Aug 16, 2024
5848c61
refactor: rename to `cached_member`
shiftinv Aug 16, 2024
b394539
refactor: create `VoiceChannelEffect` outside of raw model
shiftinv Aug 19, 2024
75a4c8f
refactor: move emoji conversion into `VoiceChannelEffect`, use emoji …
shiftinv Aug 19, 2024
beeb8f0
Merge remote-tracking branch 'upstream/master' into feature/voice-cha…
shiftinv Nov 13, 2024
2a7ced2
docs: add link to raw event
shiftinv Nov 13, 2024
022dc36
docs: effects will always happen in `VoiceChannel`s
shiftinv Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/993.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Support voice channel effect events.
- New events: :func:`on_voice_channel_effect`, :func:`on_raw_voice_channel_effect`.
- New types: :class:`VoiceChannelEffect`, :class:`RawVoiceChannelEffectEvent`.
- New enum: :class:`VoiceChannelEffectAnimationType`.
48 changes: 48 additions & 0 deletions disnake/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
ThreadLayout,
ThreadSortOrder,
VideoQualityMode,
VoiceChannelEffectAnimationType,
try_enum,
try_enum_to_int,
)
Expand All @@ -50,6 +51,7 @@
from .utils import MISSING

__all__ = (
"VoiceChannelEffect",
"TextChannel",
"VoiceChannel",
"StageChannel",
Expand Down Expand Up @@ -90,13 +92,59 @@
)
from .types.snowflake import SnowflakeList
from .types.threads import ThreadArchiveDurationLiteral
from .types.voice import VoiceChannelEffect as VoiceChannelEffectPayload
from .ui.action_row import Components, MessageUIComponent
from .ui.view import View
from .user import BaseUser, ClientUser, User
from .voice_region import VoiceRegion
from .webhook import Webhook


class VoiceChannelEffect:
"""An effect sent by a member in a voice channel.

Different sets of attributes will be present, depending on the type of effect.

.. versionadded:: 2.10

Attributes
----------
emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`]]
The emoji, for emoji reaction effects.
animation_type: Optional[:class:`VoiceChannelEffectAnimationType`]
The emoji animation type, for emoji reaction effects.
animation_id: Optional[:class:`int`]
The emoji animation ID, for emoji reaction effects.
"""

__slots__ = (
"emoji",
"animation_type",
"animation_id",
)

def __init__(self, *, data: VoiceChannelEffectPayload, state: ConnectionState) -> None:
self.emoji: Optional[Union[Emoji, PartialEmoji]] = None
if emoji_data := data.get("emoji"):
emoji = state._get_emoji_from_data(emoji_data)
if isinstance(emoji, str):
emoji = PartialEmoji(name=emoji)
self.emoji = emoji

self.animation_type = (
try_enum(VoiceChannelEffectAnimationType, value)
if (value := data.get("animation_type")) is not None
else None
)
self.animation_id: Optional[int] = utils._get_as_snowflake(data, "animation_id")

def __repr__(self) -> str:
return (
f"<VoiceChannelEffect emoji={self.emoji!r} animation_type={self.animation_type!r}"
f" animation_id={self.animation_id!r}>"
)


async def _single_delete_strategy(messages: Iterable[Message]) -> None:
for m in messages:
await m.delete()
Expand Down
19 changes: 19 additions & 0 deletions disnake/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"SKUType",
"EntitlementType",
"PollLayoutType",
"VoiceChannelEffectAnimationType",
)


Expand Down Expand Up @@ -1160,6 +1161,19 @@ class Event(Enum):
"""Called when a `Member` changes their `VoiceState`.
Represents the :func:`on_voice_state_update` event.
"""
voice_channel_effect = "voice_channel_effect"
"""Called when a `Member` sends an effect in a voice channel the bot is connected to.
Represents the :func:`on_voice_channel_effect` event.

.. versionadded:: 2.10
"""
raw_voice_channel_effect = "raw_voice_channel_effect"
"""Called when a `Member` sends an effect in a voice channel the bot is connected to,
regardless of the member cache.
Represents the :func:`on_raw_voice_channel_effect` event.

.. versionadded:: 2.10
"""
stage_instance_create = "stage_instance_create"
"""Called when a `StageInstance` is created for a `StageChannel`.
Represents the :func:`on_stage_instance_create` event.
Expand Down Expand Up @@ -1385,6 +1399,11 @@ class PollLayoutType(Enum):
default = 1


class VoiceChannelEffectAnimationType(Enum):
premium = 0
basic = 1


T = TypeVar("T")


Expand Down
2 changes: 2 additions & 0 deletions disnake/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,8 @@ def voice_states(self):
This corresponds to the following events:

- :func:`on_voice_state_update`
- :func:`on_voice_channel_effect`
- :func:`on_raw_voice_channel_effect`

This also corresponds to the following attributes and classes in terms of cache:

Expand Down
39 changes: 39 additions & 0 deletions disnake/raw_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .utils import _get_as_snowflake, get_slots

if TYPE_CHECKING:
from .channel import VoiceChannelEffect
from .member import Member
from .message import Message
from .partial_emoji import PartialEmoji
Expand All @@ -29,6 +30,7 @@
PresenceUpdateEvent,
ThreadDeleteEvent,
TypingStartEvent,
VoiceChannelEffectSendEvent,
)
from .user import User

Expand All @@ -48,6 +50,7 @@
"RawGuildMemberRemoveEvent",
"RawPresenceUpdateEvent",
"RawPollVoteActionEvent",
"RawVoiceChannelEffectEvent",
)


Expand Down Expand Up @@ -534,3 +537,39 @@ def __init__(self, data: PresenceUpdateEvent) -> None:
self.user_id: int = int(data["user"]["id"])
self.guild_id: int = int(data["guild_id"])
self.data: PresenceUpdateEvent = data


class RawVoiceChannelEffectEvent(_RawReprMixin):
"""Represents the event payload for an :func:`on_raw_voice_channel_effect` event.

.. versionadded:: 2.10

Attributes
----------
channel_id: :class:`int`
The ID of the channel where the effect was sent.
guild_id: :class:`int`
The ID of the guild where the effect was sent.
user_id: :class:`int`
The ID of the user who sent the effect.
effect: :class:`VoiceChannelEffect`
The effect that was sent.
cached_member: Optional[:class:`Member`]
The member who sent the effect, if they could be found in the internal cache.
"""

__slots__ = (
"channel_id",
"guild_id",
"user_id",
"effect",
"cached_member",
)

def __init__(self, data: VoiceChannelEffectSendEvent, effect: VoiceChannelEffect) -> None:
self.channel_id: int = int(data["channel_id"])
self.guild_id: int = int(data["guild_id"])
self.user_id: int = int(data["user_id"])
self.effect: VoiceChannelEffect = effect

self.cached_member: Optional[Member] = None
22 changes: 21 additions & 1 deletion disnake/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
StageChannel,
TextChannel,
VoiceChannel,
VoiceChannelEffect,
_guild_channel_factory,
_threaded_channel_factory,
)
Expand Down Expand Up @@ -79,6 +80,7 @@
RawThreadDeleteEvent,
RawThreadMemberRemoveEvent,
RawTypingEvent,
RawVoiceChannelEffectEvent,
)
from .role import Role
from .stage_instance import StageInstance
Expand Down Expand Up @@ -1809,7 +1811,6 @@ def parse_voice_state_update(self, data: gateway.VoiceStateUpdateEvent) -> None:
if flags.voice:
if channel_id is None and flags._voice_only and member.id != self_id:
# Only remove from cache if we only have the voice flag enabled
# Member doesn't meet the Snowflake protocol currently
guild._remove_member(member)
elif channel_id is not None:
guild._add_member(member)
Expand All @@ -1831,6 +1832,25 @@ def parse_voice_server_update(self, data: gateway.VoiceServerUpdateEvent) -> Non
logging_coroutine(coro, info="Voice Protocol voice server update handler")
)

def parse_voice_channel_effect_send(self, data: gateway.VoiceChannelEffectSendEvent) -> None:
guild = self._get_guild(int(data["guild_id"]))
if guild is None:
_log.debug(
"VOICE_CHANNEL_EFFECT_SEND referencing an unknown guild ID: %s. Discarding.",
data["guild_id"],
)
return

effect = VoiceChannelEffect(data=data, state=self)
raw = RawVoiceChannelEffectEvent(data, effect)

channel = guild.get_channel(raw.channel_id)
raw.cached_member = member = guild.get_member(raw.user_id)
self.dispatch("raw_voice_channel_effect", raw)

if channel and member:
self.dispatch("voice_channel_effect", channel, member, effect)

# FIXME: this should be refactored. The `GroupChannel` path will never be hit,
# `raw.timestamp` exists so no need to parse it twice, and `.get_user` should be used before falling back
def parse_typing_start(self, data: gateway.TypingStartEvent) -> None:
Expand Down
9 changes: 8 additions & 1 deletion disnake/types/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from .sticker import GuildSticker
from .threads import Thread, ThreadMember, ThreadMemberWithPresence, ThreadType
from .user import AvatarDecorationData, User
from .voice import GuildVoiceState, SupportedModes
from .voice import GuildVoiceState, SupportedModes, VoiceChannelEffect


class SessionStartLimit(TypedDict):
Expand Down Expand Up @@ -612,6 +612,13 @@ class VoiceServerUpdateEvent(TypedDict):
endpoint: Optional[str]


# https://discord.com/developers/docs/topics/gateway-events#voice-channel-effect-send
class VoiceChannelEffectSendEvent(VoiceChannelEffect):
channel_id: Snowflake
guild_id: Snowflake
user_id: Snowflake


# https://discord.com/developers/docs/topics/gateway-events#typing-start
class TypingStartEvent(TypedDict):
channel_id: Snowflake
Expand Down
9 changes: 9 additions & 0 deletions disnake/types/voice.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from typing_extensions import NotRequired

from .emoji import PartialEmoji
from .member import MemberWithUser
from .snowflake import Snowflake

Expand All @@ -12,6 +13,8 @@
"aead_xchacha20_poly1305_rtpsize",
]

VoiceChannelEffectAnimationType = Literal[0, 1]


class _VoiceState(TypedDict):
user_id: Snowflake
Expand Down Expand Up @@ -57,3 +60,9 @@ class VoiceReady(TypedDict):
port: int
modes: List[SupportedModes]
heartbeat_interval: int


class VoiceChannelEffect(TypedDict, total=False):
emoji: Optional[PartialEmoji]
animation_type: Optional[VoiceChannelEffectAnimationType]
animation_id: int
30 changes: 30 additions & 0 deletions docs/api/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,36 @@ Voice
:param after: The voice state after the changes.
:type after: :class:`VoiceState`

.. function:: on_voice_channel_effect(channel, member, effect)

Called when a :class:`Member` sends an effect in a voice channel the bot is connected to.

This requires :attr:`Intents.voice_states` and :attr:`Intents.members` to be enabled.

If the member is not found in the internal member cache, then this
event will not be called. Consider using :func:`on_raw_voice_channel_effect` instead.

.. versionadded:: 2.10

:param channel: The voice channel where the effect was sent.
:type channel: :class:`VoiceChannel`
:param member: The member that sent the effect.
:type member: :class:`Member`
:param effect: The effect that was sent.
:type effect: :class:`VoiceChannelEffect`

.. function:: on_raw_voice_channel_effect(payload)

Called when a :class:`Member` sends an effect in a voice channel the bot is connected to.
Unlike :func:`on_voice_channel_effect`, this is called regardless of the member cache.

This requires :attr:`Intents.voice_states` to be enabled.

.. versionadded:: 2.10

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

Interactions
~~~~~~~~~~~~

Expand Down
35 changes: 35 additions & 0 deletions docs/api/voice.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,43 @@ VoiceRegion
.. autoclass:: VoiceRegion()
:members:

VoiceChannelEffect
~~~~~~~~~~~~~~~~~~

.. attributetable:: VoiceChannelEffect

.. autoclass:: VoiceChannelEffect()
:members:

RawVoiceChannelEffectEvent
~~~~~~~~~~~~~~~~~~~~~~~~~~

.. attributetable:: RawVoiceChannelEffectEvent

.. autoclass:: RawVoiceChannelEffectEvent()
:members:


Enumerations
------------

VoiceChannelEffectAnimationType
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. class:: VoiceChannelEffectAnimationType

The type of an emoji reaction effect animation in a voice channel.

.. versionadded:: 2.10

.. attribute:: premium

A fun animation, sent by a Nitro subscriber.

.. attribute:: basic

A standard animation.

PartyType
~~~~~~~~~

Expand Down Expand Up @@ -168,3 +201,5 @@ Events
------

- :func:`on_voice_state_update(member, before, after) <disnake.on_voice_state_update>`
- :func:`on_voice_channel_effect(channel, member, effect) <disnake.on_voice_channel_effect>`
- :func:`on_raw_voice_channel_effect(payload) <disnake.on_raw_voice_channel_effect>`
Loading