diff --git a/discord/client.py b/discord/client.py index 9cd6f61671..206aa9e446 100644 --- a/discord/client.py +++ b/discord/client.py @@ -2293,7 +2293,7 @@ def get_sound(self, sound_id: int) -> SoundboardSound | None: def sounds(self) -> list[SoundboardSound]: """A list of all the sounds the bot can see. - .. versionadded:: 2.4 + .. versionadded:: 2.7 """ return self._connection.sounds @@ -2302,7 +2302,7 @@ async def fetch_default_sounds(self) -> list[SoundboardSound]: Fetches the bot's default sounds. - .. versionadded:: 2.4 + .. versionadded:: 2.7 Returns ------- diff --git a/discord/guild.py b/discord/guild.py index e6ecd1ca3b..abb34b306f 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -565,9 +565,7 @@ def _from_data(self, guild: GuildPayload) -> None: self._update_voice_state(obj, int(obj["channel_id"])) for sound in guild.get("soundboard_sounds", []): - sound = SoundboardSound( - state=state, http=state.http, data=sound, guild=self - ) + sound = SoundboardSound(state=state, http=state.http, data=sound) self._add_sound(sound) def _add_sound(self, sound: SoundboardSound) -> None: @@ -577,12 +575,33 @@ def _add_sound(self, sound: SoundboardSound) -> None: def _remove_sound(self, sound_id: int) -> None: self._sounds.pop(sound_id, None) + async def fetch_sounds(self) -> list[SoundboardSound]: + """|coro| + Fetches all the soundboard sounds in the guild. + + .. versionadded:: 2.7 + + Returns + ------- + List[:class:`SoundboardSound`] + The sounds in the guild. + """ + data = await self._state.http.get_all_guild_sounds(self.id) + return [ + SoundboardSound( + state=self._state, + http=self._state.http, + data=sound, + ) + for sound in data["items"] + ] + async def create_sound( self, name: str, sound: bytes, volume: float = 1.0, - emoji: PartialEmoji | Emoji | str | None = None, + emoji: PartialEmoji | GuildEmoji | str | None = None, reason: str | None = None, ): """|coro| @@ -600,7 +619,7 @@ async def create_sound( Only MP3 sound files that don't exceed the duration of 5.2s are supported. volume: :class:`float` The volume of the sound. Defaults to 1.0. - emoji: Union[:class:`PartialEmoji`, :class:`Emoji`, :class:`str`] + emoji: Union[:class:`PartialEmoji`, :class:`GuildEmoji`, :class:`str`] The emoji of the sound. reason: Optional[:class:`str`] The reason for creating this sound. Shows up on the audit log. @@ -640,13 +659,13 @@ async def create_sound( else: payload["emoji_id"] = partial_emoji.id - data = await self._state.http.create_sound(self.id, reason=reason, **payload) + data = await self._state.http.create_guild_sound( + self.id, reason=reason, **payload + ) return SoundboardSound( state=self._state, http=self._state.http, data=data, - guild=self, - owner_id=self._state.self_id, ) # TODO: refactor/remove? diff --git a/discord/http.py b/discord/http.py index 1824d23abf..bcccee0c69 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3191,10 +3191,12 @@ def delete_sound( def get_default_sounds(self): return self.request(Route("GET", "/soundboard-default-sounds")) - def create_sound(self, guild_id: Snowflake, reason: str | None, **payload): + def create_guild_sound( + self, guild_id: Snowflake, reason: str | None, **payload + ) -> Response[SoundboardSoundPayload]: keys = ( "name", - "suond", + "sound", "volume", "emoji_id", "emoji_name", @@ -3208,6 +3210,13 @@ def create_sound(self, guild_id: Snowflake, reason: str | None, **payload): reason=reason, ) + def get_all_guild_sounds( + self, guild_id: Snowflake + ) -> Response[list[SoundboardSoundPayload]]: + return self.request( + Route("GET", "/guilds/{guild_id}/soundboard-sounds", guild_id=guild_id) + ) + def edit_sound( self, guild_id: Snowflake, sound_Id: Snowflake, *, reason: str | None, **payload ): diff --git a/discord/soundboard.py b/discord/soundboard.py index 1e1525dc08..53bd062189 100644 --- a/discord/soundboard.py +++ b/discord/soundboard.py @@ -30,8 +30,12 @@ from .asset import Asset from .emoji import PartialEmoji from .mixins import Hashable +from .types.channel import ( + VoiceChannelEffectSendEvent as VoiceChannelEffectSendEventPayload, +) from .types.soundboard import PartialSoundboardSound as PartialSoundboardSoundPayload from .types.soundboard import SoundboardSound as SoundboardSoundPayload +from .utils import cached_slot_property if TYPE_CHECKING: from .guild import Guild @@ -62,16 +66,13 @@ class PartialSoundboardSound(Hashable): __slots__ = ("id", "volume", "emoji", "_http", "emoji") - def __init__(self, data: PartialSoundboardSoundPayload, http: HTTPClient): + def __init__( + self, + data: PartialSoundboardSoundPayload | VoiceChannelEffectSendEvent, + http: HTTPClient, + ): self._http = http - self.id = int(data["sound_id"]) - self.volume = ( - float(data["volume"]) if data.get("volume") else data["sound_volume"] - ) - self.emoji = PartialEmoji( - name=data.get("emoji_name"), - id=int(data["emoji_id"]) if data.get("emoji_id") else None, - ) + self._from_data(data) def __eq__(self, other: PartialSoundboardSound) -> bool: if isinstance(other, self, __class__): @@ -86,12 +87,21 @@ def file(self) -> Asset: """:class:`Asset`: Returns the sound's file.""" return Asset._from_soundboard_sound(self) - def _update(self, data: PartialSoundboardSoundPayload) -> None: - self.volume = float(data["volume"]) - self.emoji = PartialEmoji( - name=data.get("emoji_name"), - id=int(data["emoji_id"]) if data.get("emoji_id") else None, - ) + def _from_data( + self, data: PartialSoundboardSoundPayload | VoiceChannelEffectSendEventPayload + ) -> None: + self.id = int(data["sound_id"]) + self.volume = float(data.get("volume", 0)) or data.get("sound_volume") + + if raw_emoji := data.get( + "emoji" + ): # From gateway event (VoiceChannelEffectSendEvent) + self.emoji = PartialEmoji.from_dict(raw_emoji) + else: # From HTTP response (PartialSoundboardSoundPayload) + self.emoji = PartialEmoji( + name=data.get("emoji_name"), + id=int(data.get("emoji_id")) if data.get("emoji_id") else None, + ) class SoundboardSound(PartialSoundboardSound): @@ -121,8 +131,9 @@ class SoundboardSound(PartialSoundboardSound): "name", "available", "emoji", - "guild", - "owner", + "guild_id", + "_cs_guild", + "user", "_http", "_state", "emoji", @@ -134,16 +145,27 @@ def __init__( state: ConnectionState, http: HTTPClient, data: SoundboardSoundPayload, - guild_id: int = None, - owner_id: Member = None, - guild: Guild = None, ) -> None: self._state = state super().__init__(data, http) + + def _from_data(self, data: SoundboardSoundPayload) -> None: + super()._from_data(data) self.name = data["name"] - self.available = bool(data.get("available", True)) - self.guild: Guild = guild or state._get_guild(guild_id) - self.owner: Member = self.guild.get_member(owner_id) + self.available: bool = data["available"] + self.guild_id = int(data["guild_id"]) + user = data.get("user") + + self.user = self._state.store_user(user) if user else None + + @cached_slot_property("_cs_guild") + def guild(self) -> Guild: + """:class:`Guild`: The guild the sound belongs to. + + The :class:`Guild` object representing the guild the sound belongs to. + .. versionadded:: 2.7 + """ + return self._state._get_guild(self.guild_id) def __eq__(self, other: SoundboardSound) -> bool: return isinstance(other, SoundboardSound) and self.__dict__ == other.__dict__