mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-07-21 10:26:47 +00:00
Support for Soundboard and VC effects
This commit is contained in:
parent
59f877fcf0
commit
a70217a719
@ -70,6 +70,7 @@ from .components import *
|
|||||||
from .threads import *
|
from .threads import *
|
||||||
from .automod import *
|
from .automod import *
|
||||||
from .poll import *
|
from .poll import *
|
||||||
|
from .soundboard import *
|
||||||
|
|
||||||
|
|
||||||
class VersionInfo(NamedTuple):
|
class VersionInfo(NamedTuple):
|
||||||
|
@ -235,6 +235,10 @@ def _transform_automod_actions(entry: AuditLogEntry, data: List[AutoModerationAc
|
|||||||
return [AutoModRuleAction.from_data(action) for action in data]
|
return [AutoModRuleAction.from_data(action) for action in data]
|
||||||
|
|
||||||
|
|
||||||
|
def _transform_default_emoji(entry: AuditLogEntry, data: str) -> PartialEmoji:
|
||||||
|
return PartialEmoji(name=data)
|
||||||
|
|
||||||
|
|
||||||
E = TypeVar('E', bound=enums.Enum)
|
E = TypeVar('E', bound=enums.Enum)
|
||||||
|
|
||||||
|
|
||||||
@ -341,6 +345,8 @@ class AuditLogChanges:
|
|||||||
'available_tags': (None, _transform_forum_tags),
|
'available_tags': (None, _transform_forum_tags),
|
||||||
'flags': (None, _transform_overloaded_flags),
|
'flags': (None, _transform_overloaded_flags),
|
||||||
'default_reaction_emoji': (None, _transform_default_reaction),
|
'default_reaction_emoji': (None, _transform_default_reaction),
|
||||||
|
'emoji_name': ('emoji', _transform_default_emoji),
|
||||||
|
'user_id': ('user', _transform_member_id)
|
||||||
}
|
}
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
@ -47,7 +47,16 @@ import datetime
|
|||||||
import discord.abc
|
import discord.abc
|
||||||
from .scheduled_event import ScheduledEvent
|
from .scheduled_event import ScheduledEvent
|
||||||
from .permissions import PermissionOverwrite, Permissions
|
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 .mixins import Hashable
|
||||||
from . import utils
|
from . import utils
|
||||||
from .utils import MISSING
|
from .utils import MISSING
|
||||||
@ -58,6 +67,8 @@ from .threads import Thread
|
|||||||
from .partial_emoji import _EmojiTag, PartialEmoji
|
from .partial_emoji import _EmojiTag, PartialEmoji
|
||||||
from .flags import ChannelFlags
|
from .flags import ChannelFlags
|
||||||
from .http import handle_message_parameters
|
from .http import handle_message_parameters
|
||||||
|
from .object import Object
|
||||||
|
from .soundboard import BaseSoundboardSound, SoundboardDefaultSound
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'TextChannel',
|
'TextChannel',
|
||||||
@ -69,6 +80,8 @@ __all__ = (
|
|||||||
'ForumChannel',
|
'ForumChannel',
|
||||||
'GroupChannel',
|
'GroupChannel',
|
||||||
'PartialMessageable',
|
'PartialMessageable',
|
||||||
|
'VoiceChannelEffect',
|
||||||
|
'VoiceChannelSoundEffect',
|
||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -76,7 +89,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
from .types.threads import ThreadArchiveDuration
|
from .types.threads import ThreadArchiveDuration
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .object import Object
|
|
||||||
from .member import Member, VoiceState
|
from .member import Member, VoiceState
|
||||||
from .abc import Snowflake, SnowflakeTime
|
from .abc import Snowflake, SnowflakeTime
|
||||||
from .embeds import Embed
|
from .embeds import Embed
|
||||||
@ -100,8 +112,11 @@ if TYPE_CHECKING:
|
|||||||
ForumChannel as ForumChannelPayload,
|
ForumChannel as ForumChannelPayload,
|
||||||
MediaChannel as MediaChannelPayload,
|
MediaChannel as MediaChannelPayload,
|
||||||
ForumTag as ForumTagPayload,
|
ForumTag as ForumTagPayload,
|
||||||
|
VoiceChannelEffect as VoiceChannelEffectPayload,
|
||||||
)
|
)
|
||||||
from .types.snowflake import SnowflakeList
|
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])
|
OverwriteKeyT = TypeVar('OverwriteKeyT', Role, BaseUser, Object, Union[Role, Member, Object])
|
||||||
|
|
||||||
@ -111,6 +126,121 @@ class ThreadWithMessage(NamedTuple):
|
|||||||
message: Message
|
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):
|
class TextChannel(discord.abc.Messageable, discord.abc.GuildChannel, Hashable):
|
||||||
"""Represents a Discord guild text channel.
|
"""Represents a Discord guild text channel.
|
||||||
|
|
||||||
@ -1456,6 +1586,35 @@ class VoiceChannel(VocalGuildChannel):
|
|||||||
# the payload will always be the proper channel payload
|
# the payload will always be the proper channel payload
|
||||||
return self.__class__(state=self._state, guild=self.guild, data=payload) # type: ignore
|
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):
|
class StageChannel(VocalGuildChannel):
|
||||||
"""Represents a Discord guild stage channel.
|
"""Represents a Discord guild stage channel.
|
||||||
|
@ -77,6 +77,7 @@ from .ui.dynamic import DynamicItem
|
|||||||
from .stage_instance import StageInstance
|
from .stage_instance import StageInstance
|
||||||
from .threads import Thread
|
from .threads import Thread
|
||||||
from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory
|
from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory
|
||||||
|
from .soundboard import SoundboardDefaultSound, SoundboardSound
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
@ -383,6 +384,14 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
return self._connection.stickers
|
return self._connection.stickers
|
||||||
|
|
||||||
|
@property
|
||||||
|
def soundboard_sounds(self) -> List[SoundboardSound]:
|
||||||
|
"""List[:class:`.SoundboardSound`]: The soundboard sounds that the connected client has.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
"""
|
||||||
|
return self._connection.soundboard_sounds
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cached_messages(self) -> Sequence[Message]:
|
def cached_messages(self) -> Sequence[Message]:
|
||||||
"""Sequence[:class:`.Message`]: Read-only list of messages the connected client has cached.
|
"""Sequence[:class:`.Message`]: Read-only list of messages the connected client has cached.
|
||||||
@ -1109,6 +1118,23 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
return self._connection.get_sticker(id)
|
return self._connection.get_sticker(id)
|
||||||
|
|
||||||
|
def get_soundboard_sound(self, id: int, /) -> Optional[SoundboardSound]:
|
||||||
|
"""Returns a soundboard sound with the given ID.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
id: :class:`int`
|
||||||
|
The ID to search for.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Optional[:class:`.SoundboardSound`]
|
||||||
|
The soundboard sound or ``None`` if not found.
|
||||||
|
"""
|
||||||
|
return self._connection.get_soundboard_sound(id)
|
||||||
|
|
||||||
def get_all_channels(self) -> Generator[GuildChannel, None, None]:
|
def get_all_channels(self) -> Generator[GuildChannel, None, None]:
|
||||||
"""A generator that retrieves every :class:`.abc.GuildChannel` the client can 'access'.
|
"""A generator that retrieves every :class:`.abc.GuildChannel` the client can 'access'.
|
||||||
|
|
||||||
@ -2964,6 +2990,26 @@ class Client:
|
|||||||
data = await self.http.get_sticker_pack(sticker_pack_id)
|
data = await self.http.get_sticker_pack(sticker_pack_id)
|
||||||
return StickerPack(state=self._connection, data=data)
|
return StickerPack(state=self._connection, data=data)
|
||||||
|
|
||||||
|
async def fetch_soundboard_default_sounds(self) -> List[SoundboardDefaultSound]:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves all default soundboard sounds.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Retrieving the default soundboard sounds failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
---------
|
||||||
|
List[:class:`.SoundboardDefaultSound`]
|
||||||
|
All default soundboard sounds.
|
||||||
|
"""
|
||||||
|
data = await self.http.get_soundboard_default_sounds()
|
||||||
|
return [SoundboardDefaultSound(state=self._connection, data=sound) for sound in data]
|
||||||
|
|
||||||
async def create_dm(self, user: Snowflake) -> DMChannel:
|
async def create_dm(self, user: Snowflake) -> DMChannel:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ __all__ = (
|
|||||||
'EntitlementType',
|
'EntitlementType',
|
||||||
'EntitlementOwnerType',
|
'EntitlementOwnerType',
|
||||||
'PollLayoutType',
|
'PollLayoutType',
|
||||||
|
'VoiceChannelEffectAnimationType',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -377,6 +378,9 @@ class AuditLogAction(Enum):
|
|||||||
thread_update = 111
|
thread_update = 111
|
||||||
thread_delete = 112
|
thread_delete = 112
|
||||||
app_command_permission_update = 121
|
app_command_permission_update = 121
|
||||||
|
soundboard_sound_create = 130
|
||||||
|
soundboard_sound_update = 131
|
||||||
|
soundboard_sound_delete = 132
|
||||||
automod_rule_create = 140
|
automod_rule_create = 140
|
||||||
automod_rule_update = 141
|
automod_rule_update = 141
|
||||||
automod_rule_delete = 142
|
automod_rule_delete = 142
|
||||||
@ -447,6 +451,9 @@ class AuditLogAction(Enum):
|
|||||||
AuditLogAction.automod_timeout_member: None,
|
AuditLogAction.automod_timeout_member: None,
|
||||||
AuditLogAction.creator_monetization_request_created: None,
|
AuditLogAction.creator_monetization_request_created: None,
|
||||||
AuditLogAction.creator_monetization_terms_accepted: None,
|
AuditLogAction.creator_monetization_terms_accepted: None,
|
||||||
|
AuditLogAction.soundboard_sound_create: AuditLogActionCategory.create,
|
||||||
|
AuditLogAction.soundboard_sound_update: AuditLogActionCategory.update,
|
||||||
|
AuditLogAction.soundboard_sound_delete: AuditLogActionCategory.delete,
|
||||||
}
|
}
|
||||||
# fmt: on
|
# fmt: on
|
||||||
return lookup[self]
|
return lookup[self]
|
||||||
@ -835,6 +842,11 @@ class ReactionType(Enum):
|
|||||||
burst = 1
|
burst = 1
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceChannelEffectAnimationType(Enum):
|
||||||
|
premium = 0
|
||||||
|
basic = 1
|
||||||
|
|
||||||
|
|
||||||
def create_unknown_value(cls: Type[E], val: Any) -> E:
|
def create_unknown_value(cls: Type[E], val: Any) -> E:
|
||||||
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
|
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
|
||||||
name = f'unknown_{val}'
|
name = f'unknown_{val}'
|
||||||
|
@ -871,34 +871,52 @@ class Intents(BaseFlags):
|
|||||||
|
|
||||||
@alias_flag_value
|
@alias_flag_value
|
||||||
def emojis(self):
|
def emojis(self):
|
||||||
""":class:`bool`: Alias of :attr:`.emojis_and_stickers`.
|
""":class:`bool`: Alias of :attr:`.expressions`.
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
.. versionchanged:: 2.0
|
||||||
Changed to an alias.
|
Changed to an alias.
|
||||||
"""
|
"""
|
||||||
return 1 << 3
|
return 1 << 3
|
||||||
|
|
||||||
@flag_value
|
@alias_flag_value
|
||||||
def emojis_and_stickers(self):
|
def emojis_and_stickers(self):
|
||||||
""":class:`bool`: Whether guild emoji and sticker related events are enabled.
|
""":class:`bool`: Alias of :attr:`.expressions`.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. versionchanged:: 2.5
|
||||||
|
Changed to an alias.
|
||||||
|
"""
|
||||||
|
return 1 << 3
|
||||||
|
|
||||||
|
@flag_value
|
||||||
|
def expressions(self):
|
||||||
|
""":class:`bool`: Whether guild emoji, sticker, and soundboard sound related events are enabled.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
This corresponds to the following events:
|
This corresponds to the following events:
|
||||||
|
|
||||||
- :func:`on_guild_emojis_update`
|
- :func:`on_guild_emojis_update`
|
||||||
- :func:`on_guild_stickers_update`
|
- :func:`on_guild_stickers_update`
|
||||||
|
- :func:`on_soundboard_sound_create`
|
||||||
|
- :func:`on_soundboard_sound_update`
|
||||||
|
- :func:`on_soundboard_sound_delete`
|
||||||
|
|
||||||
This also corresponds to the following attributes and classes in terms of cache:
|
This also corresponds to the following attributes and classes in terms of cache:
|
||||||
|
|
||||||
- :class:`Emoji`
|
- :class:`Emoji`
|
||||||
- :class:`GuildSticker`
|
- :class:`GuildSticker`
|
||||||
|
- :class:`SoundboardSound`
|
||||||
- :meth:`Client.get_emoji`
|
- :meth:`Client.get_emoji`
|
||||||
- :meth:`Client.get_sticker`
|
- :meth:`Client.get_sticker`
|
||||||
|
- :meth:`Client.get_soundboard_sound`
|
||||||
- :meth:`Client.emojis`
|
- :meth:`Client.emojis`
|
||||||
- :meth:`Client.stickers`
|
- :meth:`Client.stickers`
|
||||||
|
- :meth:`Client.soundboard_sounds`
|
||||||
- :attr:`Guild.emojis`
|
- :attr:`Guild.emojis`
|
||||||
- :attr:`Guild.stickers`
|
- :attr:`Guild.stickers`
|
||||||
|
- :attr:`Guild.soundboard_sounds`
|
||||||
"""
|
"""
|
||||||
return 1 << 3
|
return 1 << 3
|
||||||
|
|
||||||
|
172
discord/guild.py
172
discord/guild.py
@ -94,6 +94,7 @@ from .object import OLDEST_OBJECT, Object
|
|||||||
from .welcome_screen import WelcomeScreen, WelcomeChannel
|
from .welcome_screen import WelcomeScreen, WelcomeChannel
|
||||||
from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction
|
from .automod import AutoModRule, AutoModTrigger, AutoModRuleAction
|
||||||
from .partial_emoji import _EmojiTag, PartialEmoji
|
from .partial_emoji import _EmojiTag, PartialEmoji
|
||||||
|
from .soundboard import SoundboardSound
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -328,6 +329,7 @@ class Guild(Hashable):
|
|||||||
'_safety_alerts_channel_id',
|
'_safety_alerts_channel_id',
|
||||||
'max_stage_video_users',
|
'max_stage_video_users',
|
||||||
'_incidents_data',
|
'_incidents_data',
|
||||||
|
'_soundboard_sounds',
|
||||||
)
|
)
|
||||||
|
|
||||||
_PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = {
|
_PREMIUM_GUILD_LIMITS: ClassVar[Dict[Optional[int], _GuildLimit]] = {
|
||||||
@ -345,6 +347,7 @@ class Guild(Hashable):
|
|||||||
self._threads: Dict[int, Thread] = {}
|
self._threads: Dict[int, Thread] = {}
|
||||||
self._stage_instances: Dict[int, StageInstance] = {}
|
self._stage_instances: Dict[int, StageInstance] = {}
|
||||||
self._scheduled_events: Dict[int, ScheduledEvent] = {}
|
self._scheduled_events: Dict[int, ScheduledEvent] = {}
|
||||||
|
self._soundboard_sounds: Dict[int, SoundboardSound] = {}
|
||||||
self._state: ConnectionState = state
|
self._state: ConnectionState = state
|
||||||
self._member_count: Optional[int] = None
|
self._member_count: Optional[int] = None
|
||||||
self._from_data(data)
|
self._from_data(data)
|
||||||
@ -390,6 +393,12 @@ class Guild(Hashable):
|
|||||||
del self._threads[k]
|
del self._threads[k]
|
||||||
return to_remove
|
return to_remove
|
||||||
|
|
||||||
|
def _add_soundboard_sound(self, sound: SoundboardSound, /) -> None:
|
||||||
|
self._soundboard_sounds[sound.id] = sound
|
||||||
|
|
||||||
|
def _remove_soundboard_sound(self, sound: SoundboardSound, /) -> None:
|
||||||
|
self._soundboard_sounds.pop(sound.id, None)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name or ''
|
return self.name or ''
|
||||||
|
|
||||||
@ -547,6 +556,11 @@ class Guild(Hashable):
|
|||||||
scheduled_event = ScheduledEvent(data=s, state=self._state)
|
scheduled_event = ScheduledEvent(data=s, state=self._state)
|
||||||
self._scheduled_events[scheduled_event.id] = scheduled_event
|
self._scheduled_events[scheduled_event.id] = scheduled_event
|
||||||
|
|
||||||
|
if 'soundboard_sounds' in guild:
|
||||||
|
for s in guild['soundboard_sounds']:
|
||||||
|
soundboard_sound = SoundboardSound(guild=self, data=s, state=self._state)
|
||||||
|
self._add_soundboard_sound(soundboard_sound)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def channels(self) -> Sequence[GuildChannel]:
|
def channels(self) -> Sequence[GuildChannel]:
|
||||||
"""Sequence[:class:`abc.GuildChannel`]: A list of channels that belongs to this guild."""
|
"""Sequence[:class:`abc.GuildChannel`]: A list of channels that belongs to this guild."""
|
||||||
@ -996,6 +1010,37 @@ class Guild(Hashable):
|
|||||||
"""
|
"""
|
||||||
return self._scheduled_events.get(scheduled_event_id)
|
return self._scheduled_events.get(scheduled_event_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def soundboard_sounds(self) -> Sequence[SoundboardSound]:
|
||||||
|
"""Sequence[:class:`SoundboardSound`]: Returns a sequence of the guild's soundboard sounds.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
"""
|
||||||
|
return utils.SequenceProxy(self._soundboard_sounds.values())
|
||||||
|
|
||||||
|
def get_soundboard_sound(self, sound_id: int, /) -> Optional[SoundboardSound]:
|
||||||
|
"""Returns a soundboard sound with the given ID.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
sound_id: :class:`int`
|
||||||
|
The ID to search for.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
Optional[:class:`SoundboardSound`]
|
||||||
|
The soundboard sound or ``None`` if not found.
|
||||||
|
"""
|
||||||
|
return self._soundboard_sounds.get(sound_id)
|
||||||
|
|
||||||
|
def _resolve_soundboard_sound(self, id: Optional[int], /) -> Optional[SoundboardSound]:
|
||||||
|
if id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
return self._soundboard_sounds.get(id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def owner(self) -> Optional[Member]:
|
def owner(self) -> Optional[Member]:
|
||||||
"""Optional[:class:`Member`]: The member that owns the guild."""
|
"""Optional[:class:`Member`]: The member that owns the guild."""
|
||||||
@ -4496,3 +4541,130 @@ class Guild(Hashable):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return self.raid_detected_at > utils.utcnow()
|
return self.raid_detected_at > utils.utcnow()
|
||||||
|
|
||||||
|
async def fetch_soundboard_sound(self, sound_id: int, /) -> SoundboardSound:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves a :class:`SoundboardSound` with the specified ID.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Using this, in order to receive :attr:`SoundboardSound.user`, you must have :attr:`~Permissions.create_expressions`
|
||||||
|
or :attr:`~Permissions.manage_expressions`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This method is an API call. For general usage, consider :attr:`get_soundboard_sound` instead.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
NotFound
|
||||||
|
The sound requested could not be found.
|
||||||
|
HTTPException
|
||||||
|
Retrieving the sound failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
:class:`SoundboardSound`
|
||||||
|
The retrieved sound.
|
||||||
|
"""
|
||||||
|
data = await self._state.http.get_soundboard_sound(self.id, sound_id)
|
||||||
|
return SoundboardSound(guild=self, state=self._state, data=data)
|
||||||
|
|
||||||
|
async def fetch_soundboard_sounds(self) -> List[SoundboardSound]:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Retrieves a list of all soundboard sounds for the guild.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Using this, in order to receive :attr:`SoundboardSound.user`, you must have :attr:`~Permissions.create_expressions`
|
||||||
|
or :attr:`~Permissions.manage_expressions`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This method is an API call. For general usage, consider :attr:`soundboard_sounds` instead.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
HTTPException
|
||||||
|
Retrieving the sounds failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
--------
|
||||||
|
List[:class:`SoundboardSound`]
|
||||||
|
The retrieved soundboard sounds.
|
||||||
|
"""
|
||||||
|
data = await self._state.http.get_soundboard_sounds(self.id)
|
||||||
|
return [SoundboardSound(guild=self, state=self._state, data=sound) for sound in data['items']]
|
||||||
|
|
||||||
|
async def create_soundboard_sound(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
name: str,
|
||||||
|
sound: bytes,
|
||||||
|
volume: float = 1,
|
||||||
|
emoji: Optional[EmojiInputType] = None,
|
||||||
|
reason: Optional[str] = None,
|
||||||
|
) -> SoundboardSound:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Creates a :class:`SoundboardSound` for the guild.
|
||||||
|
You must have :attr:`Permissions.create_expressions` to do this.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: :class:`str`
|
||||||
|
The name of the sound. Must be between 2 and 32 characters.
|
||||||
|
sound: :class:`bytes`
|
||||||
|
The :term:`py:bytes-like object` representing the sound data.
|
||||||
|
Only MP3 and OGG sound files that don't exceed the duration of 5.2s are supported.
|
||||||
|
volume: :class:`float`
|
||||||
|
The volume of the sound. Must be between 0 and 1. Defaults to ``1``.
|
||||||
|
emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]]
|
||||||
|
The emoji of the sound.
|
||||||
|
reason: Optional[:class:`str`]
|
||||||
|
The reason for creating the sound. Shows up on the audit log.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to create a soundboard sound.
|
||||||
|
HTTPException
|
||||||
|
Creating the soundboard sound failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`SoundboardSound`
|
||||||
|
The newly created soundboard sound.
|
||||||
|
"""
|
||||||
|
payload: Dict[str, Any] = {
|
||||||
|
'name': name,
|
||||||
|
'sound': utils._bytes_to_base64_data(sound, audio=True),
|
||||||
|
'volume': volume,
|
||||||
|
'emoji_id': None,
|
||||||
|
'emoji_name': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
if emoji is not None:
|
||||||
|
if isinstance(emoji, _EmojiTag):
|
||||||
|
partial_emoji = emoji._to_partial()
|
||||||
|
elif isinstance(emoji, str):
|
||||||
|
partial_emoji = PartialEmoji.from_str(emoji)
|
||||||
|
else:
|
||||||
|
partial_emoji = None
|
||||||
|
|
||||||
|
if partial_emoji is not None:
|
||||||
|
if partial_emoji.id is None:
|
||||||
|
payload['emoji_name'] = partial_emoji.name
|
||||||
|
else:
|
||||||
|
payload['emoji_id'] = partial_emoji.id
|
||||||
|
|
||||||
|
data = await self._state.http.create_soundboard_sound(self.id, reason=reason, **payload)
|
||||||
|
return SoundboardSound(guild=self, state=self._state, data=data)
|
||||||
|
@ -93,6 +93,7 @@ if TYPE_CHECKING:
|
|||||||
sku,
|
sku,
|
||||||
poll,
|
poll,
|
||||||
voice,
|
voice,
|
||||||
|
soundboard,
|
||||||
)
|
)
|
||||||
from .types.snowflake import Snowflake, SnowflakeList
|
from .types.snowflake import Snowflake, SnowflakeList
|
||||||
|
|
||||||
@ -2515,6 +2516,78 @@ class HTTPClient:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Soundboard
|
||||||
|
|
||||||
|
def get_soundboard_default_sounds(self) -> Response[List[soundboard.SoundboardDefaultSound]]:
|
||||||
|
return self.request(Route('GET', '/soundboard-default-sounds'))
|
||||||
|
|
||||||
|
def get_soundboard_sound(self, guild_id: Snowflake, sound_id: Snowflake) -> Response[soundboard.SoundboardSound]:
|
||||||
|
return self.request(
|
||||||
|
Route('GET', '/guilds/{guild_id}/soundboard-sounds/{sound_id}', guild_id=guild_id, sound_id=sound_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_soundboard_sounds(self, guild_id: Snowflake) -> Response[Dict[str, List[soundboard.SoundboardSound]]]:
|
||||||
|
return self.request(Route('GET', '/guilds/{guild_id}/soundboard-sounds', guild_id=guild_id))
|
||||||
|
|
||||||
|
def create_soundboard_sound(
|
||||||
|
self, guild_id: Snowflake, *, reason: Optional[str], **payload: Any
|
||||||
|
) -> Response[soundboard.SoundboardSound]:
|
||||||
|
valid_keys = (
|
||||||
|
'name',
|
||||||
|
'sound',
|
||||||
|
'volume',
|
||||||
|
'emoji_id',
|
||||||
|
'emoji_name',
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = {k: v for k, v in payload.items() if k in valid_keys and v is not None}
|
||||||
|
|
||||||
|
return self.request(
|
||||||
|
Route('POST', '/guilds/{guild_id}/soundboard-sounds', guild_id=guild_id), json=payload, reason=reason
|
||||||
|
)
|
||||||
|
|
||||||
|
def edit_soundboard_sound(
|
||||||
|
self, guild_id: Snowflake, sound_id: Snowflake, *, reason: Optional[str], **payload: Any
|
||||||
|
) -> Response[soundboard.SoundboardSound]:
|
||||||
|
valid_keys = (
|
||||||
|
'name',
|
||||||
|
'volume',
|
||||||
|
'emoji_id',
|
||||||
|
'emoji_name',
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = {k: v for k, v in payload.items() if k in valid_keys}
|
||||||
|
|
||||||
|
return self.request(
|
||||||
|
Route(
|
||||||
|
'PATCH',
|
||||||
|
'/guilds/{guild_id}/soundboard-sounds/{sound_id}',
|
||||||
|
guild_id=guild_id,
|
||||||
|
sound_id=sound_id,
|
||||||
|
),
|
||||||
|
json=payload,
|
||||||
|
reason=reason,
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete_soundboard_sound(self, guild_id: Snowflake, sound_id: Snowflake, *, reason: Optional[str]) -> Response[None]:
|
||||||
|
return self.request(
|
||||||
|
Route(
|
||||||
|
'DELETE',
|
||||||
|
'/guilds/{guild_id}/soundboard-sounds/{sound_id}',
|
||||||
|
guild_id=guild_id,
|
||||||
|
sound_id=sound_id,
|
||||||
|
),
|
||||||
|
reason=reason,
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_soundboard_sound(self, channel_id: Snowflake, **payload: Any) -> Response[None]:
|
||||||
|
valid_keys = ('sound_id', 'source_guild_id')
|
||||||
|
payload = {k: v for k, v in payload.items() if k in valid_keys}
|
||||||
|
print(payload)
|
||||||
|
return self.request(
|
||||||
|
(Route('POST', '/channels/{channel_id}/send-soundboard-sound', channel_id=channel_id)), json=payload
|
||||||
|
)
|
||||||
|
|
||||||
# Application
|
# Application
|
||||||
|
|
||||||
def application_info(self) -> Response[appinfo.AppInfo]:
|
def application_info(self) -> Response[appinfo.AppInfo]:
|
||||||
|
325
discord/soundboard.py
Normal file
325
discord/soundboard.py
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
"""
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
from .mixins import Hashable
|
||||||
|
from .partial_emoji import PartialEmoji, _EmojiTag
|
||||||
|
from .user import User
|
||||||
|
from .utils import MISSING
|
||||||
|
from .asset import Asset, AssetMixin
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import datetime
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
from .types.soundboard import (
|
||||||
|
BaseSoundboardSound as BaseSoundboardSoundPayload,
|
||||||
|
SoundboardDefaultSound as SoundboardDefaultSoundPayload,
|
||||||
|
SoundboardSound as SoundboardSoundPayload,
|
||||||
|
)
|
||||||
|
from .state import ConnectionState
|
||||||
|
from .guild import Guild
|
||||||
|
from .message import EmojiInputType
|
||||||
|
|
||||||
|
__all__ = ('BaseSoundboardSound', 'SoundboardDefaultSound', 'SoundboardSound')
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSoundboardSound(Hashable, AssetMixin):
|
||||||
|
"""Represents a generic Discord soundboard sound.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if two sounds are equal.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if two sounds are not equal.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the sound'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', 'id', 'volume')
|
||||||
|
|
||||||
|
def __init__(self, *, state: ConnectionState, data: BaseSoundboardSoundPayload):
|
||||||
|
self._state: ConnectionState = state
|
||||||
|
self.id: int = int(data['sound_id'])
|
||||||
|
self._update(data)
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self.id == other.id
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
def __ne__(self, other: object) -> bool:
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def _update(self, data: BaseSoundboardSoundPayload):
|
||||||
|
self.volume: float = data['volume']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> str:
|
||||||
|
""":class:`str`: Returns the URL of the sound."""
|
||||||
|
return f'{Asset.BASE}/soundboard-sounds/{self.id}'
|
||||||
|
|
||||||
|
|
||||||
|
class SoundboardDefaultSound(BaseSoundboardSound):
|
||||||
|
"""Represents a Discord soundboard default sound.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if two sounds are equal.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if two sounds are not equal.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the sound'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%).
|
||||||
|
name: :class:`str`
|
||||||
|
The name of the sound.
|
||||||
|
emoji: :class:`PartialEmoji`
|
||||||
|
The emoji of the sound.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('name', 'emoji')
|
||||||
|
|
||||||
|
def __init__(self, *, state: ConnectionState, data: SoundboardDefaultSoundPayload):
|
||||||
|
self.name: str = data['name']
|
||||||
|
self.emoji: PartialEmoji = PartialEmoji(name=data['emoji_name'])
|
||||||
|
super().__init__(state=state, data=data)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
attrs = [
|
||||||
|
('id', self.id),
|
||||||
|
('name', self.name),
|
||||||
|
('volume', self.volume),
|
||||||
|
('emoji', self.emoji),
|
||||||
|
]
|
||||||
|
inner = ' '.join('%s=%r' % t for t in attrs)
|
||||||
|
return f"<{self.__class__.__name__} {inner}>"
|
||||||
|
|
||||||
|
|
||||||
|
class SoundboardSound(BaseSoundboardSound):
|
||||||
|
"""Represents a Discord soundboard sound.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. container:: operations
|
||||||
|
|
||||||
|
.. describe:: x == y
|
||||||
|
|
||||||
|
Checks if two sounds are equal.
|
||||||
|
|
||||||
|
.. describe:: x != y
|
||||||
|
|
||||||
|
Checks if two sounds are not equal.
|
||||||
|
|
||||||
|
.. describe:: hash(x)
|
||||||
|
|
||||||
|
Returns the sound'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%).
|
||||||
|
name: :class:`str`
|
||||||
|
The name of the sound.
|
||||||
|
emoji: Optional[:class:`PartialEmoji`]
|
||||||
|
The emoji of the sound. ``None`` if no emoji is set.
|
||||||
|
guild: :class:`Guild`
|
||||||
|
The guild in which the sound is uploaded.
|
||||||
|
available: :class:`bool`
|
||||||
|
Whether this sound is available for use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ('_state', 'name', 'emoji', '_user', 'available', '_user_id', 'guild')
|
||||||
|
|
||||||
|
def __init__(self, *, guild: Guild, state: ConnectionState, data: SoundboardSoundPayload):
|
||||||
|
super().__init__(state=state, data=data)
|
||||||
|
self.guild = guild
|
||||||
|
self._user_id = utils._get_as_snowflake(data, 'user_id')
|
||||||
|
self._user = data.get('user')
|
||||||
|
|
||||||
|
self._update(data)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
attrs = [
|
||||||
|
('id', self.id),
|
||||||
|
('name', self.name),
|
||||||
|
('volume', self.volume),
|
||||||
|
('emoji', self.emoji),
|
||||||
|
('user', self.user),
|
||||||
|
]
|
||||||
|
inner = ' '.join('%s=%r' % t for t in attrs)
|
||||||
|
return f"<{self.__class__.__name__} {inner}>"
|
||||||
|
|
||||||
|
def _update(self, data: SoundboardSoundPayload):
|
||||||
|
super()._update(data)
|
||||||
|
|
||||||
|
self.name: str = data['name']
|
||||||
|
self.emoji: Optional[PartialEmoji] = None
|
||||||
|
|
||||||
|
emoji_id = utils._get_as_snowflake(data, 'emoji_id')
|
||||||
|
emoji_name = data['emoji_name']
|
||||||
|
if emoji_id is not None or emoji_name is not None:
|
||||||
|
self.emoji = PartialEmoji(id=emoji_id, name=emoji_name) # type: ignore # emoji_name cannot be None here
|
||||||
|
|
||||||
|
self.available: bool = data['available']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_at(self) -> datetime.datetime:
|
||||||
|
""":class:`datetime.datetime`: Returns the snowflake's creation time in UTC."""
|
||||||
|
return utils.snowflake_time(self.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user(self) -> Optional[User]:
|
||||||
|
"""Optional[:class:`User`]: The user who uploaded the sound."""
|
||||||
|
if self._user is None:
|
||||||
|
if self._user_id is None:
|
||||||
|
return None
|
||||||
|
return self._state.get_user(self._user_id)
|
||||||
|
return User(state=self._state, data=self._user)
|
||||||
|
|
||||||
|
async def edit(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
name: str = MISSING,
|
||||||
|
volume: Optional[float] = MISSING,
|
||||||
|
emoji: Optional[EmojiInputType] = MISSING,
|
||||||
|
reason: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Edits the soundboard sound.
|
||||||
|
|
||||||
|
You must have :attr:`~Permissions.manage_expressions` to edit the sound.
|
||||||
|
If the sound was created by the client, you must have either :attr:`~Permissions.manage_expressions`
|
||||||
|
or :attr:`~Permissions.create_expressions`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
name: :class:`str`
|
||||||
|
The new name of the sound. Must be between 2 and 32 characters.
|
||||||
|
volume: Optional[:class:`float`]
|
||||||
|
The new volume of the sound. Must be between 0 and 1.
|
||||||
|
emoji: Optional[Union[:class:`Emoji`, :class:`PartialEmoji`, :class:`str`]]
|
||||||
|
The new emoji of the sound.
|
||||||
|
reason: Optional[:class:`str`]
|
||||||
|
The reason for editing this sound. Shows up on the audit log.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to edit the soundboard sound.
|
||||||
|
HTTPException
|
||||||
|
Editing the soundboard sound failed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
:class:`SoundboardSound`
|
||||||
|
The newly updated soundboard sound.
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
if name is not MISSING:
|
||||||
|
payload['name'] = name
|
||||||
|
|
||||||
|
if volume is not MISSING:
|
||||||
|
payload['volume'] = volume
|
||||||
|
|
||||||
|
if emoji is not MISSING:
|
||||||
|
if emoji is None:
|
||||||
|
payload['emoji_id'] = None
|
||||||
|
payload['emoji_name'] = None
|
||||||
|
else:
|
||||||
|
if isinstance(emoji, _EmojiTag):
|
||||||
|
partial_emoji = emoji._to_partial()
|
||||||
|
elif isinstance(emoji, str):
|
||||||
|
partial_emoji = PartialEmoji.from_str(emoji)
|
||||||
|
else:
|
||||||
|
partial_emoji = None
|
||||||
|
|
||||||
|
if partial_emoji is not None:
|
||||||
|
if partial_emoji.id is None:
|
||||||
|
payload['emoji_name'] = partial_emoji.name
|
||||||
|
else:
|
||||||
|
payload['emoji_id'] = partial_emoji.id
|
||||||
|
|
||||||
|
data = await self._state.http.edit_soundboard_sound(self.guild.id, self.id, reason=reason, **payload)
|
||||||
|
return SoundboardSound(guild=self.guild, state=self._state, data=data)
|
||||||
|
|
||||||
|
async def delete(self, *, reason: Optional[str] = None) -> None:
|
||||||
|
"""|coro|
|
||||||
|
|
||||||
|
Deletes the soundboard sound.
|
||||||
|
|
||||||
|
You must have :attr:`~Permissions.manage_expressions` to delete the sound.
|
||||||
|
If the sound was created by the client, you must have either :attr:`~Permissions.manage_expressions`
|
||||||
|
or :attr:`~Permissions.create_expressions`.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
-----------
|
||||||
|
reason: Optional[:class:`str`]
|
||||||
|
The reason for deleting this sound. Shows up on the audit log.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
Forbidden
|
||||||
|
You do not have permissions to delete the soundboard sound.
|
||||||
|
HTTPException
|
||||||
|
Deleting the soundboard sound failed.
|
||||||
|
"""
|
||||||
|
await self._state.http.delete_soundboard_sound(self.guild.id, self.id, reason=reason)
|
@ -78,6 +78,7 @@ from .sticker import GuildSticker
|
|||||||
from .automod import AutoModRule, AutoModAction
|
from .automod import AutoModRule, AutoModAction
|
||||||
from .audit_logs import AuditLogEntry
|
from .audit_logs import AuditLogEntry
|
||||||
from ._types import ClientT
|
from ._types import ClientT
|
||||||
|
from .soundboard import SoundboardSound
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .abc import PrivateChannel
|
from .abc import PrivateChannel
|
||||||
@ -455,6 +456,14 @@ class ConnectionState(Generic[ClientT]):
|
|||||||
def stickers(self) -> Sequence[GuildSticker]:
|
def stickers(self) -> Sequence[GuildSticker]:
|
||||||
return utils.SequenceProxy(self._stickers.values())
|
return utils.SequenceProxy(self._stickers.values())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def soundboard_sounds(self) -> List[SoundboardSound]:
|
||||||
|
all_sounds = []
|
||||||
|
for guild in self.guilds:
|
||||||
|
all_sounds.extend(guild.soundboard_sounds)
|
||||||
|
|
||||||
|
return all_sounds
|
||||||
|
|
||||||
def get_emoji(self, emoji_id: Optional[int]) -> Optional[Emoji]:
|
def get_emoji(self, emoji_id: Optional[int]) -> Optional[Emoji]:
|
||||||
# the keys of self._emojis are ints
|
# the keys of self._emojis are ints
|
||||||
return self._emojis.get(emoji_id) # type: ignore
|
return self._emojis.get(emoji_id) # type: ignore
|
||||||
@ -1555,6 +1564,62 @@ class ConnectionState(Generic[ClientT]):
|
|||||||
else:
|
else:
|
||||||
_log.debug('SCHEDULED_EVENT_USER_REMOVE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
|
_log.debug('SCHEDULED_EVENT_USER_REMOVE referencing unknown guild ID: %s. Discarding.', data['guild_id'])
|
||||||
|
|
||||||
|
def parse_guild_soundboard_sound_create(self, data: gw.GuildSoundBoardSoundCreateEvent) -> None:
|
||||||
|
guild_id = int(data['guild_id']) # type: ignore # can't be None here
|
||||||
|
guild = self._get_guild(guild_id)
|
||||||
|
if guild is not None:
|
||||||
|
sound = SoundboardSound(guild=guild, state=self, data=data)
|
||||||
|
guild._add_soundboard_sound(sound)
|
||||||
|
self.dispatch('soundboard_sound_create', sound)
|
||||||
|
else:
|
||||||
|
_log.debug('GUILD_SOUNDBOARD_SOUND_CREATE referencing unknown guild ID: %s. Discarding.', guild_id)
|
||||||
|
|
||||||
|
def _update_and_dispatch_sound_update(self, sound: SoundboardSound, data: gw.GuildSoundBoardSoundUpdateEvent):
|
||||||
|
old_sound = copy.copy(sound)
|
||||||
|
sound._update(data)
|
||||||
|
self.dispatch('soundboard_sound_update', old_sound, sound)
|
||||||
|
|
||||||
|
def parse_guild_soundboard_sound_update(self, data: gw.GuildSoundBoardSoundUpdateEvent) -> None:
|
||||||
|
guild_id = int(data['guild_id']) # type: ignore # can't be None here
|
||||||
|
guild = self._get_guild(guild_id)
|
||||||
|
if guild is not None:
|
||||||
|
sound_id = int(data['sound_id'])
|
||||||
|
sound = guild.get_soundboard_sound(sound_id)
|
||||||
|
if sound is not None:
|
||||||
|
self._update_and_dispatch_sound_update(sound, data)
|
||||||
|
else:
|
||||||
|
_log.warning('GUILD_SOUNDBOARD_SOUND_UPDATE referencing unknown sound ID: %s. Discarding.', sound_id)
|
||||||
|
else:
|
||||||
|
_log.debug('GUILD_SOUNDBOARD_SOUND_UPDATE referencing unknown guild ID: %s. Discarding.', guild_id)
|
||||||
|
|
||||||
|
def parse_guild_soundboard_sound_delete(self, data: gw.GuildSoundBoardSoundDeleteEvent) -> None:
|
||||||
|
guild_id = int(data['guild_id'])
|
||||||
|
guild = self._get_guild(guild_id)
|
||||||
|
if guild is not None:
|
||||||
|
sound_id = int(data['sound_id'])
|
||||||
|
sound = guild.get_soundboard_sound(sound_id)
|
||||||
|
if sound is not None:
|
||||||
|
guild._remove_soundboard_sound(sound)
|
||||||
|
self.dispatch('soundboard_sound_delete', sound)
|
||||||
|
else:
|
||||||
|
_log.warning('GUILD_SOUNDBOARD_SOUND_DELETE referencing unknown sound ID: %s. Discarding.', sound_id)
|
||||||
|
else:
|
||||||
|
_log.debug('GUILD_SOUNDBOARD_SOUND_DELETE referencing unknown guild ID: %s. Discarding.', guild_id)
|
||||||
|
|
||||||
|
def parse_guild_soundboard_sounds_update(self, data: gw.GuildSoundBoardSoundsUpdateEvent) -> None:
|
||||||
|
for raw_sound in data:
|
||||||
|
guild_id = int(raw_sound['guild_id']) # type: ignore # can't be None here
|
||||||
|
guild = self._get_guild(guild_id)
|
||||||
|
if guild is not None:
|
||||||
|
sound_id = int(raw_sound['sound_id'])
|
||||||
|
sound = guild.get_soundboard_sound(sound_id)
|
||||||
|
if sound is not None:
|
||||||
|
self._update_and_dispatch_sound_update(sound, raw_sound)
|
||||||
|
else:
|
||||||
|
_log.warning('GUILD_SOUNDBOARD_SOUNDS_UPDATE referencing unknown sound ID: %s. Discarding.', sound_id)
|
||||||
|
else:
|
||||||
|
_log.debug('GUILD_SOUNDBOARD_SOUNDS_UPDATE referencing unknown guild ID: %s. Discarding.', guild_id)
|
||||||
|
|
||||||
def parse_application_command_permissions_update(self, data: GuildApplicationCommandPermissionsPayload):
|
def parse_application_command_permissions_update(self, data: GuildApplicationCommandPermissionsPayload):
|
||||||
raw = RawAppCommandPermissionsUpdateEvent(data=data, state=self)
|
raw = RawAppCommandPermissionsUpdateEvent(data=data, state=self)
|
||||||
self.dispatch('raw_app_command_permissions_update', raw)
|
self.dispatch('raw_app_command_permissions_update', raw)
|
||||||
@ -1585,6 +1650,14 @@ class ConnectionState(Generic[ClientT]):
|
|||||||
else:
|
else:
|
||||||
_log.debug('VOICE_STATE_UPDATE referencing an unknown member ID: %s. Discarding.', data['user_id'])
|
_log.debug('VOICE_STATE_UPDATE referencing an unknown member ID: %s. Discarding.', data['user_id'])
|
||||||
|
|
||||||
|
def parse_voice_channel_effect_send(self, data: gw.VoiceChannelEffectSendEvent):
|
||||||
|
guild = self._get_guild(int(data['guild_id']))
|
||||||
|
if guild is not None:
|
||||||
|
effect = VoiceChannelEffect(state=self, data=data, guild=guild)
|
||||||
|
self.dispatch('voice_channel_effect', effect)
|
||||||
|
else:
|
||||||
|
_log.debug('VOICE_CHANNEL_EFFECT_SEND referencing an unknown guild ID: %s. Discarding.', data['guild_id'])
|
||||||
|
|
||||||
def parse_voice_server_update(self, data: gw.VoiceServerUpdateEvent) -> None:
|
def parse_voice_server_update(self, data: gw.VoiceServerUpdateEvent) -> None:
|
||||||
key_id = int(data['guild_id'])
|
key_id = int(data['guild_id'])
|
||||||
|
|
||||||
@ -1707,6 +1780,15 @@ class ConnectionState(Generic[ClientT]):
|
|||||||
def create_message(self, *, channel: MessageableChannel, data: MessagePayload) -> Message:
|
def create_message(self, *, channel: MessageableChannel, data: MessagePayload) -> Message:
|
||||||
return Message(state=self, channel=channel, data=data)
|
return Message(state=self, channel=channel, data=data)
|
||||||
|
|
||||||
|
def get_soundboard_sound(self, id: Optional[int]) -> Optional[SoundboardSound]:
|
||||||
|
if id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for guild in self.guilds:
|
||||||
|
sound = guild._resolve_soundboard_sound(id)
|
||||||
|
if sound is not None:
|
||||||
|
return sound
|
||||||
|
|
||||||
|
|
||||||
class AutoShardedConnectionState(ConnectionState[ClientT]):
|
class AutoShardedConnectionState(ConnectionState[ClientT]):
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
@ -88,6 +88,9 @@ AuditLogEvent = Literal[
|
|||||||
111,
|
111,
|
||||||
112,
|
112,
|
||||||
121,
|
121,
|
||||||
|
130,
|
||||||
|
131,
|
||||||
|
132,
|
||||||
140,
|
140,
|
||||||
141,
|
141,
|
||||||
142,
|
142,
|
||||||
@ -112,6 +115,7 @@ class _AuditLogChange_Str(TypedDict):
|
|||||||
'permissions',
|
'permissions',
|
||||||
'tags',
|
'tags',
|
||||||
'unicode_emoji',
|
'unicode_emoji',
|
||||||
|
'emoji_name',
|
||||||
]
|
]
|
||||||
new_value: str
|
new_value: str
|
||||||
old_value: str
|
old_value: str
|
||||||
@ -136,6 +140,8 @@ class _AuditLogChange_Snowflake(TypedDict):
|
|||||||
'channel_id',
|
'channel_id',
|
||||||
'inviter_id',
|
'inviter_id',
|
||||||
'guild_id',
|
'guild_id',
|
||||||
|
'user_id',
|
||||||
|
'sound_id',
|
||||||
]
|
]
|
||||||
new_value: Snowflake
|
new_value: Snowflake
|
||||||
old_value: Snowflake
|
old_value: Snowflake
|
||||||
@ -183,6 +189,12 @@ class _AuditLogChange_Int(TypedDict):
|
|||||||
old_value: int
|
old_value: int
|
||||||
|
|
||||||
|
|
||||||
|
class _AuditLogChange_Float(TypedDict):
|
||||||
|
key: Literal['volume']
|
||||||
|
new_value: float
|
||||||
|
old_value: float
|
||||||
|
|
||||||
|
|
||||||
class _AuditLogChange_ListRole(TypedDict):
|
class _AuditLogChange_ListRole(TypedDict):
|
||||||
key: Literal['$add', '$remove']
|
key: Literal['$add', '$remove']
|
||||||
new_value: List[Role]
|
new_value: List[Role]
|
||||||
@ -290,6 +302,7 @@ AuditLogChange = Union[
|
|||||||
_AuditLogChange_AssetHash,
|
_AuditLogChange_AssetHash,
|
||||||
_AuditLogChange_Snowflake,
|
_AuditLogChange_Snowflake,
|
||||||
_AuditLogChange_Int,
|
_AuditLogChange_Int,
|
||||||
|
_AuditLogChange_Float,
|
||||||
_AuditLogChange_Bool,
|
_AuditLogChange_Bool,
|
||||||
_AuditLogChange_ListRole,
|
_AuditLogChange_ListRole,
|
||||||
_AuditLogChange_MFALevel,
|
_AuditLogChange_MFALevel,
|
||||||
|
@ -28,6 +28,7 @@ from typing_extensions import NotRequired
|
|||||||
from .user import PartialUser
|
from .user import PartialUser
|
||||||
from .snowflake import Snowflake
|
from .snowflake import Snowflake
|
||||||
from .threads import ThreadMetadata, ThreadMember, ThreadArchiveDuration, ThreadType
|
from .threads import ThreadMetadata, ThreadMember, ThreadArchiveDuration, ThreadType
|
||||||
|
from .emoji import PartialEmoji
|
||||||
|
|
||||||
|
|
||||||
OverwriteType = Literal[0, 1]
|
OverwriteType = Literal[0, 1]
|
||||||
@ -89,6 +90,20 @@ class VoiceChannel(_BaseTextChannel):
|
|||||||
video_quality_mode: NotRequired[VideoQualityMode]
|
video_quality_mode: NotRequired[VideoQualityMode]
|
||||||
|
|
||||||
|
|
||||||
|
VoiceChannelEffectAnimationType = Literal[0, 1]
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceChannelEffect(TypedDict):
|
||||||
|
guild_id: Snowflake
|
||||||
|
channel_id: Snowflake
|
||||||
|
user_id: Snowflake
|
||||||
|
emoji: NotRequired[Optional[PartialEmoji]]
|
||||||
|
animation_type: NotRequired[VoiceChannelEffectAnimationType]
|
||||||
|
animation_id: NotRequired[int]
|
||||||
|
sound_id: NotRequired[Union[int, str]]
|
||||||
|
sound_volume: NotRequired[float]
|
||||||
|
|
||||||
|
|
||||||
class CategoryChannel(_BaseGuildChannel):
|
class CategoryChannel(_BaseGuildChannel):
|
||||||
type: Literal[4]
|
type: Literal[4]
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional, TypedDict
|
from typing import Optional, TypedDict
|
||||||
|
from typing_extensions import NotRequired
|
||||||
from .snowflake import Snowflake, SnowflakeList
|
from .snowflake import Snowflake, SnowflakeList
|
||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ from .user import User
|
|||||||
class PartialEmoji(TypedDict):
|
class PartialEmoji(TypedDict):
|
||||||
id: Optional[Snowflake]
|
id: Optional[Snowflake]
|
||||||
name: Optional[str]
|
name: Optional[str]
|
||||||
|
animated: NotRequired[bool]
|
||||||
|
|
||||||
|
|
||||||
class Emoji(PartialEmoji, total=False):
|
class Emoji(PartialEmoji, total=False):
|
||||||
|
@ -31,7 +31,7 @@ from .sku import Entitlement
|
|||||||
from .voice import GuildVoiceState
|
from .voice import GuildVoiceState
|
||||||
from .integration import BaseIntegration, IntegrationApplication
|
from .integration import BaseIntegration, IntegrationApplication
|
||||||
from .role import Role
|
from .role import Role
|
||||||
from .channel import ChannelType, StageInstance
|
from .channel import ChannelType, StageInstance, VoiceChannelEffect
|
||||||
from .interactions import Interaction
|
from .interactions import Interaction
|
||||||
from .invite import InviteTargetType
|
from .invite import InviteTargetType
|
||||||
from .emoji import Emoji, PartialEmoji
|
from .emoji import Emoji, PartialEmoji
|
||||||
@ -45,6 +45,7 @@ from .user import User, AvatarDecorationData
|
|||||||
from .threads import Thread, ThreadMember
|
from .threads import Thread, ThreadMember
|
||||||
from .scheduled_event import GuildScheduledEvent
|
from .scheduled_event import GuildScheduledEvent
|
||||||
from .audit_log import AuditLogEntry
|
from .audit_log import AuditLogEntry
|
||||||
|
from .soundboard import SoundboardSound
|
||||||
|
|
||||||
|
|
||||||
class SessionStartLimit(TypedDict):
|
class SessionStartLimit(TypedDict):
|
||||||
@ -319,6 +320,15 @@ class _GuildScheduledEventUsersEvent(TypedDict):
|
|||||||
GuildScheduledEventUserAdd = GuildScheduledEventUserRemove = _GuildScheduledEventUsersEvent
|
GuildScheduledEventUserAdd = GuildScheduledEventUserRemove = _GuildScheduledEventUsersEvent
|
||||||
|
|
||||||
VoiceStateUpdateEvent = GuildVoiceState
|
VoiceStateUpdateEvent = GuildVoiceState
|
||||||
|
VoiceChannelEffectSendEvent = VoiceChannelEffect
|
||||||
|
|
||||||
|
GuildSoundBoardSoundCreateEvent = GuildSoundBoardSoundUpdateEvent = SoundboardSound
|
||||||
|
GuildSoundBoardSoundsUpdateEvent = List[SoundboardSound]
|
||||||
|
|
||||||
|
|
||||||
|
class GuildSoundBoardSoundDeleteEvent(TypedDict):
|
||||||
|
sound_id: Snowflake
|
||||||
|
guild_id: Snowflake
|
||||||
|
|
||||||
|
|
||||||
class VoiceServerUpdateEvent(TypedDict):
|
class VoiceServerUpdateEvent(TypedDict):
|
||||||
|
@ -37,6 +37,7 @@ from .member import Member
|
|||||||
from .emoji import Emoji
|
from .emoji import Emoji
|
||||||
from .user import User
|
from .user import User
|
||||||
from .threads import Thread
|
from .threads import Thread
|
||||||
|
from .soundboard import SoundboardSound
|
||||||
|
|
||||||
|
|
||||||
class Ban(TypedDict):
|
class Ban(TypedDict):
|
||||||
@ -90,6 +91,8 @@ GuildFeature = Literal[
|
|||||||
'VIP_REGIONS',
|
'VIP_REGIONS',
|
||||||
'WELCOME_SCREEN_ENABLED',
|
'WELCOME_SCREEN_ENABLED',
|
||||||
'RAID_ALERTS_DISABLED',
|
'RAID_ALERTS_DISABLED',
|
||||||
|
'SOUNDBOARD',
|
||||||
|
'MORE_SOUNDBOARD',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -154,6 +157,7 @@ class Guild(_BaseGuildPreview):
|
|||||||
max_members: NotRequired[int]
|
max_members: NotRequired[int]
|
||||||
premium_subscription_count: NotRequired[int]
|
premium_subscription_count: NotRequired[int]
|
||||||
max_video_channel_users: NotRequired[int]
|
max_video_channel_users: NotRequired[int]
|
||||||
|
soundboard_sounds: NotRequired[List[SoundboardSound]]
|
||||||
|
|
||||||
|
|
||||||
class InviteGuild(Guild, total=False):
|
class InviteGuild(Guild, total=False):
|
||||||
|
49
discord/types/soundboard.py
Normal file
49
discord/types/soundboard.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
"""
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015-present Rapptz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
copy of this software and associated documentation files (the "Software"),
|
||||||
|
to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import TypedDict, Optional, Union
|
||||||
|
from typing_extensions import NotRequired
|
||||||
|
|
||||||
|
from .snowflake import Snowflake
|
||||||
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSoundboardSound(TypedDict):
|
||||||
|
sound_id: Union[Snowflake, str] # basic string number when it's a default sound
|
||||||
|
volume: float
|
||||||
|
|
||||||
|
|
||||||
|
class SoundboardSound(BaseSoundboardSound):
|
||||||
|
name: str
|
||||||
|
emoji_name: Optional[str]
|
||||||
|
emoji_id: Optional[Snowflake]
|
||||||
|
user_id: NotRequired[Snowflake]
|
||||||
|
available: bool
|
||||||
|
guild_id: NotRequired[Snowflake]
|
||||||
|
user: NotRequired[User]
|
||||||
|
|
||||||
|
|
||||||
|
class SoundboardDefaultSound(BaseSoundboardSound):
|
||||||
|
name: str
|
||||||
|
emoji_name: str
|
@ -623,8 +623,18 @@ def _get_mime_type_for_image(data: bytes):
|
|||||||
raise ValueError('Unsupported image type given')
|
raise ValueError('Unsupported image type given')
|
||||||
|
|
||||||
|
|
||||||
def _bytes_to_base64_data(data: bytes) -> str:
|
def _get_mime_type_for_audio(data: bytes):
|
||||||
|
if data.startswith(b'\x49\x44\x33') or data.startswith(b'\xff\xfb'):
|
||||||
|
return 'audio/mpeg'
|
||||||
|
else:
|
||||||
|
raise ValueError('Unsupported audio type given')
|
||||||
|
|
||||||
|
|
||||||
|
def _bytes_to_base64_data(data: bytes, *, audio: bool = False) -> str:
|
||||||
fmt = 'data:{mime};base64,{data}'
|
fmt = 'data:{mime};base64,{data}'
|
||||||
|
if audio:
|
||||||
|
mime = _get_mime_type_for_audio(data)
|
||||||
|
else:
|
||||||
mime = _get_mime_type_for_image(data)
|
mime = _get_mime_type_for_image(data)
|
||||||
b64 = b64encode(data).decode('ascii')
|
b64 = b64encode(data).decode('ascii')
|
||||||
return fmt.format(mime=mime, data=b64)
|
return fmt.format(mime=mime, data=b64)
|
||||||
|
172
docs/api.rst
172
docs/api.rst
@ -1298,6 +1298,35 @@ Scheduled Events
|
|||||||
:type user: :class:`User`
|
:type user: :class:`User`
|
||||||
|
|
||||||
|
|
||||||
|
Soundboard
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. function:: on_soundboard_sound_create(sound)
|
||||||
|
on_soundboard_sound_delete(sound)
|
||||||
|
|
||||||
|
Called when a :class:`SoundboardSound` is created or deleted.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
:param sound: The soundboard sound that was created or deleted.
|
||||||
|
:type sound: :class:`SoundboardSound`
|
||||||
|
|
||||||
|
.. function:: on_soundboard_sound_update(before, after)
|
||||||
|
|
||||||
|
Called when a :class:`SoundboardSound` is updated.
|
||||||
|
|
||||||
|
The following examples illustrate when this event is called:
|
||||||
|
|
||||||
|
- The name is changed.
|
||||||
|
- The emoji is changed.
|
||||||
|
- The volume is changed.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
:param sound: The soundboard sound that was updated.
|
||||||
|
:type sound: :class:`SoundboardSound`
|
||||||
|
|
||||||
|
|
||||||
Stages
|
Stages
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
@ -1483,6 +1512,17 @@ Voice
|
|||||||
:param after: The voice state after the changes.
|
:param after: The voice state after the changes.
|
||||||
:type after: :class:`VoiceState`
|
:type after: :class:`VoiceState`
|
||||||
|
|
||||||
|
.. function:: on_voice_channel_effect(effect)
|
||||||
|
|
||||||
|
Called when a :class:`Member` sends a :class:`VoiceChannelEffect` in a voice channel the bot is in.
|
||||||
|
|
||||||
|
This requires :attr:`Intents.voice_states` to be enabled.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
:param effect: The effect that is sent.
|
||||||
|
:type effect: :class:`VoiceChannelEffect`
|
||||||
|
|
||||||
.. _discord-api-utils:
|
.. _discord-api-utils:
|
||||||
|
|
||||||
Utility Functions
|
Utility Functions
|
||||||
@ -2945,6 +2985,42 @@ of :class:`enum.Enum`.
|
|||||||
|
|
||||||
.. versionadded:: 2.4
|
.. versionadded:: 2.4
|
||||||
|
|
||||||
|
.. attribute:: soundboard_sound_create
|
||||||
|
|
||||||
|
A soundboard sound was created.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.emoji`
|
||||||
|
- :attr:`~AuditLogDiff.volume`
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. attribute:: soundboard_sound_update
|
||||||
|
|
||||||
|
A soundboard sound was updated.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.emoji`
|
||||||
|
- :attr:`~AuditLogDiff.volume`
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. attribute:: soundboard_sound_delete
|
||||||
|
|
||||||
|
A soundboard sound was deleted.
|
||||||
|
|
||||||
|
Possible attributes for :class:`AuditLogDiff`:
|
||||||
|
|
||||||
|
- :attr:`~AuditLogDiff.name`
|
||||||
|
- :attr:`~AuditLogDiff.emoji`
|
||||||
|
- :attr:`~AuditLogDiff.volume`
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
.. class:: AuditLogActionCategory
|
.. class:: AuditLogActionCategory
|
||||||
|
|
||||||
Represents the category that the :class:`AuditLogAction` belongs to.
|
Represents the category that the :class:`AuditLogAction` belongs to.
|
||||||
@ -3663,6 +3739,21 @@ of :class:`enum.Enum`.
|
|||||||
A burst reaction, also known as a "super reaction".
|
A burst reaction, also known as a "super reaction".
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: VoiceChannelEffectAnimationType
|
||||||
|
|
||||||
|
Represents the animation type of a voice channel effect.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. attribute:: premium
|
||||||
|
|
||||||
|
A fun animation, sent by a Nitro subscriber.
|
||||||
|
|
||||||
|
.. attribute:: basic
|
||||||
|
|
||||||
|
The standard animation.
|
||||||
|
|
||||||
|
|
||||||
.. _discord-api-audit-logs:
|
.. _discord-api-audit-logs:
|
||||||
|
|
||||||
Audit Log Data
|
Audit Log Data
|
||||||
@ -4128,11 +4219,12 @@ AuditLogDiff
|
|||||||
|
|
||||||
.. attribute:: emoji
|
.. attribute:: emoji
|
||||||
|
|
||||||
The name of the emoji that represents a sticker being changed.
|
The emoji which represents one of the following:
|
||||||
|
|
||||||
See also :attr:`GuildSticker.emoji`.
|
* :attr:`GuildSticker.emoji`
|
||||||
|
* :attr:`SoundboardSound.emoji`
|
||||||
|
|
||||||
:type: :class:`str`
|
:type: Union[:class:`str`, :class:`PartialEmoji`]
|
||||||
|
|
||||||
.. attribute:: unicode_emoji
|
.. attribute:: unicode_emoji
|
||||||
|
|
||||||
@ -4153,9 +4245,10 @@ AuditLogDiff
|
|||||||
|
|
||||||
.. attribute:: available
|
.. attribute:: available
|
||||||
|
|
||||||
The availability of a sticker being changed.
|
The availability of one of the following being changed:
|
||||||
|
|
||||||
See also :attr:`GuildSticker.available`
|
* :attr:`GuildSticker.available`
|
||||||
|
* :attr:`SoundboardSound.available`
|
||||||
|
|
||||||
:type: :class:`bool`
|
:type: :class:`bool`
|
||||||
|
|
||||||
@ -4378,6 +4471,22 @@ AuditLogDiff
|
|||||||
|
|
||||||
:type: Optional[:class:`PartialEmoji`]
|
:type: Optional[:class:`PartialEmoji`]
|
||||||
|
|
||||||
|
.. attribute:: user
|
||||||
|
|
||||||
|
The user that represents the uploader of a soundboard sound.
|
||||||
|
|
||||||
|
See also :attr:`SoundboardSound.user`
|
||||||
|
|
||||||
|
:type: Union[:class:`Member`, :class:`User`]
|
||||||
|
|
||||||
|
.. attribute:: volume
|
||||||
|
|
||||||
|
The volume of a soundboard sound.
|
||||||
|
|
||||||
|
See also :attr:`SoundboardSound.volume`
|
||||||
|
|
||||||
|
:type: :class:`float`
|
||||||
|
|
||||||
.. this is currently missing the following keys: reason and application_id
|
.. this is currently missing the following keys: reason and application_id
|
||||||
I'm not sure how to port these
|
I'm not sure how to port these
|
||||||
|
|
||||||
@ -4799,6 +4908,35 @@ VoiceChannel
|
|||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
|
.. attributetable:: VoiceChannelEffect
|
||||||
|
|
||||||
|
.. autoclass:: VoiceChannelEffect()
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
|
.. class:: VoiceChannelEffectAnimation
|
||||||
|
|
||||||
|
A namedtuple which represents a voice channel effect animation.
|
||||||
|
|
||||||
|
.. versionadded:: 2.5
|
||||||
|
|
||||||
|
.. attribute:: id
|
||||||
|
|
||||||
|
The ID of the animation.
|
||||||
|
|
||||||
|
:type: :class:`int`
|
||||||
|
.. attribute:: type
|
||||||
|
|
||||||
|
The type of the animation.
|
||||||
|
|
||||||
|
:type: :class:`VoiceChannelEffectAnimationType`
|
||||||
|
|
||||||
|
.. attributetable:: VoiceChannelSoundEffect
|
||||||
|
|
||||||
|
.. autoclass:: VoiceChannelSoundEffect()
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
StageChannel
|
StageChannel
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
@ -4965,6 +5103,30 @@ GuildSticker
|
|||||||
.. autoclass:: GuildSticker()
|
.. autoclass:: GuildSticker()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
BaseSoundboardSound
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: BaseSoundboardSound
|
||||||
|
|
||||||
|
.. autoclass:: BaseSoundboardSound()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
SoundboardDefaultSound
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: SoundboardDefaultSound
|
||||||
|
|
||||||
|
.. autoclass:: SoundboardDefaultSound()
|
||||||
|
:members:
|
||||||
|
|
||||||
|
SoundboardSound
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: SoundboardSound
|
||||||
|
|
||||||
|
.. autoclass:: SoundboardSound()
|
||||||
|
:members:
|
||||||
|
|
||||||
ShardInfo
|
ShardInfo
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user