Skip to content

Commit

Permalink
Add Soundboard (#283)
Browse files Browse the repository at this point in the history
* Add GuildVoiceChannelEffectSend

* Add Soundboard

* add event to ListenerAdapter

* add soundboard sound events

* rename file

* rename interface

* add sound fetching

* add check for gateway

* add docs for GuildVoiceChannelEffectSend

* rename SoundboardAnimationType to SoundboardEffectAnimationType

* bruh

* change param to varargs

* remove pointless else

* add CDNEndpoint.Path()

* shorten impl name

* add audit log events

* updates

* add caches

* remove SoundOverridePath

* updates

* remove UserID from SoundboardSound

* updates

discord/discord-api-docs@8b05cc6

* add URL()

* add required intent + fix docs

* SoundboardEffectAnimationType -> VoiceChannelEffectAnimationType

* oh man

* Revert "oh man"

This reverts commit 7957f48.

* change SoundID type to int64 with ,string

* add event umarshaler

* damn this shit is broken

* add GuildFeatureMoreSoundboard

discord/discord-api-docs@5a13333

* add missing cache getter

* add request func to client interface

* fix endpoint method

* Update discord/soundboard.go

Co-authored-by: Toπ <[email protected]>

* Apply suggestions from code review

Co-authored-by: Toπ <[email protected]>

---------

Co-authored-by: mlnrDev <[email protected]>
Co-authored-by: Toπ <[email protected]>
  • Loading branch information
3 people authored Sep 21, 2024
1 parent bb89022 commit 03e9ee0
Show file tree
Hide file tree
Showing 24 changed files with 550 additions and 50 deletions.
12 changes: 12 additions & 0 deletions bot/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ type Client interface {
// limit : The number of discord.Member(s) to return.
RequestMembersWithQuery(ctx context.Context, guildID snowflake.ID, presence bool, nonce string, query string, limit int) error

// RequestSoundboardSounds a gateway.MessageDataRequestSoundboardSounds to the specific gateway.Gateway and requests the SoundboardSounds of the specified guilds.
RequestSoundboardSounds(ctx context.Context, guildIDs ...snowflake.ID) error

// SetPresence sends new presence data to the gateway.Gateway.
SetPresence(ctx context.Context, opts ...gateway.PresenceOpt) error

Expand Down Expand Up @@ -277,6 +280,15 @@ func (c *clientImpl) RequestMembersWithQuery(ctx context.Context, guildID snowfl
})
}

func (c *clientImpl) RequestSoundboardSounds(ctx context.Context, guildIDs ...snowflake.ID) error {
if !c.HasGateway() {
return discord.ErrNoGateway
}
return c.gateway.Send(ctx, gateway.OpcodeRequestSoundboardSounds, gateway.MessageDataRequestSoundboardSounds{
GuildIDs: guildIDs,
})
}

