Skip to content

Commit aecb101

Browse files
EepyElvyraDamego
andauthored
feat: Add the voice state event (#1147)
* feat: add voice state event * refactor: you did not see that * refactor: do things that were requested * Update interactions/api/models/member.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/member.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/guild.py Co-authored-by: Damego <[email protected]> * Update interactions/api/gateway/client.py Co-authored-by: Damego <[email protected]> * Update interactions/api/gateway/client.py Co-authored-by: Damego <[email protected]> * Update interactions/api/gateway/client.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/gw.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/gw.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/gw.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/gw.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/gw.py * Update interactions/api/models/gw.py * Update interactions/api/models/member.py * Update interactions/api/models/channel.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/guild.py Co-authored-by: Damego <[email protected]> * Update interactions/api/models/guild.py Co-authored-by: Damego <[email protected]> Co-authored-by: Damego <[email protected]>
1 parent 3cb1f58 commit aecb101

File tree

5 files changed

+190
-5
lines changed

5 files changed

+190
-5
lines changed

interactions/api/gateway/client.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from ..http.client import HTTPClient
3434
from ..models.flags import Intents
3535
from ..models.guild import Guild
36-
from ..models.gw import GuildMember, GuildRole
36+
from ..models.gw import GuildMember, GuildRole, VoiceState
3737
from ..models.member import Member
3838
from ..models.message import Message
3939
from ..models.misc import Snowflake
@@ -463,7 +463,8 @@ def _dispatch_event(self, event: str, data: dict) -> None:
463463
log.warning(
464464
"Context is being created for the interaction, but no type is specified. Skipping..."
465465
)
466-
elif event not in {"TYPING_START", "VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE"}:
466+
467+
elif event not in {"TYPING_START", "VOICE_SERVER_UPDATE"}:
467468
name: str = event.lower()
468469
try:
469470
data["_client"] = self._http
@@ -542,6 +543,10 @@ def _dispatch_event(self, event: str, data: dict) -> None:
542543
old_obj = obj
543544

544545
_cache.add(old_obj, id)
546+
547+
if event == "VOICE_STATE_UPDATE" and not obj.channel_id: # user left
548+
del _cache[obj.user_id]
549+
545550
self._dispatch.dispatch(
546551
f"on_{name}", before, old_obj
547552
) # give previously stored and new one
@@ -601,6 +606,8 @@ def __get_object_id(
601606
"""
602607
if isinstance(obj, (Member, GuildMember)):
603608
id = (Snowflake(data["guild_id"]), obj.id)
609+
if isinstance(obj, VoiceState):
610+
id = obj.user_id
604611
else:
605612
id = getattr(obj, "id", None)
606613
if id is not None:

interactions/api/models/channel.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from ...client.models.component import ActionRow, Button, SelectMenu
2828
from ..http.client import HTTPClient
2929
from .guild import Invite, InviteTargetType
30+
from .gw import VoiceState
3031
from .member import Member
3132
from .message import Attachment, Embed, Message, Sticker
3233

@@ -500,6 +501,29 @@ def mention(self) -> str:
500501
"""
501502
return f"<#{self.id}>"
502503

504+
@property
505+
def voice_states(self) -> List["VoiceState"]:
506+
"""
507+
Returns all voice states this channel has. Only applicable for voice channels.
508+
509+
:rtype: List[VoiceState]
510+
"""
511+
if self.type != ChannelType.GUILD_VOICE:
512+
raise LibraryException(
513+
code=14, message="Cannot only get voice states from a voice channel!"
514+
)
515+
516+
if not self._client:
517+
raise LibraryException(code=13)
518+
519+
from .gw import VoiceState
520+
521+
states: List[VoiceState] = []
522+
523+
data = self._client.cache[VoiceState].values.values()
524+
states.extend(state for state in data if state.channel_id == self.id)
525+
return states
526+
503527
def history(
504528
self,
505529
start_at: Optional[Union[int, str, Snowflake, "Message"]] = MISSING,

interactions/api/models/guild.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
if TYPE_CHECKING:
4040
from ..http.client import HTTPClient
41-
from .gw import AutoModerationRule
41+
from .gw import AutoModerationRule, VoiceState
4242
from .message import Message
4343

4444
__all__ = (
@@ -540,6 +540,40 @@ async def ban(
540540
if int(member.id) == _member_id:
541541
return self.members.remove(member)
542542

543+
@property
544+
def voice_states(self) -> List["VoiceState"]:
545+
"""
546+
Gets all voice states of the guild.
547+
548+
:rtype: List[VoiceState]
549+
"""
550+
551+
if not self._client:
552+
raise LibraryException(code=13)
553+
554+
from .gw import VoiceState
555+
556+
states: List[VoiceState] = []
557+
558+
data = self._client.cache[VoiceState].values.values()
559+
states.extend(state for state in data if state.guild_id == self.id)
560+
return states
561+
562+
@property
563+
def mapped_voice_states(self) -> Dict[int, List["VoiceState"]]:
564+
"""
565+
Returns all the voice states mapped after their channel id.
566+
567+
:rtype: Dict[int, List[VoiceState]]
568+
"""
569+
states = self.voice_states
570+
_states: Dict[int, List[VoiceState]] = {int(state.channel_id): [] for state in states}
571+
572+
for state in states:
573+
_states[int(state.channel_id)].append(state)
574+
575+
return _states
576+
543577
async def remove_ban(
544578
self,
545579
user_id: Union[int, Snowflake], # only support ID since there's no member on the guild

interactions/api/models/gw.py

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from datetime import datetime
2-
from typing import Any, List, Optional
2+
from typing import Any, List, Optional, Union
33

44
from ...utils.attrs_utils import (
55
ClientSerializerMixin,
@@ -10,7 +10,7 @@
1010
)
1111
from .channel import Channel, ThreadMember
1212
from .emoji import Emoji
13-
from .guild import EventMetadata
13+
from .guild import EventMetadata, Guild
1414
from .member import Member
1515
from .message import Sticker
1616
from .misc import (
@@ -50,6 +50,7 @@
5050
"GuildJoinRequest",
5151
"GuildEmojis",
5252
"GuildRole",
53+
"VoiceState",
5354
)
5455

5556

@@ -572,3 +573,107 @@ class Webhooks(DictSerializerMixin):
572573

573574
channel_id: Snowflake = field(converter=Snowflake)
574575
guild_id: Snowflake = field(converter=Snowflake)
576+
577+
578+
@define()
579+
class VoiceState(ClientSerializerMixin):
580+
"""
581+
A class object representing the gateway event ``VOICE_STATE_UPDATE``.
582+
This class creates an object every time the event ``VOICE_STATE_UPDATE`` is received from the discord API.
583+
It contains information about the user's update voice information. Additionally, the last voice state is cached,
584+
allowing you to see, what attributes of the user's voice information change.
585+
586+
:ivar Member member: The member whose VoiceState was updated
587+
:ivar Snowflake user_id: The id of the user whose VoiceState was updated. This is technically the same as the "member id", but it is called `user_id` because of API terminology.
588+
:ivar bool suppress: Whether the user is muted by the current user(-> bot)
589+
:ivar int session_id: The id of the session
590+
:ivar bool self_video: Whether the user's camera is enabled.
591+
:ivar bool self_mute: Whether the user is muted by themselves
592+
:ivar bool self_deaf: Whether the user is deafened by themselves
593+
:ivar bool self_stream: Whether the user is streaming in the current channel
594+
:ivar datetime request_to_speak_timestamp: Only for stage-channels; when the user requested permissions to speak in the stage channel
595+
:ivar bool mute: Whether the user's microphone is muted by the server
596+
:ivar Snowflake guild_id: The id of the guild in what the update took action
597+
:ivar bool deaf: Whether the user is deafened by the guild
598+
:ivar Snowflake channel_id: The id of the channel the update took action
599+
"""
600+
601+
channel_id: Optional[Snowflake] = field(converter=Snowflake, default=None)
602+
guild_id: Optional[Snowflake] = field(converter=Snowflake, default=None)
603+
user_id: Optional[Snowflake] = field(converter=Snowflake, default=None)
604+
member: Optional[Member] = field(converter=Member, default=None, add_client=True)
605+
request_to_speak_timestamp: Optional[datetime] = field(
606+
converter=datetime.fromisoformat, default=None
607+
)
608+
suppress: bool = field()
609+
session_id: int = field()
610+
self_video: bool = field()
611+
self_mute: bool = field()
612+
self_deaf: bool = field()
613+
self_stream: Optional[bool] = field(default=None)
614+
mute: bool = field()
615+
deaf: bool = field()
616+
617+
def __attrs_post_init__(self):
618+
if self.member:
619+
self.member._extras["guild_id"] = self.guild_id
620+
621+
@property
622+
def joined(self) -> bool:
623+
"""
624+
Whether the user joined the channel.
625+
626+
:rtype: bool
627+
"""
628+
return self.channel_id is not None
629+
630+
async def mute_member(self, reason: Optional[str] = None) -> Member:
631+
"""
632+
Mutes the current member.
633+
634+
:param Optional[str] reason: The reason of the muting, optional
635+
:return: The modified member object
636+
:rtype: Member
637+
"""
638+
return await self.member.modify(guild_id=int(self.guild_id), mute=True, reason=reason)
639+
640+
async def deafen_member(self, reason: Optional[str] = None) -> Member:
641+
"""
642+
Deafens the current member.
643+
644+
:param Optional[str] reason: The reason of the deafening, optional
645+
:return: The modified member object
646+
:rtype: Member
647+
"""
648+
return await self.member.modify(guild_id=int(self.guild_id), deaf=True, reason=reason)
649+
650+
async def move_member(
651+
self, channel_id: Union[int, str, Snowflake], *, reason: Optional[str] = None
652+
) -> Member:
653+
"""
654+
Moves the member to another channel.
655+
656+
:param Union[int, str, Snowflake] channel_id: The ID of the channel to move the user to
657+
:param Optional[str] reason: The reason of the move
658+
:return: The modified member object
659+
:rtype: Member
660+
"""
661+
return await self.member.modify(
662+
guild_id=int(self.guild_id), channel_id=channel_id, reason=reason
663+
)
664+
665+
async def get_channel(self) -> Channel:
666+
"""
667+
Gets the channel in what the update took place.
668+
669+
:rtype: Channel
670+
"""
671+
return Channel(**await self._client.get_channel(int(self.channel_id)), _client=self._client)
672+
673+
async def get_guild(self) -> "Guild":
674+
"""
675+
Gets the guild in what the update took place.
676+
677+
:rtype: Guild
678+
"""
679+
return Guild(**await self._client.get_guild(int(self.guild_id)), _client=self._client)

interactions/api/models/member.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
if TYPE_CHECKING:
1515
from ...client.models.component import ActionRow, Button, SelectMenu
1616
from .guild import Guild
17+
from .gw import VoiceState
1718
from .message import Attachment, Embed, Message
1819

1920
__all__ = ("Member",)
@@ -75,6 +76,20 @@ def __getattr__(self, name):
7576
except AttributeError as e:
7677
raise AttributeError(f"Neither `User` nor `Member` have attribute {name}") from e
7778

79+
@property
80+
def voice_state(self) -> Optional["VoiceState"]:
81+
"""
82+
Returns the current voice state of the member, if any.
83+
84+
:rtype: VoiceState
85+
"""
86+
if not self._client:
87+
raise LibraryException(code=13)
88+
89+
from .gw import VoiceState
90+
91+
return self._client.cache[VoiceState].get(self.id)
92+
7893
@property
7994
def guild_id(self) -> Optional[Union[Snowflake, LibraryException]]:
8095
"""

0 commit comments

Comments
 (0)