Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Soundboard #2623

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fefff2b
Original work at #2321
Dasupergrasskakjd Jan 16, 2024
780062e
:sparkles: `MORE_SOUNDBOARD` and `SOUNDBOARD` feature flags
Paillat-dev Oct 24, 2024
61a0876
:sparkles: `Guild.fetch_sounds`
Paillat-dev Oct 24, 2024
58a2b00
:sparkles: `Guild.fetch_sound`
Paillat-dev Oct 25, 2024
e64e4f1
:recycle: Cleanup
Paillat-dev Oct 25, 2024
f03c225
:bug: fix `PartialSoundboardSound.file`
Paillat-dev Oct 25, 2024
90269f0
:sparkles: `VoiceChannel.send_soundboard_sound`
Paillat-dev Oct 25, 2024
35af400
:sparkles: `SoundboardSound.edit`
Paillat-dev Oct 25, 2024
eac7e4b
:adhesive_bandage: Small fix
Paillat-dev Oct 25, 2024
3eb8dac
:memo: Docs
Paillat-dev Oct 25, 2024
04d6e15
:recycle: Cleanup
Paillat-dev Oct 25, 2024
93a52b3
:memo: Add `soundboard.py` example :tada:
Paillat-dev Oct 25, 2024
4373816
:memo: Add objects to docs
Paillat-dev Oct 28, 2024
9a08df5
:memo: CHANGELOG.md
Paillat-dev Oct 25, 2024
26f8dcd
Merge branch 'master' into soundboard
Paillat-dev Nov 3, 2024
636273a
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 3, 2024
8238d7c
:bug: `partial_emoji` could be unbound
Paillat-dev Nov 7, 2024
a600965
:sparkles: Soundboard gateway events
Paillat-dev Nov 7, 2024
40c9093
:bug: Add types and raw delete event
Paillat-dev Nov 7, 2024
0528a8c
:memo: Fix typo
Paillat-dev Nov 7, 2024
0bfe575
:memo: Fix CHANGELOG.md
Paillat-dev Nov 7, 2024
e8ebba9
:coffin: This is dead
Paillat-dev Nov 8, 2024
f3f8d15
Merge branch 'master' into soundboard
Paillat-dev Nov 15, 2024
9ba044d
Merge branch 'master' into soundboard
Paillat-dev Nov 28, 2024
a6cb39f
Merge branch 'master' into soundboard
Paillat-dev Dec 10, 2024
6f71350
:heavy_plus_sign: Add `typing_extensions` to reqs
Paillat-dev Dec 10, 2024
2bd05aa
:pencil2: Grammar and stuff
Paillat-dev Dec 12, 2024
e7f271e
:pencil2: Grammar
Paillat-dev Dec 17, 2024
a25ccdb
:memo: Add `VoiceChannelEffectAnimationType`
Paillat-dev Dec 17, 2024
8dd4b4f
chore: :alien: Update base max filesize to `10` Mb (#2671)
Paillat-dev Dec 18, 2024
9fa9c53
Merge branch 'master' into soundboard
Paillat-dev Jan 2, 2025
fa7f893
Merge branch 'master' into soundboard
Dorukyum Jan 4, 2025
b6b9220
:memo: Requested changes
Paillat-dev Jan 6, 2025
60bfe5b
Merge remote-tracking branch 'origin/master' into sound…
Paillat-dev Jan 6, 2025
2a705bb
:adhesive_bandage: Merge broke stuff
Paillat-dev Jan 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ These changes are available on the `master` branch, but have not yet been releas
([#2659](https://github.com/Pycord-Development/pycord/pull/2659))
- Added `VoiceMessage` subclass of `File` to allow voice messages to be sent.
([#2579](https://github.com/Pycord-Development/pycord/pull/2579))
- Added the following soundboard-related features:
- Manage guild soundboard sounds with `Guild.fetch_sounds()`, `Guild.create_sound()`,
`SoundboardSound.edit()`, and `SoundboardSound.delete()`.
- Access Discord default sounds with `Client.fetch_default_sounds()`.
- Play sounds in voice channels with `VoiceChannel.send_soundboard_sound()`.
- New `on_voice_channel_effect_send` event for sound and emoji effects.
- Soundboard limits based on guild premium tier (8-48 slots) in
`Guild.soundboard_limit`.
([#2623](https://github.com/Pycord-Development/pycord/pull/2623))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions discord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
from .role import *
from .scheduled_events import *
from .shard import *
from .soundboard import *
from .stage_instance import *
from .sticker import *
from .team import *
Expand Down
8 changes: 8 additions & 0 deletions discord/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,14 @@ def _from_scheduled_event_image(
animated=False,
)

@classmethod
def _from_soundboard_sound(cls, state, sound_id: int) -> Asset:
return cls(
state,
url=f"{cls.BASE}/soundboard-sounds/{sound_id}",
key=str(sound_id),
)

def __str__(self) -> str:
return self._url

Expand Down
112 changes: 111 additions & 1 deletion discord/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,16 @@
from __future__ import annotations

import datetime
from typing import TYPE_CHECKING, Any, Callable, Iterable, Mapping, TypeVar, overload
from typing import (
TYPE_CHECKING,
Any,
Callable,
Iterable,
Mapping,
NamedTuple,
TypeVar,
overload,
)

import discord.abc

Expand All @@ -40,6 +49,7 @@
SortOrder,
StagePrivacyLevel,
VideoQualityMode,
VoiceChannelEffectAnimationType,
VoiceRegion,
try_enum,
)
Expand All @@ -52,6 +62,7 @@
from .object import Object
from .partial_emoji import PartialEmoji, _EmojiTag
from .permissions import PermissionOverwrite, Permissions
from .soundboard import PartialSoundboardSound, SoundboardSound
from .stage_instance import StageInstance
from .threads import Thread
from .utils import MISSING
Expand All @@ -66,6 +77,7 @@
"PartialMessageable",
"ForumChannel",
"ForumTag",
"VoiceChannelEffectSendEvent",
)

if TYPE_CHECKING:
Expand All @@ -84,6 +96,7 @@
from .types.channel import StageChannel as StageChannelPayload
from .types.channel import TextChannel as TextChannelPayload
from .types.channel import VoiceChannel as VoiceChannelPayload
from .types.channel import VoiceChannelEffectSendEvent as VoiceChannelEffectSend
from .types.snowflake import SnowflakeList
from .types.threads import ThreadArchiveDuration
from .user import BaseUser, ClientUser, User
Expand Down Expand Up @@ -2000,6 +2013,25 @@ async def set_status(
"""
await self._state.http.set_voice_channel_status(self.id, status, reason=reason)

async def send_soundboard_sound(self, sound: PartialSoundboardSound) -> None:
"""|coro|

Sends a soundboard sound to the voice channel.

Parameters
----------
sound: :class:`PartialSoundboardSound`
The soundboard sound to send.

Raises
------
Forbidden
You do not have proper permissions to send the soundboard sound.
HTTPException
Sending the soundboard sound failed.
"""
await self._state.http.send_soundboard_sound(self.id, sound)


class StageChannel(discord.abc.Messageable, VocalGuildChannel):
"""Represents a Discord guild stage channel.
Expand Down Expand Up @@ -3220,6 +3252,84 @@ def get_partial_message(self, message_id: int, /) -> PartialMessage:
return PartialMessage(channel=self, id=message_id)


class VoiceChannelEffectAnimation(NamedTuple):
"""Represents an animation that can be sent to a voice channel.

.. versionadded:: 2.7
"""

id: int
type: VoiceChannelEffectAnimationType


class VoiceChannelSoundEffect(PartialSoundboardSound): ...


class VoiceChannelEffectSendEvent:
"""Represents the payload for an :func:`on_voice_channel_effect_send`.

.. versionadded:: 2.7

Attributes
----------
animation_type: :class:`int`
The type of animation that is being sent.
animation_id: :class:`int`
The ID of the animation that is being sent.
sound: Optional[:class:`SoundboardSound`]
The sound that is being sent, could be ``None`` if the effect is not a sound effect.
guild: :class:`Guild`
The guild in which the sound is being sent.
user: :class:`Member`
The member that sent the sound.
channel: :class:`VoiceChannel`
The voice channel in which the sound is being sent.
data: :class:`dict`
The raw data sent by the gateway.
"""

__slots__ = (
"_state",
"animation_type",
"animation_id",
"sound",
"guild",
"user",
"channel",
"data",
"emoji",
)

def __init__(
self,
data: VoiceChannelEffectSend,
state: ConnectionState,
sound: SoundboardSound | PartialSoundboardSound | None = None,
) -> None:
self._state = state
channel_id = int(data["channel_id"])
user_id = int(data["user_id"])
guild_id = int(data["guild_id"])
self.animation_type: VoiceChannelEffectAnimationType = try_enum(
VoiceChannelEffectAnimationType, data["animation_type"]
)
self.animation_id = int(data["animation_id"])
self.sound = sound
self.guild = state._get_guild(guild_id)
self.user = self.guild.get_member(user_id)
self.channel = self.guild.get_channel(channel_id)
self.emoji = (
PartialEmoji(
name=data["emoji"]["name"],
animated=data["emoji"]["animated"],
id=data["emoji"]["id"],
)
if data.get("emoji", None)
else None
)
self.data = data


def _guild_channel_factory(channel_type: int):
value = try_enum(ChannelType, channel_type)
if value is ChannelType.text:
Expand Down
45 changes: 45 additions & 0 deletions discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from .mentions import AllowedMentions
from .monetization import SKU, Entitlement
from .object import Object
from .soundboard import SoundboardSound
from .stage_instance import StageInstance
from .state import ConnectionState
from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory
Expand All @@ -71,6 +72,7 @@
from .member import Member
from .message import Message
from .poll import Poll
from .soundboard import SoundboardSound
from .voice_client import VoiceProtocol

__all__ = ("Client",)
Expand Down Expand Up @@ -2278,3 +2280,46 @@ async def delete_emoji(self, emoji: Snowflake) -> None:
)
if self._connection.cache_app_emojis and self._connection.get_emoji(emoji.id):
self._connection.remove_emoji(emoji)

def get_sound(self, sound_id: int) -> SoundboardSound | None:
"""Gets a :class:`.Sound` from the bot's sound cache.

.. versionadded:: 2.7

Parameters
----------
sound_id: :class:`int`
The ID of the sound to get.

Returns
-------
Optional[:class:`.SoundboardSound`]
The sound with the given ID.
"""
return self._connection._get_sound(sound_id)

@property
def sounds(self) -> list[SoundboardSound]:
"""A list of all the sounds the bot can see.

.. versionadded:: 2.7
"""
return self._connection.sounds

async def fetch_default_sounds(self) -> list[SoundboardSound]:
"""|coro|

Fetches the bot's default sounds.

.. versionadded:: 2.7

Returns
-------
List[:class:`.SoundboardSound`]
The bot's default sounds.
"""
data = await self._connection.http.get_default_sounds()
return [
SoundboardSound(http=self.http, state=self._connection, data=s)
for s in data
]
11 changes: 11 additions & 0 deletions discord/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"PromptType",
"OnboardingMode",
"ReactionType",
"VoiceChannelEffectAnimationType",
"SKUType",
"EntitlementType",
"EntitlementOwnerType",
Expand Down Expand Up @@ -1054,6 +1055,16 @@ class PollLayoutType(Enum):
default = 1


class VoiceChannelEffectAnimationType(Enum):
"""Voice channel effect animation type.

.. versionadded:: 2.7
"""

premium = 0
basic = 1


T = TypeVar("T")


Expand Down
10 changes: 10 additions & 0 deletions discord/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ class DiscordWebSocket:
HELLO = 10
HEARTBEAT_ACK = 11
GUILD_SYNC = 12
REQUEST_SOUNDBOARD_SOUNDS = 31

def __init__(self, socket, *, loop):
self.socket = socket
Expand Down Expand Up @@ -724,6 +725,15 @@ async def voice_state(self, guild_id, channel_id, self_mute=False, self_deaf=Fal
_log.debug("Updating our voice state to %s.", payload)
await self.send_as_json(payload)

async def request_soundboard_sounds(self, guild_ids):
payload = {
"op": self.REQUEST_SOUNDBOARD_SOUNDS,
"d": {"guild_ids": guild_ids},
}

_log.debug("Requesting soundboard sounds for guilds %s.", guild_ids)
await self.send_as_json(payload)

async def close(self, code=4000):
if self._keep_alive:
self._keep_alive.stop()
Expand Down
Loading
Loading