func (c *clientImpl) SetPresence(ctx context.Context, opts ...gateway.PresenceOpt) error {
if !c.HasGateway() {
return discord.ErrNoGateway
Expand Down
38 changes: 26 additions & 12 deletions cache/cache_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@ import (
// DefaultConfig returns a Config with sensible defaults.
func DefaultConfig() *Config {
return &Config{
GuildCachePolicy: PolicyAll[discord.Guild],
ChannelCachePolicy: PolicyAll[discord.GuildChannel],
StageInstanceCachePolicy: PolicyAll[discord.StageInstance],
GuildScheduledEventCachePolicy: PolicyAll[discord.GuildScheduledEvent],
RoleCachePolicy: PolicyAll[discord.Role],
MemberCachePolicy: PolicyAll[discord.Member],
ThreadMemberCachePolicy: PolicyAll[discord.ThreadMember],
PresenceCachePolicy: PolicyAll[discord.Presence],
VoiceStateCachePolicy: PolicyAll[discord.VoiceState],
MessageCachePolicy: PolicyAll[discord.Message],
EmojiCachePolicy: PolicyAll[discord.Emoji],
StickerCachePolicy: PolicyAll[discord.Sticker],
GuildCachePolicy: PolicyAll[discord.Guild],
ChannelCachePolicy: PolicyAll[discord.GuildChannel],
StageInstanceCachePolicy: PolicyAll[discord.StageInstance],
GuildScheduledEventCachePolicy: PolicyAll[discord.GuildScheduledEvent],
GuildSoundboardSoundCachePolicy: PolicyAll[discord.SoundboardSound],
RoleCachePolicy: PolicyAll[discord.Role],
MemberCachePolicy: PolicyAll[discord.Member],
ThreadMemberCachePolicy: PolicyAll[discord.ThreadMember],
PresenceCachePolicy: PolicyAll[discord.Presence],
VoiceStateCachePolicy: PolicyAll[discord.VoiceState],
MessageCachePolicy: PolicyAll[discord.Message],
EmojiCachePolicy: PolicyAll[discord.Emoji],
StickerCachePolicy: PolicyAll[discord.Sticker],
}
}

Expand All @@ -42,6 +43,9 @@ type Config struct {
GuildScheduledEventCache GuildScheduledEventCache
GuildScheduledEventCachePolicy Policy[discord.GuildScheduledEvent]

GuildSoundboardSoundCache GuildSoundboardSoundCache
GuildSoundboardSoundCachePolicy Policy[discord.SoundboardSound]

RoleCache RoleCache
RoleCachePolicy Policy[discord.Role]

Expand Down Expand Up @@ -90,6 +94,9 @@ func (c *Config) Apply(opts []ConfigOpt) {
if c.GuildScheduledEventCache == nil {
c.GuildScheduledEventCache = NewGuildScheduledEventCache(NewGroupedCache[discord.GuildScheduledEvent](c.CacheFlags, FlagGuildScheduledEvents, c.GuildScheduledEventCachePolicy))
}
if c.GuildSoundboardSoundCache == nil {
c.GuildSoundboardSoundCache = NewGuildSoundboardSoundCache(NewGroupedCache[discord.SoundboardSound](c.CacheFlags, FlagGuildSoundboardSounds, c.GuildSoundboardSoundCachePolicy))
}
if c.RoleCache == nil {
c.RoleCache = NewRoleCache(NewGroupedCache[discord.Role](c.CacheFlags, FlagRoles, c.RoleCachePolicy))
}
Expand Down Expand Up @@ -179,6 +186,13 @@ func WithGuildScheduledEventCache(guildScheduledEventCache GuildScheduledEventCa
}
}

// WithGuildSoundboardSoundCache sets the GuildSoundboardSoundCache of the Config.
func WithGuildSoundboardSoundCache(guildSoundboardSoundCache GuildSoundboardSoundCache) ConfigOpt {
return func(config *Config) {
config.GuildSoundboardSoundCache = guildSoundboardSoundCache
}
}

// WithRoleCachePolicy sets the Policy[discord.Role] of the Config.
func WithRoleCachePolicy(policy Policy[discord.Role]) ConfigOpt {
return func(config *Config) {
Expand Down
4 changes: 3 additions & 1 deletion cache/cache_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
FlagStickers
FlagVoiceStates
FlagStageInstances
FlagGuildSoundboardSounds

FlagsNone Flags = 0
FlagsAll = FlagGuilds |
Expand All @@ -32,7 +33,8 @@ const (
FlagEmojis |
FlagStickers |
FlagVoiceStates |
FlagStageInstances
FlagStageInstances |
FlagGuildSoundboardSounds
)

// Add allows you to add multiple bits together, producing a new bit
Expand Down
111 changes: 84 additions & 27 deletions cache/caches.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,59 @@ func (c *guildScheduledEventCacheImpl) RemoveGuildScheduledEventsByGuildID(guild
c.cache.GroupRemove(guildID)
}

type GuildSoundboardSoundCache interface {
GuildSoundboardSoundCache() GroupedCache[discord.SoundboardSound]
GuildSoundboardSound(guildID snowflake.ID, soundID snowflake.ID) (discord.SoundboardSound, bool)
GuildSoundboardSoundsForEach(guildID snowflake.ID, fn func(sound discord.SoundboardSound))
GuildSoundboardSoundsAllLen() int
GuildSoundboardSoundsLen(guildID snowflake.ID) int
AddGuildSoundboardSound(sound discord.SoundboardSound)
RemoveGuildSoundboardSound(guildID snowflake.ID, sound snowflake.ID) (discord.SoundboardSound, bool)
RemoveGuildSoundboardSoundsByGuildID(guildID snowflake.ID)
}

func NewGuildSoundboardSoundCache(cache GroupedCache[discord.SoundboardSound]) GuildSoundboardSoundCache {
return &guildSoundboardSoundCacheImpl{
cache: cache,
}
}

type guildSoundboardSoundCacheImpl struct {
cache GroupedCache[discord.SoundboardSound]
}

func (c *guildSoundboardSoundCacheImpl) GuildSoundboardSoundCache() GroupedCache[discord.SoundboardSound] {
return c.cache
}

func (c *guildSoundboardSoundCacheImpl) GuildSoundboardSound(guildID snowflake.ID, soundID snowflake.ID) (discord.SoundboardSound, bool) {
return c.cache.Get(guildID, soundID)
}

func (c *guildSoundboardSoundCacheImpl) GuildSoundboardSoundsForEach(guildID snowflake.ID, fn func(sound discord.SoundboardSound)) {
c.cache.GroupForEach(guildID, fn)
}

func (c *guildSoundboardSoundCacheImpl) GuildSoundboardSoundsAllLen() int {
return c.cache.Len()
}

func (c *guildSoundboardSoundCacheImpl) GuildSoundboardSoundsLen(guildID snowflake.ID) int {
return c.cache.GroupLen(guildID)
}

func (c *guildSoundboardSoundCacheImpl) AddGuildSoundboardSound(sound discord.SoundboardSound) {
c.cache.Put(*sound.GuildID, sound.SoundID, sound)
}

func (c *guildSoundboardSoundCacheImpl) RemoveGuildSoundboardSound(guildID snowflake.ID, soundID snowflake.ID) (discord.SoundboardSound, bool) {
return c.cache.Remove(guildID, soundID)
}

func (c *guildSoundboardSoundCacheImpl) RemoveGuildSoundboardSoundsByGuildID(guildID snowflake.ID) {
c.cache.GroupRemove(guildID)
}

type RoleCache interface {
RoleCache() GroupedCache[discord.Role]

Expand Down Expand Up @@ -749,6 +802,7 @@ type Caches interface {
ChannelCache
StageInstanceCache
GuildScheduledEventCache
GuildSoundboardSoundCache
RoleCache
MemberCache
ThreadMemberCache
Expand Down Expand Up @@ -830,38 +884,40 @@ func New(opts ...ConfigOpt) Caches {
config.Apply(opts)

return &cachesImpl{
config: *config,
selfUserCache: config.SelfUserCache,
guildCache: config.GuildCache,
channelCache: config.ChannelCache,
stageInstanceCache: config.StageInstanceCache,
guildScheduledEventCache: config.GuildScheduledEventCache,
roleCache: config.RoleCache,
memberCache: config.MemberCache,
threadMemberCache: config.ThreadMemberCache,
presenceCache: config.PresenceCache,
voiceStateCache: config.VoiceStateCache,
messageCache: config.MessageCache,
emojiCache: config.EmojiCache,
stickerCache: config.StickerCache,
config: *config,
selfUserCache: config.SelfUserCache,
guildCache: config.GuildCache,
channelCache: config.ChannelCache,
stageInstanceCache: config.StageInstanceCache,
guildScheduledEventCache: config.GuildScheduledEventCache,
guildSoundboardSoundCache: config.GuildSoundboardSoundCache,
roleCache: config.RoleCache,
memberCache: config.MemberCache,
threadMemberCache: config.ThreadMemberCache,
presenceCache: config.PresenceCache,
voiceStateCache: config.VoiceStateCache,
messageCache: config.MessageCache,
emojiCache: config.EmojiCache,
stickerCache: config.StickerCache,
}
}

// these type aliases are needed to allow having the GuildCache, ChannelCache, etc. as methods on the cachesImpl struct
type (
guildCache = GuildCache
channelCache = ChannelCache
stageInstanceCache = StageInstanceCache
guildScheduledEventCache = GuildScheduledEventCache
roleCache = RoleCache
memberCache = MemberCache
threadMemberCache = ThreadMemberCache
presenceCache = PresenceCache
voiceStateCache = VoiceStateCache
messageCache = MessageCache
emojiCache = EmojiCache
stickerCache = StickerCache
selfUserCache = SelfUserCache
guildCache = GuildCache
channelCache = ChannelCache
stageInstanceCache = StageInstanceCache
guildScheduledEventCache = GuildScheduledEventCache
guildSoundboardSoundCache = GuildSoundboardSoundCache
roleCache = RoleCache
memberCache = MemberCache
threadMemberCache = ThreadMemberCache
presenceCache = PresenceCache
voiceStateCache = VoiceStateCache
messageCache = MessageCache
emojiCache = EmojiCache
stickerCache = StickerCache
selfUserCache = SelfUserCache
)

type cachesImpl struct {
Expand All @@ -871,6 +927,7 @@ type cachesImpl struct {
channelCache
stageInstanceCache
guildScheduledEventCache
guildSoundboardSoundCache
roleCache
memberCache
threadMemberCache
Expand Down
6 changes: 6 additions & 0 deletions discord/audit_log.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ const (
AuditLogApplicationCommandPermissionUpdate AuditLogEvent = 121
)

const (
AuditLogSoundboardSoundCreate AuditLogEvent = iota + 130
AuditLogSoundboardSoundUpdate
AuditLogSoundboardSoundDelete
)

const (
AuditLogAutoModerationRuleCreate AuditLogEvent = iota + 140
AuditLogAutoModerationRuleUpdate
Expand Down
2 changes: 2 additions & 0 deletions discord/cdn_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ var (
CustomSticker = NewCDN("/stickers/{sticker.id}", FileFormatPNG, FileFormatLottie, FileFormatGIF)

AttachmentFile = NewCDN("/attachments/{channel.id}/{attachment.id}/{file.name}", FileFormatNone)

SoundboardSoundFile = NewCDN("/soundboard-sounds/{sound.id}", FileFormatNone)
)

// FileFormat is the type of file on Discord's CDN (https://discord.com/developers/docs/reference#image-formatting-image-formats)
Expand Down
3 changes: 3 additions & 0 deletions discord/guild.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ const (
GuildFeatureInvitesDisabled GuildFeature = "INVITES_DISABLED"
GuildFeatureInviteSplash GuildFeature = "INVITE_SPLASH"
GuildFeatureMemberVerificationGateEnabled GuildFeature = "MEMBER_VERIFICATION_GATE_ENABLED"
GuildFeatureMoreSoundboard GuildFeature = "MORE_SOUNDBOARD"
GuildFeatureMoreStickers GuildFeature = "MORE_STICKERS"
GuildFeatureNews GuildFeature = "NEWS"
GuildFeaturePartnered GuildFeature = "PARTNERED"
Expand All @@ -119,6 +120,7 @@ const (
GuildFeatureRoleIcons GuildFeature = "ROLE_ICONS"
GuildFeatureRoleSubscriptionsAvailableForPurchase GuildFeature = "ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE"
GuildFeatureRoleSubscriptionsEnabled GuildFeature = "ROLE_SUBSCRIPTIONS_ENABLED"
GuildFeatureSoundboard GuildFeature = "SOUNDBOARD"
GuildFeatureTicketedEventsEnabled GuildFeature = "TICKETED_EVENTS_ENABLED"
GuildFeatureVanityURL GuildFeature = "VANITY_URL"
GuildFeatureVerified GuildFeature = "VERIFIED"
Expand Down Expand Up @@ -224,6 +226,7 @@ type GatewayGuild struct {
Presences []Presence `json:"presences"`
StageInstances []StageInstance `json:"stage_instances"`
GuildScheduledEvents []GuildScheduledEvent `json:"guild_scheduled_events"`
SoundboardSounds []SoundboardSound `json:"soundboard_sounds"`
}

func (g *GatewayGuild) UnmarshalJSON(data []byte) error {
Expand Down
59 changes: 59 additions & 0 deletions discord/sound.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package discord

import (
"encoding/base64"
"fmt"
"io"

"github.com/disgoorg/json"
)

type SoundType string

const (
SoundTypeMP3 SoundType = "audio/mpeg"
SoundTypeOGG SoundType = "audio/ogg"
SoundTypeWAV SoundType = "audio/wav"
SoundTypeUnknown = SoundTypeMP3
)

func (t SoundType) MIME() string {
return string(t)
}

func (t SoundType) Header() string {
return "data:" + string(t) + ";base64"
}

var _ json.Marshaler = (*Sound)(nil)
var _ fmt.Stringer = (*Sound)(nil)

func NewSound(soundType SoundType, reader io.Reader) (*Sound, error) {
data, err := io.ReadAll(reader)
if err != nil {
return nil, err
}
return NewSoundRaw(soundType, data), nil
}

func NewSoundRaw(soundType SoundType, src []byte) *Sound {
data := make([]byte, base64.StdEncoding.EncodedLen(len(src)))
base64.StdEncoding.Encode(data, src)
return &Sound{Type: soundType, Data: data}
}

type Sound struct {
Type SoundType
Data []byte
}

func (s Sound) MarshalJSON() ([]byte, error) {
return json.Marshal(s.String())
}

func (s Sound) String() string {
if len(s.Data) == 0 {
return ""
}
return s.Type.Header() + "," + string(s.Data)
}
Loading

0 comments on commit 03e9ee0

Please sign in to comment.