Skip to content

Conversation

@mplatypus
Copy link
Contributor

@mplatypus mplatypus commented Oct 17, 2025

Summary

Added the soundboard sound object and all events/endpoints.

Checklist

  • I have run nox and all the pipelines have passed.
  • I have made unittests according to the code I have added/modified/deleted.

@davfsa davfsa added the enhancement New feature or request label Oct 17, 2025
@davfsa davfsa requested review from Copilot and davfsa October 17, 2025 10:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds full soundboard support across the library: model, REST endpoints, gateway events, and shard/bot helpers.

  • Introduces SoundboardSound entity and (de)serialization.
  • Adds REST endpoints for default/guild sounds, sending, creating, editing, and deleting sounds.
  • Wires new gateway events (soundboard sound create/update/delete/sounds update, and voice channel effect send) and a shard/bot request for soundboard sounds.

Reviewed Changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
hikari/soundboard.py Adds SoundboardSound model.
hikari/impl/entity_factory.py Deserializes soundboard sound payloads into entities.
hikari/impl/rest.py Implements REST methods for soundboard endpoints.
hikari/internal/routes.py Declares REST routes for soundboard.
hikari/impl/event_factory.py Deserializes new gateway events (soundboard + channel effect send).
hikari/impl/event_manager.py Dispatches new soundboard and channel effect events.
hikari/events/soundboard_events.py Defines soundboard event classes.
hikari/events/channel_events.py Adds GuildChannelEffectSendEvent type.
hikari/impl/shard.py Implements OP 31 request for soundboard sounds.
hikari/impl/gateway_bot.py Adds bot helper to request soundboard sounds.
hikari/api/* Extends REST, shard, and event factory interfaces for soundboard.
hikari/emojis.py Adds EmojiAnimationType enum.
hikari/intents.py Updates intents docstring with soundboard events.
tests/* Unit tests for soundboard model, REST, shard, bot, events, and factories.
hikari/init.* Exposes soundboard symbols at package root.
changes/2575.feature.md Change log entry for soundboard support.

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@typing_extensions.override
def app(self) -> traits.RESTAware:
# <<inherited docstring from Event>>.
return self.app
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app property is recursively referencing itself, causing infinite recursion at runtime and there is no app field on this event. Return the application from the shard (minimal change), e.g. return self.shard.app. Alternatively, add an app: traits.RESTAware attrs field (as done in other events) and have the event factory pass app=self._app.

Suggested change
return self.app
return self.shard.app

Copilot uses AI. Check for mistakes.
Comment on lines +168 to +171
animation_id=snowflakes.Snowflake(payload["animation_id"])
if "animation_id" in payload
else undefined.UNDEFINED,
sound_id=snowflakes.Snowflake(payload["sound_id"]) if "sound_id" in payload else undefined.UNDEFINED,
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If animation_id or sound_id are present but null, snowflakes.Snowflake(None) will raise; mirror the earlier pattern and guard against None, e.g. use value = payload.get("animation_id"); animation_id = snowflakes.Snowflake(value) if value is not None else None (or undefined.UNDEFINED if missing).

Suggested change
animation_id=snowflakes.Snowflake(payload["animation_id"])
if "animation_id" in payload
else undefined.UNDEFINED,
sound_id=snowflakes.Snowflake(payload["sound_id"]) if "sound_id" in payload else undefined.UNDEFINED,
animation_id=(
snowflakes.Snowflake(payload["animation_id"])
if "animation_id" in payload and payload["animation_id"] is not None
else (None if "animation_id" in payload else undefined.UNDEFINED)
),
sound_id=(
snowflakes.Snowflake(payload["sound_id"])
if "sound_id" in payload and payload["sound_id"] is not None
else (None if "sound_id" in payload else undefined.UNDEFINED)
),

Copilot uses AI. Check for mistakes.
Comment on lines +5273 to +5276
result = await self._request(routes.GET_GUILD_SOUNDBOARD_SOUND.compile(guild=guild, sound=sound))
assert isinstance(result, dict)
return self._entity_factory.deserialize_soundboard_sound(result)

Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation never returns None, but the signature advertises Optional and API docs describe returning None when not found. Either update the return type (and API docs) to soundboard.SoundboardSound, or handle NotFoundError (and possibly 204) to return None as documented.

Suggested change
result = await self._request(routes.GET_GUILD_SOUNDBOARD_SOUND.compile(guild=guild, sound=sound))
assert isinstance(result, dict)
return self._entity_factory.deserialize_soundboard_sound(result)
try:
result = await self._request(routes.GET_GUILD_SOUNDBOARD_SOUND.compile(guild=guild, sound=sound))
except errors.NotFoundError:
return None
assert isinstance(result, dict)
return self._entity_factory.deserialize_soundboard_sound(result)

Copilot uses AI. Check for mistakes.
Comment on lines +9653 to +9659
@abc.abstractmethod
async def fetch_guild_soundboard_sound(
self,
guild: snowflakes.SnowflakeishOr[guilds.PartialGuild],
sound: snowflakes.SnowflakeishOr[soundboard.SoundboardSound],
/,
) -> soundboard.SoundboardSound | None:
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API contract states Optional[SoundboardSound] and the docstring promises None when not found, but the concrete implementation does not return None; align the contract and behavior (either drop None here and in docs, or specify/implement the NotFound-to-None behavior).

Copilot uses AI. Check for mistakes.
# 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.
"""Application and entities that are used to describe stage instances on Discord."""
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module docstring references "stage instances"; update to "soundboard sounds" to reflect this module's purpose.

Suggested change
"""Application and entities that are used to describe stage instances on Discord."""
"""Application and entities that are used to describe soundboard sounds on Discord."""

Copilot uses AI. Check for mistakes.
@attrs_extensions.with_copy
@attrs.define(kw_only=True, weakref_slot=False)
class SoundboardSoundsUpdateEvent(SoundboardSoundEvent):
"""Event fired when a guild soundboard sound is created."""
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class docstring is inaccurate for an update event; change to "Event fired when a guild's soundboard sounds are updated."

Suggested change
"""Event fired when a guild soundboard sound is created."""
"""Event fired when a guild's soundboard sounds are updated."""

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +89
guild_id: snowflakes.Snowflake = attrs.field(hash=True, repr=False)
"""The guild ID of the stage instance."""
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace "stage instance" with "soundboard sound" or "event" context; e.g., "The guild ID this sound belongs to."

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +122
guild_id: snowflakes.Snowflake = attrs.field(hash=True, repr=False)
"""The guild ID of the stage instance."""
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the docstring to reference the soundboard sound instead of a stage instance.

Copilot uses AI. Check for mistakes.
"""The guilds soundboard sounds."""

guild_id: snowflakes.Snowflake = attrs.field(hash=True, repr=True)
"""The guild ID of the stage instance."""
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update to "The guild ID these sounds belong to." to match the event context.

Suggested change
"""The guild ID of the stage instance."""
"""The guild ID these sounds belong to."""

Copilot uses AI. Check for mistakes.
@attrs_extensions.with_copy
@attrs.define(kw_only=True, weakref_slot=False)
class GuildChannelEffectSendEvent(GuildChannelEvent):
"""Event fired when a guild channel is created."""
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The class docstring is misleading; this event represents a "voice channel effect send" (emoji/sound in a voice channel). Update the description accordingly.

Suggested change
"""Event fired when a guild channel is created."""
"""Event fired when a voice channel effect (emoji or sound) is sent in the connected voice channel."""

Copilot uses AI. Check for mistakes.
Comment on lines +4723 to +4726
emoji_id = payload.get("emoji_id")
if emoji_id:
emoji_id = snowflakes.Snowflake(emoji_id)
emoji_name = payload.get("emoji_name")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
emoji_id = payload.get("emoji_id")
if emoji_id:
emoji_id = snowflakes.Snowflake(emoji_id)
emoji_name = payload.get("emoji_name")
emoji_id = payload["emoji_id"]
if emoji_id:
emoji_id = snowflakes.Snowflake(emoji_id)
emoji_name = payload["emoji_name"]

Comment on lines +4722 to +4740
emoji: emoji_models.CustomEmoji | emoji_models.UnicodeEmoji | None = None
emoji_id = payload.get("emoji_id")
if emoji_id:
emoji_id = snowflakes.Snowflake(emoji_id)
emoji_name = payload.get("emoji_name")
if emoji_id and emoji_name:
emoji = emoji_models.CustomEmoji(
id=emoji_id,
name=emoji_name,
is_animated=False, # FIXME: I am unsure if this is correct, but I have no clue on how I could validate this. # noqa: TD001, E501
)
if emoji_id and not emoji_name:
emoji = emoji_models.CustomEmoji(
id=emoji_id,
name="", # FIXME: I am unsure if this is correct, but I have no clue on how I could validate this. # noqa: TD001, E501
is_animated=False, # FIXME: I am unsure if this is correct, but I have no clue on how I could validate this. # noqa: TD001, E501
)
if not emoji_id and emoji_name:
emoji = emoji_models.UnicodeEmoji(emoji_name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given we are not given animated, then lets always set it as False (Resolves the FIXMEs on it)

There will never be the case of emoji_id and not emoji_name, so that branch can be removed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants