mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-09-06 09:56:09 +00:00
Support for Soundboard and VC effects
This commit is contained in:
@ -47,7 +47,16 @@ import datetime
|
||||
import discord.abc
|
||||
from .scheduled_event import ScheduledEvent
|
||||
from .permissions import PermissionOverwrite, Permissions
|
||||
from .enums import ChannelType, ForumLayoutType, ForumOrderType, PrivacyLevel, try_enum, VideoQualityMode, EntityType
|
||||
from .enums import (
|
||||
ChannelType,
|
||||
ForumLayoutType,
|
||||
ForumOrderType,
|
||||
PrivacyLevel,
|
||||
try_enum,
|
||||
VideoQualityMode,
|
||||
EntityType,
|
||||
VoiceChannelEffectAnimationType,
|
||||
)
|
||||
from .mixins import Hashable
|
||||
from . import utils
|
||||
from .utils import MISSING
|
||||
@ -58,6 +67,8 @@ from .threads import Thread
|
||||
from .partial_emoji import _EmojiTag, PartialEmoji
|
||||
from .flags import ChannelFlags
|
||||
from .http import handle_message_parameters
|
||||
from .object import Object
|
||||
from .soundboard import BaseSoundboardSound, SoundboardDefaultSound
|
||||
|
||||
__all__ = (
|
||||
'TextChannel',
|
||||
@ -69,6 +80,8 @@ __all__ = (
|
||||
'ForumChannel',
|
||||
'GroupChannel',
|
||||
'PartialMessageable',
|
||||
'VoiceChannelEffect',
|
||||
'VoiceChannelSoundEffect',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -76,7 +89,6 @@ if TYPE_CHECKING:
|
||||
|
||||
from .types.threads import ThreadArchiveDuration
|
||||
from .role import Role
|
||||
from .object import Object
|
||||
from .member import Member, VoiceState
|
||||
from .abc import Snowflake, SnowflakeTime
|
||||
from .embeds import Embed
|
||||
@ -100,8 +112,11 @@ if TYPE_CHECKING:
|
||||
ForumChannel as ForumChannelPayload,
|
||||
MediaChannel as MediaChannelPayload,
|
||||
ForumTag as ForumTagPayload,
|
||||
VoiceChannelEffect as VoiceChannelEffectPayload,
|
||||
)
|
||||
from .types.snowflake import SnowflakeList
|
||||
from .types.soundboard import BaseSoundboardSound as BaseSoundboardSoundPayload
|
||||
from .soundboard import SoundboardSound
|
||||
|
||||
OverwriteKeyT = TypeVar('OverwriteKeyT', Role, BaseUser, Object, Union[Role, Member, Object])
|
||||
|
||||
@ -111,6 +126,121 @@ class ThreadWithMessage(NamedTuple):
|
||||
message: Message
|
||||
|
||||
|
||||
class VoiceChannelEffectAnimation(NamedTuple):
|
||||
id: int
|
||||
type: VoiceChannelEffectAnimationType
|
||||
|
||||
|
||||
class VoiceChannelSoundEffect(BaseSoundboardSound):
|
||||
"""Represents a Discord voice channel sound effect.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
|
||||
.. container:: operations
|
||||
|
||||
.. describe:: x == y
|
||||
|
||||
Checks if two sound effects are equal.
|
||||
|
||||
.. describe:: x != y
|
||||
|
||||
Checks if two sound effects are not equal.
|
||||
|
||||
.. describe:: hash(x)
|
||||
|
||||
Returns the sound effect's hash.
|
||||
|
||||
Attributes
|
||||
------------
|
||||
id: :class:`int`
|
||||
The ID of the sound.
|
||||
volume: :class:`float`
|
||||
The volume of the sound as floating point percentage (e.g. ``1.0`` for 100%).
|
||||
"""
|
||||
|
||||
__slots__ = ('_state',)
|
||||
|
||||
def __init__(self, *, state: ConnectionState, id: int, volume: float):
|
||||
data: BaseSoundboardSoundPayload = {
|
||||
'sound_id': id,
|
||||
'volume': volume,
|
||||
}
|
||||
super().__init__(state=state, data=data)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__} id={self.id} volume={self.volume}>"
|
||||
|
||||
@property
|
||||
def created_at(self) -> Optional[datetime.datetime]:
|
||||
"""Optional[:class:`datetime.datetime`]: Returns the snowflake's creation time in UTC.
|
||||
Returns ``None`` if it's a default sound."""
|
||||
if self.is_default():
|
||||
return None
|
||||
else:
|
||||
return utils.snowflake_time(self.id)
|
||||
|
||||
def is_default(self) -> bool:
|
||||
""":class:`bool`: Whether it's a default sound or not."""
|
||||
# if it's smaller than the Discord Epoch it cannot be a snowflake
|
||||
return self.id < utils.DISCORD_EPOCH
|
||||
|
||||
|
||||
class VoiceChannelEffect:
|
||||
"""Represents a Discord voice channel effect.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
|
||||
Attributes
|
||||
------------
|
||||
channel: :class:`VoiceChannel`
|
||||
The channel in which the effect is sent.
|
||||
user: Optional[:class:`Member`]
|
||||
The user who sent the effect. ``None`` if not found in cache.
|
||||
animation: Optional[:class:`VoiceChannelEffectAnimation`]
|
||||
The animation the effect has. Returns ``None`` if the effect has no animation.
|
||||
emoji: Optional[:class:`PartialEmoji`]
|
||||
The emoji of the effect.
|
||||
sound: Optional[:class:`VoiceChannelSoundEffect`]
|
||||
The sound of the effect. Returns ``None`` if it's an emoji effect.
|
||||
"""
|
||||
|
||||
__slots__ = ('channel', 'user', 'animation', 'emoji', 'sound')
|
||||
|
||||
def __init__(self, *, state: ConnectionState, data: VoiceChannelEffectPayload, guild: Guild):
|
||||
self.channel: VoiceChannel = guild.get_channel(int(data['channel_id'])) # type: ignore # will always be a VoiceChannel
|
||||
self.user: Optional[Member] = guild.get_member(int(data['user_id']))
|
||||
self.animation: Optional[VoiceChannelEffectAnimation] = None
|
||||
|
||||
animation_id = data.get('animation_id')
|
||||
if animation_id is not None:
|
||||
animation_type = try_enum(VoiceChannelEffectAnimationType, data['animation_type']) # type: ignore # cannot be None here
|
||||
self.animation = VoiceChannelEffectAnimation(id=animation_id, type=animation_type)
|
||||
|
||||
emoji = data.get('emoji')
|
||||
self.emoji: Optional[PartialEmoji] = PartialEmoji.from_dict(emoji) if emoji is not None else None
|
||||
self.sound: Optional[VoiceChannelSoundEffect] = None
|
||||
|
||||
sound_id: Optional[int] = utils._get_as_snowflake(data, 'sound_id')
|
||||
if sound_id is not None:
|
||||
sound_volume = data.get('sound_volume') or 0.0
|
||||
self.sound = VoiceChannelSoundEffect(state=state, id=sound_id, volume=sound_volume)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
attrs = [
|
||||
('channel', self.channel),
|
||||
('user', self.user),
|
||||
('animation', self.animation),
|
||||
('emoji', self.emoji),
|
||||
('sound', self.sound),
|
||||
]
|
||||
inner = ' '.join('%s=%r' % t for t in attrs)
|
||||
return f"<{self.__class__.__name__} {inner}>"
|
||||
|
||||
def is_sound(self) -> bool:
|
||||
""":class:`bool`: Whether the effect is a sound or not."""
|
||||
return self.sound is not None
|
||||
|
||||
|
||||
class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||
"""Represents a Discord guild text channel.
|
||||
|
||||
@ -1456,6 +1586,35 @@ class VoiceChannel(VocalGuildChannel):
|
||||
# the payload will always be the proper channel payload
|
||||
return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore
|
||||
|
||||
async def send_sound(self, sound: Union[SoundboardSound, SoundboardDefaultSound], /) -> None:
|
||||
"""|coro|
|
||||
|
||||
Sends a soundboard sound for this channel.
|
||||
|
||||
You must have :attr:`~Permissions.speak` and :attr:`~Permissions.use_soundboard` to do this.
|
||||
Additionally, you must have :attr:`~Permissions.use_external_sounds` if the sound is from
|
||||
a different guild.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
sound: Union[:class:`SoundboardSound`, :class:`SoundboardDefaultSound`]
|
||||
The sound to send for this channel.
|
||||
|
||||
Raises
|
||||
-------
|
||||
Forbidden
|
||||
You do not have permissions to send a sound for this channel.
|
||||
HTTPException
|
||||
Sending the sound failed.
|
||||
"""
|
||||
payload = {'sound_id': sound.id}
|
||||
if not isinstance(sound, SoundboardDefaultSound) and self.guild.id != sound.guild.id:
|
||||
payload['source_guild_id'] = sound.guild.id
|
||||
|
||||
await self._state.http.send_soundboard_sound(self.id, **payload)
|
||||
|
||||
|
||||
class StageChannel(VocalGuildChannel):
|
||||
"""Represents a Discord guild stage channel.
|
||||
|
Reference in New Issue
Block a user