diff --git a/.editorconfig b/.editorconfig index cf6454ec8..2d8be7b92 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,7 @@ end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = false -max_line_length = 120 +max_line_length = 180 tab_width = 4 ij_continuation_indent_size = 8 ij_formatter_off_tag = @formatter:off @@ -16,11 +16,11 @@ ij_smart_tabs = false ij_visual_guides = none ij_wrap_on_typing = false -[{*.go,*.go2}] +[*.go] indent_style = tab ij_continuation_indent_size = 4 -ij_go_GROUP_CURRENT_PROJECT_IMPORTS = false -ij_go_add_leading_space_to_comments = false +ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true +ij_go_add_leading_space_to_comments = true ij_go_add_parentheses_for_single_import = false ij_go_call_parameters_new_line_after_left_paren = true ij_go_call_parameters_right_paren_on_new_line = true @@ -42,6 +42,8 @@ ij_go_wrap_func_params_newline_before_rparen = true ij_go_wrap_func_result = off ij_go_wrap_func_result_newline_after_lparen = true ij_go_wrap_func_result_newline_before_rparen = true +ij_go_local_group_mode = project +ij_go_run_go_fmt_on_reformat = true [{*.markdown,*.md}] indent_style = tab diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ef103f311..afa3f2e9a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -7,12 +7,6 @@ on: - '**/go.mod' - '**/go.sum' - '.github/workflows/go.yml' - pull_request_target: - paths: - - '**/*.go' - - '**/go.mod' - - '**/go.sum' - - '.github/workflows/go.yml' jobs: build: @@ -20,7 +14,7 @@ jobs: steps: - uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.24 - uses: actions/checkout@v4 - name: go build run: go build -v ./... @@ -30,7 +24,7 @@ jobs: steps: - uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.24 - uses: actions/checkout@v4 - name: go test run: go test -v ./... @@ -40,7 +34,7 @@ jobs: steps: - uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.24 - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v6 diff --git a/_examples/application_commands/gateway/example.go b/_examples/application_commands/gateway/example.go index e318e7ee8..f9e6f673b 100644 --- a/_examples/application_commands/gateway/example.go +++ b/_examples/application_commands/gateway/example.go @@ -7,11 +7,12 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/_examples/application_commands/http/example.go b/_examples/application_commands/http/example.go index 59e6fbc93..2e3300bc8 100644 --- a/_examples/application_commands/http/example.go +++ b/_examples/application_commands/http/example.go @@ -7,13 +7,14 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/snowflake/v2" + "github.com/oasisprotocol/curve25519-voi/primitives/ed25519" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/httpserver" - "github.com/disgoorg/snowflake/v2" - "github.com/oasisprotocol/curve25519-voi/primitives/ed25519" ) var ( diff --git a/_examples/application_commands/http/go.mod b/_examples/application_commands/http/go.mod index 14095cb5e..bc4d3acb9 100644 --- a/_examples/application_commands/http/go.mod +++ b/_examples/application_commands/http/go.mod @@ -1,19 +1,20 @@ module github.com/disgoorg/disgo/_examples/application_commands/http -go 1.21 +go 1.24 replace github.com/disgoorg/disgo => ../../../ require ( - github.com/disgoorg/disgo v0.18.14 + github.com/disgoorg/disgo v0.18.15 github.com/disgoorg/snowflake/v2 v2.0.3 github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a ) require ( - github.com/disgoorg/json v1.2.0 // indirect + github.com/disgoorg/json/v2 v2.0.0 // indirect + github.com/disgoorg/omit v1.0.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/sys v0.32.0 // indirect ) diff --git a/_examples/application_commands/http/go.sum b/_examples/application_commands/http/go.sum index 9dd3abd74..3a4812a17 100644 --- a/_examples/application_commands/http/go.sum +++ b/_examples/application_commands/http/go.sum @@ -1,7 +1,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/disgoorg/json v1.2.0 h1:6e/j4BCfSHIvucG1cd7tJPAOp1RgnnMFSqkvZUtEd1Y= -github.com/disgoorg/json v1.2.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= +github.com/disgoorg/json/v2 v2.0.0 h1:U16yy/ARK7/aEpzjjqK1b/KaqqGHozUdeVw/DViEzQI= +github.com/disgoorg/json/v2 v2.0.0/go.mod h1:jZTBC0nIE1WeetSEI3/Dka8g+qglb4FPVmp5I5HpEfI= +github.com/disgoorg/omit v1.0.0 h1:y0LkVUOyUHT8ZlnhIAeOZEA22UYykeysK8bLJ0SfT78= +github.com/disgoorg/omit v1.0.0/go.mod h1:RTmSARkf6PWT/UckwI0bV8XgWkWQoPppaT01rYKLcFQ= github.com/disgoorg/snowflake/v2 v2.0.3 h1:3B+PpFjr7j4ad7oeJu4RlQ+nYOTadsKapJIzgvSI2Ro= github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -14,9 +16,9 @@ github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxE github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/_examples/application_commands/localization/example.go b/_examples/application_commands/localization/example.go index c8fdfa8a5..b2021d148 100644 --- a/_examples/application_commands/localization/example.go +++ b/_examples/application_commands/localization/example.go @@ -7,11 +7,12 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/_examples/auto_moderation/example.go b/_examples/auto_moderation/example.go index 0e41558d9..11e38c902 100644 --- a/_examples/auto_moderation/example.go +++ b/_examples/auto_moderation/example.go @@ -9,13 +9,14 @@ import ( "syscall" "time" + "github.com/disgoorg/omit" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/gateway" - "github.com/disgoorg/json" - "github.com/disgoorg/snowflake/v2" ) var ( @@ -83,7 +84,7 @@ func showCaseAutoMod(client bot.Client) { Type: discord.AutoModerationActionTypeBlockMessage, }, }, - Enabled: json.Ptr(true), + Enabled: omit.Ptr(true), }) if err != nil { slog.Error("error while creating rule", slog.Any("err", err)) @@ -93,7 +94,7 @@ func showCaseAutoMod(client bot.Client) { time.Sleep(time.Second * 10) rule, err = client.Rest().UpdateAutoModerationRule(guildID, rule.ID, discord.AutoModerationRuleUpdate{ - Name: json.Ptr("test-rule-updated"), + Name: omit.Ptr("test-rule-updated"), TriggerMetadata: &discord.AutoModerationTriggerMetadata{ KeywordFilter: []string{"*test2*"}, }, diff --git a/_examples/componentsv2/example.go b/_examples/componentsv2/example.go new file mode 100644 index 000000000..71557db5c --- /dev/null +++ b/_examples/componentsv2/example.go @@ -0,0 +1,108 @@ +package main + +import ( + "bytes" + "context" + _ "embed" + "log/slog" + "os" + "os/signal" + "syscall" + + "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo" + "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/handler" +) + +var ( + token = os.Getenv("disgo_token") + guildID = snowflake.GetEnv("disgo_guild_id") + + //go:embed thumbnail.jpg + thumbnail []byte + + commands = []discord.ApplicationCommandCreate{ + discord.SlashCommandCreate{ + Name: "test", + Description: "test", + Options: []discord.ApplicationCommandOption{ + discord.ApplicationCommandOptionBool{ + Name: "ephemeral", + Description: "if the message should be ephemeral", + Required: false, + }, + }, + }, + } +) + +func main() { + slog.Info("starting example...") + slog.Info("disgo version", slog.String("version", disgo.Version)) + slog.SetLogLoggerLevel(slog.LevelDebug) + + client, err := disgo.New(token, + bot.WithDefaultGateway(), + bot.WithEventListenerFunc(onCommand), + ) + if err != nil { + slog.Error("error while building bot", slog.Any("err", err)) + return + } + defer client.Close(context.TODO()) + + if err = handler.SyncCommands(client, commands, []snowflake.ID{guildID}); err != nil { + slog.Error("error while syncing commands", slog.Any("err", err)) + } + + if err = client.OpenGateway(context.TODO()); err != nil { + slog.Error("error while connecting to gateway", slog.Any("err", err)) + return + } + + slog.Info("example is now running. Press CTRL-C to exit.") + s := make(chan os.Signal, 1) + signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + <-s +} + +func onCommand(e *events.ApplicationCommandInteractionCreate) { + switch data := e.Data.(type) { + case discord.SlashCommandInteractionData: + flags := discord.MessageFlagIsComponentsV2 + if ephemeral, ok := data.OptBool("ephemeral"); !ok || ephemeral { + flags = flags.Add(discord.MessageFlagEphemeral) + } + if err := e.CreateMessage(discord.MessageCreate{ + Flags: flags, + Components: []discord.LayoutComponent{ + discord.NewContainer( + discord.NewSection( + discord.NewTextDisplay("**Name: [Seeing Red](https://open.spotify.com/track/65qBr6ToDUjTD1RiE1H4Gl)**"), + discord.NewTextDisplay("**Artist: [Architects](https://open.spotify.com/artist/3ZztVuWxHzNpl0THurTFCv)**"), + discord.NewTextDisplay("**Album: [The Sky, The Earth & All Between](https://open.spotify.com/album/2W82VyyIFAXigJEiLm5TT1)**"), + ).WithAccessory(discord.NewThumbnail("attachment://thumbnail.png")), + discord.NewTextDisplay("`0:08`/`3:40`"), + discord.NewTextDisplay("[🔘▬▬▬▬▬▬▬▬▬]"), + discord.NewSmallSeparator(), + discord.NewActionRow( + discord.NewPrimaryButton("", "/player/previous").WithEmoji(discord.ComponentEmoji{Name: "⏮"}), + discord.NewPrimaryButton("", "/player/pause_play").WithEmoji(discord.ComponentEmoji{Name: "⏯"}), + discord.NewPrimaryButton("", "/player/next").WithEmoji(discord.ComponentEmoji{Name: "⏭"}), + discord.NewDangerButton("", "/player/stop").WithEmoji(discord.ComponentEmoji{Name: "⏹"}), + discord.NewPrimaryButton("", "/player/like").WithEmoji(discord.ComponentEmoji{Name: "❤️"}), + ), + ).WithAccentColor(0x5c5fea), + }, + Files: []*discord.File{ + discord.NewFile("thumbnail.png", "", bytes.NewReader(thumbnail)), + }, + }); err != nil { + slog.Error("error while sending message", slog.Any("err", err)) + } + } +} diff --git a/_examples/componentsv2/thumbnail.jpg b/_examples/componentsv2/thumbnail.jpg new file mode 100644 index 000000000..68a49cf63 Binary files /dev/null and b/_examples/componentsv2/thumbnail.jpg differ diff --git a/_examples/custom_cache/example.go b/_examples/custom_cache/example.go index bf2ed19c9..dd8ab9de9 100644 --- a/_examples/custom_cache/example.go +++ b/_examples/custom_cache/example.go @@ -9,12 +9,13 @@ import ( "syscall" "time" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/cache" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/gateway" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/_examples/echo/echo.go b/_examples/echo/echo.go index 080d05b56..0c9792c9e 100644 --- a/_examples/echo/echo.go +++ b/_examples/echo/echo.go @@ -10,12 +10,13 @@ import ( "syscall" "time" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/gateway" "github.com/disgoorg/disgo/voice" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/_examples/handler/example.go b/_examples/handler/example.go index 630231b84..26ef0dfca 100644 --- a/_examples/handler/example.go +++ b/_examples/handler/example.go @@ -7,12 +7,13 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/handler" "github.com/disgoorg/disgo/handler/middleware" - "github.com/disgoorg/snowflake/v2" ) var ( @@ -124,10 +125,10 @@ func handleVariableContent(event *handler.CommandEvent) error { func handlePing(event *handler.CommandEvent) error { return event.CreateMessage(discord.MessageCreate{ Content: "pong", - Components: []discord.ContainerComponent{ - discord.ActionRowComponent{ + Components: []discord.LayoutComponent{ + discord.NewActionRow( discord.NewPrimaryButton("button1", "/button1/testData"), - }, + ), }, }) } diff --git a/_examples/listening_events/example.go b/_examples/listening_events/example.go index 0d1102a48..ca5d30966 100644 --- a/_examples/listening_events/example.go +++ b/_examples/listening_events/example.go @@ -7,11 +7,12 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/_examples/oauth2/example.go b/_examples/oauth2/example.go index 184382870..3ba6bb5ad 100644 --- a/_examples/oauth2/example.go +++ b/_examples/oauth2/example.go @@ -3,21 +3,21 @@ package main import ( "fmt" "log/slog" - "math/rand" + "math/rand/v2" "net/http" "os" - "time" + + "github.com/disgoorg/json/v2" + "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/oauth2" "github.com/disgoorg/disgo/rest" - "github.com/disgoorg/json" - "github.com/disgoorg/snowflake/v2" ) var ( - letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") clientID = snowflake.GetEnv("client_id") clientSecret = os.Getenv("client_secret") baseURL = os.Getenv("base_url") @@ -26,10 +26,6 @@ var ( sessions map[string]oauth2.Session ) -func init() { - rand.Seed(time.Now().UnixNano()) -} - func main() { slog.Info("starting example...") slog.Info("disgo version", slog.String("version", disgo.Version)) @@ -119,9 +115,9 @@ func writeError(w http.ResponseWriter, text string, err error) { } func randStr(n int) string { - b := make([]rune, n) - for i := range b { - b[i] = letters[rand.Intn(len(letters))] + b := make([]byte, n) + for i := range n { + b[i] = letters[rand.IntN(len(letters))] } return string(b) } diff --git a/_examples/proxy/example.go b/_examples/proxy/example.go index dcb942ee8..bd27e2f15 100644 --- a/_examples/proxy/example.go +++ b/_examples/proxy/example.go @@ -7,6 +7,8 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" @@ -14,7 +16,6 @@ import ( "github.com/disgoorg/disgo/gateway" "github.com/disgoorg/disgo/rest" "github.com/disgoorg/disgo/sharding" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/_examples/test/examplebot.go b/_examples/test/examplebot.go index 0d72c9776..4cf513a2c 100644 --- a/_examples/test/examplebot.go +++ b/_examples/test/examplebot.go @@ -8,12 +8,13 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/cache" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/gateway" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/_examples/test/listeners.go b/_examples/test/listeners.go index 20d64ed01..09cbfeaf5 100644 --- a/_examples/test/listeners.go +++ b/_examples/test/listeners.go @@ -50,8 +50,8 @@ func componentListener(event *events.ComponentInteractionCreate) { _ = event.Modal(discord.ModalCreate{ CustomID: "test" + ids[1], Title: "Test" + ids[1] + " Modal", - Components: []discord.ContainerComponent{ - discord.ActionRowComponent{ + Components: []discord.LayoutComponent{ + discord.NewActionRow( discord.TextInputComponent{ CustomID: "test_input", Style: discord.TextInputStyleShort, @@ -60,7 +60,7 @@ func componentListener(event *events.ComponentInteractionCreate) { Placeholder: "test placeholder", Value: "uwu", }, - }, + ), }, }) diff --git a/_examples/verified_roles/main.go b/_examples/verified_roles/main.go index a053bd837..cf1da0969 100644 --- a/_examples/verified_roles/main.go +++ b/_examples/verified_roles/main.go @@ -2,20 +2,21 @@ package main import ( "log/slog" - "math/rand" + "math/rand/v2" "net/http" "os" "strconv" + "github.com/disgoorg/json/v2" + "github.com/disgoorg/omit" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/oauth2" - "github.com/disgoorg/json" ) var ( - letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") token = os.Getenv("disgo_token") clientSecret = os.Getenv("disgo_client_secret") baseURL = os.Getenv("disgo_base_url") @@ -79,10 +80,10 @@ func handleCallback(w http.ResponseWriter, r *http.Request) { } _, err = oAuth2Client.UpdateApplicationRoleConnection(session, client.ApplicationID(), discord.ApplicationRoleConnectionUpdate{ - PlatformName: json.Ptr("Cookie Monster " + user.Username), - PlatformUsername: json.Ptr("Cookie Monster " + user.Tag()), + PlatformName: omit.Ptr("Cookie Monster " + user.Username), + PlatformUsername: omit.Ptr("Cookie Monster " + user.Tag()), Metadata: &map[string]string{ - "cookies_eaten": strconv.Itoa(rand.Intn(100)), + "cookies_eaten": strconv.Itoa(rand.IntN(100)), }, }) if err != nil { diff --git a/_examples/voice/voice.go b/_examples/voice/voice.go index 90b71742a..7c8a65d68 100644 --- a/_examples/voice/voice.go +++ b/_examples/voice/voice.go @@ -10,12 +10,13 @@ import ( "syscall" "time" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/gateway" "github.com/disgoorg/disgo/voice" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/_examples/webhook/example.go b/_examples/webhook/example.go index 9cb1c3e5d..ce94d2718 100644 --- a/_examples/webhook/example.go +++ b/_examples/webhook/example.go @@ -7,11 +7,12 @@ import ( "sync" "time" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/rest" "github.com/disgoorg/disgo/webhook" - "github.com/disgoorg/snowflake/v2" ) var ( @@ -48,6 +49,7 @@ func send(wg *sync.WaitGroup, client webhook.Client, i int) { if _, err := client.CreateMessage(discord.NewWebhookMessageCreateBuilder(). SetContentf("test %d", i). Build(), + rest.CreateWebhookMessageParams{Wait: true}, // delay each request by 2 seconds rest.WithDelay(2*time.Second), ); err != nil { diff --git a/bot/client.go b/bot/client.go index eb5936512..6d0a9a19d 100644 --- a/bot/client.go +++ b/bot/client.go @@ -4,6 +4,8 @@ import ( "context" "log/slog" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo/cache" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/gateway" @@ -11,7 +13,6 @@ import ( "github.com/disgoorg/disgo/rest" "github.com/disgoorg/disgo/sharding" "github.com/disgoorg/disgo/voice" - "github.com/disgoorg/snowflake/v2" ) var _ Client = (*clientImpl)(nil) diff --git a/bot/config.go b/bot/config.go index 88d3ff700..65fb75f81 100644 --- a/bot/config.go +++ b/bot/config.go @@ -282,7 +282,7 @@ func BuildClient(token string, cfg *Config, gatewayEventHandlerFunc func(client } shardIDs := make([]int, gatewayBotRs.Shards) - for i := 0; i < gatewayBotRs.Shards; i++ { + for i := range gatewayBotRs.Shards { shardIDs[i] = i } diff --git a/bot/event_manager.go b/bot/event_manager.go index b8ea355f4..6c5e614ec 100644 --- a/bot/event_manager.go +++ b/bot/event_manager.go @@ -156,7 +156,7 @@ func (e *eventManagerImpl) DispatchEvent(event Event) { defer e.eventListenerMu.Unlock() for _, listener := range e.eventListeners { if e.asyncEventsEnabled { - go func(listener EventListener) { + go func() { defer func() { if r := recover(); r != nil { e.logger.Error("recovered from panic in event listener", slog.Any("arg", r), slog.String("stack", string(debug.Stack()))) @@ -164,7 +164,7 @@ func (e *eventManagerImpl) DispatchEvent(event Event) { } }() listener.OnEvent(event) - }(listener) + }() continue } listener.OnEvent(event) diff --git a/bot/member_chunking_manager.go b/bot/member_chunking_manager.go index 7be1646da..ee07af1ac 100644 --- a/bot/member_chunking_manager.go +++ b/bot/member_chunking_manager.go @@ -15,6 +15,7 @@ import ( var _ MemberChunkingManager = (*memberChunkingManagerImpl)(nil) +// ErrNoUserIDs is returned when no user IDs are provided to request members. var ErrNoUserIDs = errors.New("no user ids to request") // NewMemberChunkingManager returns a new MemberChunkingManager with the given MemberChunkingFilter. @@ -45,30 +46,17 @@ type MemberChunkingManager interface { // RequestMembers requests members from the given guildID and userIDs. // Notice: This action requires the gateway.IntentGuildMembers. - RequestMembers(guildID snowflake.ID, userIDs ...snowflake.ID) ([]discord.Member, error) + RequestMembers(ctx context.Context, guildID snowflake.ID, userIDs ...snowflake.ID) ([]discord.Member, error) // RequestAllMembers requests all members from the given guildID. // Notice: This action requires the gateway.IntentGuildMembers. - RequestAllMembers(guildID snowflake.ID) ([]discord.Member, error) + RequestAllMembers(ctx context.Context, guildID snowflake.ID) ([]discord.Member, error) // RequestMembersWithQuery requests members from the given guildID and query. // query : string the username starts with // Notice: This action requires the gateway.IntentGuildMembers. - RequestMembersWithQuery(guildID snowflake.ID, query string, limit int) ([]discord.Member, error) + RequestMembersWithQuery(ctx context.Context, guildID snowflake.ID, query string, limit int) ([]discord.Member, error) // RequestMembersWithFilter requests members from the given guildID and userIDs. memberFilterFunc is used to filter all returned members. // Notice: This action requires the gateway.IntentGuildMembers. - RequestMembersWithFilter(guildID snowflake.ID, memberFilterFunc func(member discord.Member) bool) ([]discord.Member, error) - - // RequestMembersCtx requests members from the given guildID and userIDs. - // Notice: This action requires the gateway.IntentGuildMembers. - RequestMembersCtx(ctx context.Context, guildID snowflake.ID, userIDs ...snowflake.ID) ([]discord.Member, error) - // RequestAllMembersCtx requests all members from the given guildID. - // Notice: This action requires the gateway.IntentGuildMembers. - RequestAllMembersCtx(ctx context.Context, guildID snowflake.ID) ([]discord.Member, error) - // RequestMembersWithQueryCtx requests members from the given guildID and query. - // Notice: This action requires the gateway.IntentGuildMembers. - RequestMembersWithQueryCtx(ctx context.Context, guildID snowflake.ID, query string, limit int) ([]discord.Member, error) - // RequestMembersWithFilterCtx requests members from the given guildID and userIDs. memberFilterFunc is used to filter all returned members. - // Notice: This action requires the gateway.IntentGuildMembers. - RequestMembersWithFilterCtx(ctx context.Context, guildID snowflake.ID, memberFilterFunc func(member discord.Member) bool) ([]discord.Member, error) + RequestMembersWithFilter(ctx context.Context, guildID snowflake.ID, memberFilterFunc func(member discord.Member) bool) ([]discord.Member, error) // RequestMembersChan requests members from the given guildID and userIDs. // Returns a channel which will receive the members. @@ -216,37 +204,24 @@ func (m *memberChunkingManagerImpl) requestGuildMembers(ctx context.Context, gui } } -func (m *memberChunkingManagerImpl) RequestMembers(guildID snowflake.ID, userIDs ...snowflake.ID) ([]discord.Member, error) { - return m.RequestMembersCtx(context.Background(), guildID, userIDs...) -} -func (m *memberChunkingManagerImpl) RequestMembersWithQuery(guildID snowflake.ID, query string, limit int) ([]discord.Member, error) { - return m.RequestMembersWithQueryCtx(context.Background(), guildID, query, limit) -} -func (m *memberChunkingManagerImpl) RequestAllMembers(guildID snowflake.ID) ([]discord.Member, error) { - return m.RequestAllMembersCtx(context.Background(), guildID) -} -func (m *memberChunkingManagerImpl) RequestMembersWithFilter(guildID snowflake.ID, memberFilterFunc func(member discord.Member) bool) ([]discord.Member, error) { - return m.RequestMembersWithFilterCtx(context.Background(), guildID, memberFilterFunc) -} - -func (m *memberChunkingManagerImpl) RequestMembersCtx(ctx context.Context, guildID snowflake.ID, userIDs ...snowflake.ID) ([]discord.Member, error) { +func (m *memberChunkingManagerImpl) RequestMembers(ctx context.Context, guildID snowflake.ID, userIDs ...snowflake.ID) ([]discord.Member, error) { if len(userIDs) == 0 { return nil, ErrNoUserIDs } return m.requestGuildMembers(ctx, guildID, nil, nil, userIDs, nil) } -func (m *memberChunkingManagerImpl) RequestMembersWithQueryCtx(ctx context.Context, guildID snowflake.ID, query string, limit int) ([]discord.Member, error) { +func (m *memberChunkingManagerImpl) RequestMembersWithQuery(ctx context.Context, guildID snowflake.ID, query string, limit int) ([]discord.Member, error) { return m.requestGuildMembers(ctx, guildID, &query, &limit, nil, nil) } -func (m *memberChunkingManagerImpl) RequestAllMembersCtx(ctx context.Context, guildID snowflake.ID) ([]discord.Member, error) { +func (m *memberChunkingManagerImpl) RequestAllMembers(ctx context.Context, guildID snowflake.ID) ([]discord.Member, error) { query := "" limit := 0 return m.requestGuildMembers(ctx, guildID, &query, &limit, nil, nil) } -func (m *memberChunkingManagerImpl) RequestMembersWithFilterCtx(ctx context.Context, guildID snowflake.ID, memberFilterFunc func(member discord.Member) bool) ([]discord.Member, error) { +func (m *memberChunkingManagerImpl) RequestMembersWithFilter(ctx context.Context, guildID snowflake.ID, memberFilterFunc func(member discord.Member) bool) ([]discord.Member, error) { query := "" limit := 0 return m.requestGuildMembers(ctx, guildID, &query, &limit, nil, memberFilterFunc) diff --git a/cache/cache.go b/cache/cache.go index 99dced2eb..31fcf2b43 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,6 +1,7 @@ package cache import ( + "iter" "sync" "github.com/disgoorg/snowflake/v2" @@ -27,8 +28,8 @@ type Cache[T any] interface { // Len returns the number of entities in the cache. Len() int - // ForEach calls the given function for each entity in the cache. - ForEach(func(entity T)) + // All returns an [iter.Seq] of all entities in the cache. + All() iter.Seq[T] } var _ Cache[any] = (*DefaultCache[any])(nil) @@ -99,11 +100,14 @@ func (c *DefaultCache[T]) Len() int { return len(c.cache) } -func (c *DefaultCache[T]) ForEach(forEachFunc func(entity T)) { - c.mu.RLock() - defer c.mu.RUnlock() - - for _, entity := range c.cache { - forEachFunc(entity) +func (c *DefaultCache[T]) All() iter.Seq[T] { + return func(yield func(T) bool) { + c.mu.RLock() + defer c.mu.RUnlock() + for _, entity := range c.cache { + if !yield(entity) { + break + } + } } } diff --git a/cache/cache_policy.go b/cache/cache_policy.go index a54e0587f..4412f3279 100644 --- a/cache/cache_policy.go +++ b/cache/cache_policy.go @@ -3,8 +3,9 @@ package cache import ( "slices" - "github.com/disgoorg/disgo/discord" "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo/discord" ) // PolicyNone returns a policy that will never cache anything. diff --git a/cache/caches.go b/cache/caches.go index dbc9cdfa9..06fca0051 100644 --- a/cache/caches.go +++ b/cache/caches.go @@ -1,6 +1,8 @@ package cache import ( + "iter" + "slices" "sync" "time" @@ -52,7 +54,7 @@ type GuildCache interface { UnavailableGuildIDs() []snowflake.ID Guild(guildID snowflake.ID) (discord.Guild, bool) - GuildsForEach(fn func(guild discord.Guild)) + Guilds() iter.Seq[discord.Guild] GuildsLen() int AddGuild(guild discord.Guild) RemoveGuild(guildID snowflake.ID) (discord.Guild, bool) @@ -90,9 +92,9 @@ func (c *guildCacheImpl) SetGuildUnready(guildID snowflake.ID, unready bool) { func (c *guildCacheImpl) UnreadyGuildIDs() []snowflake.ID { var guilds []snowflake.ID - c.unreadyGuilds.ForEach(func(guildID snowflake.ID) { + for guildID := range c.unreadyGuilds.All() { guilds = append(guilds, guildID) - }) + } return guilds } @@ -110,9 +112,9 @@ func (c *guildCacheImpl) SetGuildUnavailable(guildID snowflake.ID, unavailable b func (c *guildCacheImpl) UnavailableGuildIDs() []snowflake.ID { var guilds []snowflake.ID - c.unavailableGuilds.ForEach(func(guildID snowflake.ID) { - guilds = append(guilds, guildID) - }) + for guildId := range c.unavailableGuilds.All() { + guilds = append(guilds, guildId) + } return guilds } @@ -120,8 +122,8 @@ func (c *guildCacheImpl) Guild(guildID snowflake.ID) (discord.Guild, bool) { return c.cache.Get(guildID) } -func (c *guildCacheImpl) GuildsForEach(fn func(guild discord.Guild)) { - c.cache.ForEach(fn) +func (c *guildCacheImpl) Guilds() iter.Seq[discord.Guild] { + return c.cache.All() } func (c *guildCacheImpl) GuildsLen() int { @@ -140,7 +142,7 @@ type ChannelCache interface { ChannelCache() Cache[discord.GuildChannel] Channel(channelID snowflake.ID) (discord.GuildChannel, bool) - ChannelsForEach(fn func(channel discord.GuildChannel)) + Channels() iter.Seq[discord.GuildChannel] ChannelsLen() int AddChannel(channel discord.GuildChannel) RemoveChannel(channelID snowflake.ID) (discord.GuildChannel, bool) @@ -165,8 +167,8 @@ func (c *channelCacheImpl) Channel(channelID snowflake.ID) (discord.GuildChannel return c.cache.Get(channelID) } -func (c *channelCacheImpl) ChannelsForEach(fn func(channel discord.GuildChannel)) { - c.cache.ForEach(fn) +func (c *channelCacheImpl) Channels() iter.Seq[discord.GuildChannel] { + return c.cache.All() } func (c *channelCacheImpl) ChannelsLen() int { @@ -191,7 +193,7 @@ type StageInstanceCache interface { StageInstanceCache() GroupedCache[discord.StageInstance] StageInstance(guildID snowflake.ID, stageInstanceID snowflake.ID) (discord.StageInstance, bool) - StageInstanceForEach(guildID snowflake.ID, fn func(stageInstance discord.StageInstance)) + StageInstances(guildID snowflake.ID) iter.Seq[discord.StageInstance] StageInstancesAllLen() int StageInstancesLen(guildID snowflake.ID) int AddStageInstance(stageInstance discord.StageInstance) @@ -217,10 +219,8 @@ func (c *stageInstanceCacheImpl) StageInstance(guildID snowflake.ID, stageInstan return c.cache.Get(guildID, stageInstanceID) } -func (c *stageInstanceCacheImpl) StageInstanceForEach(guildID snowflake.ID, fn func(stageInstance discord.StageInstance)) { - c.cache.GroupForEach(guildID, func(stageInstance discord.StageInstance) { - fn(stageInstance) - }) +func (c *stageInstanceCacheImpl) StageInstances(guildID snowflake.ID) iter.Seq[discord.StageInstance] { + return c.cache.GroupAll(guildID) } func (c *stageInstanceCacheImpl) StageInstancesAllLen() int { @@ -245,8 +245,9 @@ func (c *stageInstanceCacheImpl) RemoveStageInstancesByGuildID(guildID snowflake type GuildScheduledEventCache interface { GuildScheduledEventCache() GroupedCache[discord.GuildScheduledEvent] + GuildScheduledEvent(guildID snowflake.ID, guildScheduledEventID snowflake.ID) (discord.GuildScheduledEvent, bool) - GuildScheduledEventsForEach(guildID snowflake.ID, fn func(guildScheduledEvent discord.GuildScheduledEvent)) + GuildScheduledEvents(guildID snowflake.ID) iter.Seq[discord.GuildScheduledEvent] GuildScheduledEventsAllLen() int GuildScheduledEventsLen(guildID snowflake.ID) int AddGuildScheduledEvent(guildScheduledEvent discord.GuildScheduledEvent) @@ -272,8 +273,8 @@ func (c *guildScheduledEventCacheImpl) GuildScheduledEvent(guildID snowflake.ID, return c.cache.Get(guildID, guildScheduledEventID) } -func (c *guildScheduledEventCacheImpl) GuildScheduledEventsForEach(guildID snowflake.ID, fn func(guildScheduledEvent discord.GuildScheduledEvent)) { - c.cache.GroupForEach(guildID, fn) +func (c *guildScheduledEventCacheImpl) GuildScheduledEvents(guildID snowflake.ID) iter.Seq[discord.GuildScheduledEvent] { + return c.cache.GroupAll(guildID) } func (c *guildScheduledEventCacheImpl) GuildScheduledEventsAllLen() int { @@ -299,7 +300,7 @@ func (c *guildScheduledEventCacheImpl) RemoveGuildScheduledEventsByGuildID(guild 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)) + GuildSoundboardSounds(guildID snowflake.ID) iter.Seq[discord.SoundboardSound] GuildSoundboardSoundsAllLen() int GuildSoundboardSoundsLen(guildID snowflake.ID) int AddGuildSoundboardSound(sound discord.SoundboardSound) @@ -325,8 +326,8 @@ func (c *guildSoundboardSoundCacheImpl) GuildSoundboardSound(guildID snowflake.I 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) GuildSoundboardSounds(guildID snowflake.ID) iter.Seq[discord.SoundboardSound] { + return c.cache.GroupAll(guildID) } func (c *guildSoundboardSoundCacheImpl) GuildSoundboardSoundsAllLen() int { @@ -353,7 +354,7 @@ type RoleCache interface { RoleCache() GroupedCache[discord.Role] Role(guildID snowflake.ID, roleID snowflake.ID) (discord.Role, bool) - RolesForEach(guildID snowflake.ID, fn func(role discord.Role)) + Roles(guildID snowflake.ID) iter.Seq[discord.Role] RolesAllLen() int RolesLen(guildID snowflake.ID) int AddRole(role discord.Role) @@ -379,8 +380,8 @@ func (c *roleCacheImpl) Role(guildID snowflake.ID, roleID snowflake.ID) (discord return c.cache.Get(guildID, roleID) } -func (c *roleCacheImpl) RolesForEach(guildID snowflake.ID, fn func(role discord.Role)) { - c.cache.GroupForEach(guildID, fn) +func (c *roleCacheImpl) Roles(guildID snowflake.ID) iter.Seq[discord.Role] { + return c.cache.GroupAll(guildID) } func (c *roleCacheImpl) RolesAllLen() int { @@ -407,7 +408,7 @@ type MemberCache interface { MemberCache() GroupedCache[discord.Member] Member(guildID snowflake.ID, userID snowflake.ID) (discord.Member, bool) - MembersForEach(guildID snowflake.ID, fn func(member discord.Member)) + Members(guildID snowflake.ID) iter.Seq[discord.Member] MembersAllLen() int MembersLen(guildID snowflake.ID) int AddMember(member discord.Member) @@ -433,8 +434,8 @@ func (c *memberCacheImpl) Member(guildID snowflake.ID, userID snowflake.ID) (dis return c.cache.Get(guildID, userID) } -func (c *memberCacheImpl) MembersForEach(guildID snowflake.ID, fn func(member discord.Member)) { - c.cache.GroupForEach(guildID, fn) +func (c *memberCacheImpl) Members(guildID snowflake.ID) iter.Seq[discord.Member] { + return c.cache.GroupAll(guildID) } func (c *memberCacheImpl) MembersAllLen() int { @@ -461,7 +462,7 @@ type ThreadMemberCache interface { ThreadMemberCache() GroupedCache[discord.ThreadMember] ThreadMember(threadID snowflake.ID, userID snowflake.ID) (discord.ThreadMember, bool) - ThreadMemberForEach(threadID snowflake.ID, fn func(threadMember discord.ThreadMember)) + ThreadMembers(threadID snowflake.ID) iter.Seq[discord.ThreadMember] ThreadMembersAllLen() int ThreadMembersLen(guildID snowflake.ID) int AddThreadMember(threadMember discord.ThreadMember) @@ -487,10 +488,8 @@ func (c *threadMemberCacheImpl) ThreadMember(threadID snowflake.ID, userID snowf return c.cache.Get(threadID, userID) } -func (c *threadMemberCacheImpl) ThreadMemberForEach(threadID snowflake.ID, fn func(threadMember discord.ThreadMember)) { - c.cache.GroupForEach(threadID, func(threadMember discord.ThreadMember) { - fn(threadMember) - }) +func (c *threadMemberCacheImpl) ThreadMembers(threadID snowflake.ID) iter.Seq[discord.ThreadMember] { + return c.cache.GroupAll(threadID) } func (c *threadMemberCacheImpl) ThreadMembersAllLen() int { @@ -517,7 +516,7 @@ type PresenceCache interface { PresenceCache() GroupedCache[discord.Presence] Presence(guildID snowflake.ID, userID snowflake.ID) (discord.Presence, bool) - PresenceForEach(guildID snowflake.ID, fn func(presence discord.Presence)) + Presences(guildID snowflake.ID) iter.Seq[discord.Presence] PresencesAllLen() int PresencesLen(guildID snowflake.ID) int AddPresence(presence discord.Presence) @@ -543,10 +542,8 @@ func (c *presenceCacheImpl) Presence(guildID snowflake.ID, userID snowflake.ID) return c.cache.Get(guildID, userID) } -func (c *presenceCacheImpl) PresenceForEach(guildID snowflake.ID, fn func(presence discord.Presence)) { - c.cache.GroupForEach(guildID, func(presence discord.Presence) { - fn(presence) - }) +func (c *presenceCacheImpl) Presences(guildID snowflake.ID) iter.Seq[discord.Presence] { + return c.cache.GroupAll(guildID) } func (c *presenceCacheImpl) PresencesAllLen() int { @@ -573,7 +570,7 @@ type VoiceStateCache interface { VoiceStateCache() GroupedCache[discord.VoiceState] VoiceState(guildID snowflake.ID, userID snowflake.ID) (discord.VoiceState, bool) - VoiceStatesForEach(guildID snowflake.ID, fn func(discord.VoiceState)) + VoiceStates(guildID snowflake.ID) iter.Seq[discord.VoiceState] VoiceStatesAllLen() int VoiceStatesLen(guildID snowflake.ID) int AddVoiceState(voiceState discord.VoiceState) @@ -599,8 +596,8 @@ func (c *voiceStateCacheImpl) VoiceState(guildID snowflake.ID, userID snowflake. return c.cache.Get(guildID, userID) } -func (c *voiceStateCacheImpl) VoiceStatesForEach(guildID snowflake.ID, fn func(discord.VoiceState)) { - c.cache.GroupForEach(guildID, fn) +func (c *voiceStateCacheImpl) VoiceStates(guildID snowflake.ID) iter.Seq[discord.VoiceState] { + return c.cache.GroupAll(guildID) } func (c *voiceStateCacheImpl) VoiceStatesAllLen() int { @@ -627,7 +624,7 @@ type MessageCache interface { MessageCache() GroupedCache[discord.Message] Message(channelID snowflake.ID, messageID snowflake.ID) (discord.Message, bool) - MessagesForEach(channelID snowflake.ID, fn func(message discord.Message)) + Messages(channelID snowflake.ID) iter.Seq[discord.Message] MessagesAllLen() int MessagesLen(guildID snowflake.ID) int AddMessage(message discord.Message) @@ -654,8 +651,8 @@ func (c *messageCacheImpl) Message(channelID snowflake.ID, messageID snowflake.I return c.cache.Get(channelID, messageID) } -func (c *messageCacheImpl) MessagesForEach(channelID snowflake.ID, fn func(message discord.Message)) { - c.cache.GroupForEach(channelID, fn) +func (c *messageCacheImpl) Messages(channelID snowflake.ID) iter.Seq[discord.Message] { + return c.cache.GroupAll(channelID) } func (c *messageCacheImpl) MessagesAllLen() int { @@ -688,7 +685,7 @@ type EmojiCache interface { EmojiCache() GroupedCache[discord.Emoji] Emoji(guildID snowflake.ID, emojiID snowflake.ID) (discord.Emoji, bool) - EmojisForEach(guildID snowflake.ID, fn func(emoji discord.Emoji)) + Emojis(guildID snowflake.ID) iter.Seq[discord.Emoji] EmojisAllLen() int EmojisLen(guildID snowflake.ID) int AddEmoji(emoji discord.Emoji) @@ -714,8 +711,8 @@ func (c *emojiCacheImpl) Emoji(guildID snowflake.ID, emojiID snowflake.ID) (disc return c.cache.Get(guildID, emojiID) } -func (c *emojiCacheImpl) EmojisForEach(guildID snowflake.ID, fn func(emoji discord.Emoji)) { - c.cache.GroupForEach(guildID, fn) +func (c *emojiCacheImpl) Emojis(guildID snowflake.ID) iter.Seq[discord.Emoji] { + return c.cache.GroupAll(guildID) } func (c *emojiCacheImpl) EmojisAllLen() int { @@ -742,7 +739,7 @@ type StickerCache interface { StickerCache() GroupedCache[discord.Sticker] Sticker(guildID snowflake.ID, stickerID snowflake.ID) (discord.Sticker, bool) - StickersForEach(guildID snowflake.ID, fn func(sticker discord.Sticker)) + Stickers(guildID snowflake.ID) iter.Seq[discord.Sticker] StickersAllLen() int StickersLen(guildID snowflake.ID) int AddSticker(sticker discord.Sticker) @@ -768,8 +765,8 @@ func (c *stickerCacheImpl) Sticker(guildID snowflake.ID, stickerID snowflake.ID) return c.cache.Get(guildID, stickerID) } -func (c *stickerCacheImpl) StickersForEach(guildID snowflake.ID, fn func(sticker discord.Sticker)) { - c.cache.GroupForEach(guildID, fn) +func (c *stickerCacheImpl) Stickers(guildID snowflake.ID) iter.Seq[discord.Sticker] { + return c.cache.GroupAll(guildID) } func (c *stickerCacheImpl) StickersAllLen() int { @@ -1009,23 +1006,22 @@ func (c *cachesImpl) MemberPermissionsInChannel(channel discord.GuildChannel, me func (c *cachesImpl) MemberRoles(member discord.Member) []discord.Role { var roles []discord.Role - c.RolesForEach(member.GuildID, func(role discord.Role) { - for _, roleID := range member.RoleIDs { - if roleID == role.ID { - roles = append(roles, role) - } + + for role := range c.Roles(member.GuildID) { + if slices.Contains(member.RoleIDs, role.ID) { + roles = append(roles, role) } - }) + } return roles } func (c *cachesImpl) AudioChannelMembers(channel discord.GuildAudioChannel) []discord.Member { var members []discord.Member - c.VoiceStatesForEach(channel.GuildID(), func(state discord.VoiceState) { + for state := range c.VoiceStates(channel.GuildID()) { if member, ok := c.Member(channel.GuildID(), state.UserID); ok && state.ChannelID != nil && *state.ChannelID == channel.ID() { members = append(members, member) } - }) + } return members } @@ -1039,11 +1035,11 @@ func (c *cachesImpl) SelfMember(guildID snowflake.ID) (discord.Member, bool) { func (c *cachesImpl) GuildThreadsInChannel(channelID snowflake.ID) []discord.GuildThread { var threads []discord.GuildThread - c.ChannelsForEach(func(channel discord.GuildChannel) { + for channel := range c.Channels() { if thread, ok := channel.(discord.GuildThread); ok && *thread.ParentID() == channelID { threads = append(threads, thread) } - }) + } return threads } diff --git a/cache/grouped_cache.go b/cache/grouped_cache.go index 0795c6713..72ff91492 100644 --- a/cache/grouped_cache.go +++ b/cache/grouped_cache.go @@ -1,6 +1,7 @@ package cache import ( + "iter" "sync" "github.com/disgoorg/snowflake/v2" @@ -36,11 +37,11 @@ type GroupedCache[T any] interface { // GroupLen returns the number of entities in the cache within the groupID. GroupLen(groupID snowflake.ID) int - // ForEach calls the given function for each entity in the cache. - ForEach(func(groupID snowflake.ID, entity T)) + // All returns an [iter.Seq2] of all entities in the cache. + All() iter.Seq2[snowflake.ID, T] - // GroupForEach calls the given function for each entity in the cache within the groupID. - GroupForEach(groupID snowflake.ID, forEachFunc func(entity T)) + // GroupAll returns an [iter.Seq] of all entities in the cache within the groupID. + GroupAll(groupID snowflake.ID) iter.Seq[T] } var _ GroupedCache[any] = (*defaultGroupedCache[any])(nil) @@ -166,21 +167,32 @@ func (c *defaultGroupedCache[T]) GroupLen(groupID snowflake.ID) int { return 0 } -func (c *defaultGroupedCache[T]) ForEach(forEachFunc func(groupID snowflake.ID, entity T)) { - c.mu.RLock() - defer c.mu.RUnlock() +func (c *defaultGroupedCache[T]) All() iter.Seq2[snowflake.ID, T] { + return func(yield func(snowflake.ID, T) bool) { + c.mu.RLock() + defer c.mu.RUnlock() - for groupID, groupEntities := range c.cache { - for _, entity := range groupEntities { - forEachFunc(groupID, entity) + for groupID, groupEntities := range c.cache { + for _, entity := range groupEntities { + if !yield(groupID, entity) { + return + } + } } } } -func (c *defaultGroupedCache[T]) GroupForEach(groupID snowflake.ID, forEachFunc func(entity T)) { - c.mu.RLock() - defer c.mu.RUnlock() - for _, entity := range c.cache[groupID] { - forEachFunc(entity) +func (c *defaultGroupedCache[T]) GroupAll(groupID snowflake.ID) iter.Seq[T] { + return func(yield func(T) bool) { + c.mu.RLock() + defer c.mu.RUnlock() + + if groupEntities, ok := c.cache[groupID]; ok { + for _, entity := range groupEntities { + if !yield(entity) { + return + } + } + } } } diff --git a/cache/set.go b/cache/set.go index c37af70f4..fe81a8407 100644 --- a/cache/set.go +++ b/cache/set.go @@ -1,6 +1,9 @@ package cache -import "sync" +import ( + "iter" + "sync" +) // Set is a collection of unique items. It should be thread-safe. type Set[T comparable] interface { @@ -14,8 +17,8 @@ type Set[T comparable] interface { Len() int // Clear removes all items from the set. Clear() - // ForEach iterates over the items in the set. - ForEach(f func(item T)) + // All returns an iterator over all items in the set. + All() iter.Seq[T] } type set[T comparable] struct { @@ -61,10 +64,14 @@ func (s *set[T]) Clear() { s.items = make(map[T]struct{}) } -func (s *set[T]) ForEach(f func(item T)) { - s.mu.RLock() - defer s.mu.RUnlock() - for item := range s.items { - f(item) +func (s *set[T]) All() iter.Seq[T] { + return func(yield func(T) bool) { + s.mu.RLock() + defer s.mu.RUnlock() + for item := range s.items { + if !yield(item) { + return + } + } } } diff --git a/discord/access_token.go b/discord/access_token.go index 55fc9bdee..aef80bb17 100644 --- a/discord/access_token.go +++ b/discord/access_token.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" ) // AccessTokenResponse is the response from the OAuth2 exchange endpoint. diff --git a/discord/activity.go b/discord/activity.go index 8e0a91967..0fd2a250c 100644 --- a/discord/activity.go +++ b/discord/activity.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/internal/flags" diff --git a/discord/application.go b/discord/application.go index 69c1fdeaa..051f6ebc7 100644 --- a/discord/application.go +++ b/discord/application.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/internal/flags" @@ -68,8 +68,8 @@ type ApplicationUpdate struct { RoleConnectionsVerificationURL *string `json:"role_connections_verification_url,omitempty"` InstallParams *InstallParams `json:"install_params,omitempty"` Flags *ApplicationFlags `json:"flags,omitempty"` - Icon *json.Nullable[Icon] `json:"icon,omitempty"` - CoverImage *json.Nullable[Icon] `json:"cover_image,omitempty"` + Icon omit.Omit[*Icon] `json:"icon,omitzero"` + CoverImage omit.Omit[*Icon] `json:"cover_image,omitzero"` InteractionsEndpointURL *string `json:"interactions_endpoint_url,omitempty"` Tags []string `json:"tags,omitempty"` IntegrationTypesConfig *ApplicationIntegrationTypesConfig `json:"integration_types_config,omitempty"` diff --git a/discord/application_command.go b/discord/application_command.go index faa9bb15b..7faf45515 100644 --- a/discord/application_command.go +++ b/discord/application_command.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/application_command_autocomplete_option.go b/discord/application_command_autocomplete_option.go index 20dbc135f..d4b069c02 100644 --- a/discord/application_command_autocomplete_option.go +++ b/discord/application_command_autocomplete_option.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" ) type internalAutocompleteOption interface { diff --git a/discord/application_command_create.go b/discord/application_command_create.go index cbe04f18e..44b5c6c5d 100644 --- a/discord/application_command_create.go +++ b/discord/application_command_create.go @@ -1,6 +1,9 @@ package discord -import "github.com/disgoorg/json" +import ( + "github.com/disgoorg/json/v2" + "github.com/disgoorg/omit" +) type ApplicationCommandCreate interface { json.Marshaler @@ -10,17 +13,15 @@ type ApplicationCommandCreate interface { } type SlashCommandCreate struct { - Name string `json:"name"` - NameLocalizations map[Locale]string `json:"name_localizations,omitempty"` - Description string `json:"description"` - DescriptionLocalizations map[Locale]string `json:"description_localizations,omitempty"` - Options []ApplicationCommandOption `json:"options,omitempty"` - DefaultMemberPermissions *json.Nullable[Permissions] `json:"default_member_permissions,omitempty"` // different behavior for 0 and null, optional - // Deprecated: Use Contexts instead - DMPermission *bool `json:"dm_permission,omitempty"` - IntegrationTypes []ApplicationIntegrationType `json:"integration_types,omitempty"` - Contexts []InteractionContextType `json:"contexts,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` + Name string `json:"name"` + NameLocalizations map[Locale]string `json:"name_localizations,omitempty"` + Description string `json:"description"` + DescriptionLocalizations map[Locale]string `json:"description_localizations,omitempty"` + Options []ApplicationCommandOption `json:"options,omitempty"` + DefaultMemberPermissions omit.Omit[*Permissions] `json:"default_member_permissions,omitzero"` // different behavior for 0 and null, optional + IntegrationTypes []ApplicationIntegrationType `json:"integration_types,omitempty"` + Contexts []InteractionContextType `json:"contexts,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` } func (c SlashCommandCreate) MarshalJSON() ([]byte, error) { @@ -45,14 +46,12 @@ func (c SlashCommandCreate) CommandName() string { func (SlashCommandCreate) applicationCommandCreate() {} type UserCommandCreate struct { - Name string `json:"name"` - NameLocalizations map[Locale]string `json:"name_localizations,omitempty"` - DefaultMemberPermissions *json.Nullable[Permissions] `json:"default_member_permissions,omitempty"` - // Deprecated: Use Contexts instead - DMPermission *bool `json:"dm_permission,omitempty"` - IntegrationTypes []ApplicationIntegrationType `json:"integration_types,omitempty"` - Contexts []InteractionContextType `json:"contexts,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` + Name string `json:"name"` + NameLocalizations map[Locale]string `json:"name_localizations,omitempty"` + DefaultMemberPermissions omit.Omit[*Permissions] `json:"default_member_permissions,omitzero"` + IntegrationTypes []ApplicationIntegrationType `json:"integration_types,omitempty"` + Contexts []InteractionContextType `json:"contexts,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` } func (c UserCommandCreate) MarshalJSON() ([]byte, error) { @@ -77,14 +76,12 @@ func (c UserCommandCreate) CommandName() string { func (UserCommandCreate) applicationCommandCreate() {} type MessageCommandCreate struct { - Name string `json:"name"` - NameLocalizations map[Locale]string `json:"name_localizations,omitempty"` - DefaultMemberPermissions *json.Nullable[Permissions] `json:"default_member_permissions,omitempty"` - // Deprecated: Use Contexts instead - DMPermission *bool `json:"dm_permission,omitempty"` - IntegrationTypes []ApplicationIntegrationType `json:"integration_types,omitempty"` - Contexts []InteractionContextType `json:"contexts,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` + Name string `json:"name"` + NameLocalizations map[Locale]string `json:"name_localizations,omitempty"` + DefaultMemberPermissions omit.Omit[*Permissions] `json:"default_member_permissions,omitzero"` + IntegrationTypes []ApplicationIntegrationType `json:"integration_types,omitempty"` + Contexts []InteractionContextType `json:"contexts,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` } func (c MessageCommandCreate) MarshalJSON() ([]byte, error) { @@ -109,15 +106,13 @@ func (c MessageCommandCreate) CommandName() string { func (MessageCommandCreate) applicationCommandCreate() {} type EntryPointCommandCreate struct { - Name string `json:"name"` - NameLocalizations map[Locale]string `json:"name_localizations,omitempty"` - DefaultMemberPermissions *json.Nullable[Permissions] `json:"default_member_permissions,omitempty"` - // Deprecated: Use Contexts instead - DMPermission *bool `json:"dm_permission,omitempty"` - IntegrationTypes []ApplicationIntegrationType `json:"integration_types,omitempty"` - Contexts []InteractionContextType `json:"contexts,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` - Handler EntryPointCommandHandlerType `json:"handler,omitempty"` + Name string `json:"name"` + NameLocalizations map[Locale]string `json:"name_localizations,omitempty"` + DefaultMemberPermissions omit.Omit[*Permissions] `json:"default_member_permissions,omitzero"` + IntegrationTypes []ApplicationIntegrationType `json:"integration_types,omitempty"` + Contexts []InteractionContextType `json:"contexts,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` + Handler EntryPointCommandHandlerType `json:"handler,omitempty"` } func (c EntryPointCommandCreate) MarshalJSON() ([]byte, error) { diff --git a/discord/application_command_option.go b/discord/application_command_option.go index 0647b8913..a612bfb83 100644 --- a/discord/application_command_option.go +++ b/discord/application_command_option.go @@ -3,7 +3,7 @@ package discord import ( "fmt" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" ) // ApplicationCommandOptionType specifies the type of the arguments used in ApplicationCommand.Options diff --git a/discord/application_command_permission.go b/discord/application_command_permission.go index 75277a3ec..de91a81bd 100644 --- a/discord/application_command_permission.go +++ b/discord/application_command_permission.go @@ -3,7 +3,7 @@ package discord import ( "fmt" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/application_command_raw.go b/discord/application_command_raw.go index aee24e8b8..9ad7f1ffc 100644 --- a/discord/application_command_raw.go +++ b/discord/application_command_raw.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/application_command_update.go b/discord/application_command_update.go index 97904cf28..364cdd7ef 100644 --- a/discord/application_command_update.go +++ b/discord/application_command_update.go @@ -1,6 +1,9 @@ package discord -import "github.com/disgoorg/json" +import ( + "github.com/disgoorg/json/v2" + "github.com/disgoorg/omit" +) type ApplicationCommandUpdate interface { json.Marshaler @@ -10,17 +13,15 @@ type ApplicationCommandUpdate interface { } type SlashCommandUpdate struct { - Name *string `json:"name,omitempty"` - NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` - Description *string `json:"description,omitempty"` - DescriptionLocalizations *map[Locale]string `json:"description_localizations,omitempty"` - Options *[]ApplicationCommandOption `json:"options,omitempty"` - DefaultMemberPermissions *json.Nullable[Permissions] `json:"default_member_permissions,omitempty"` - // Deprecated: Use Contexts instead - DMPermission *bool `json:"dm_permission,omitempty"` - IntegrationTypes *[]ApplicationIntegrationType `json:"integration_types,omitempty"` - Contexts *[]InteractionContextType `json:"contexts,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` + Name *string `json:"name,omitempty"` + NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` + Description *string `json:"description,omitempty"` + DescriptionLocalizations *map[Locale]string `json:"description_localizations,omitempty"` + Options *[]ApplicationCommandOption `json:"options,omitempty"` + DefaultMemberPermissions omit.Omit[*Permissions] `json:"default_member_permissions,omitzero"` + IntegrationTypes *[]ApplicationIntegrationType `json:"integration_types,omitempty"` + Contexts *[]InteractionContextType `json:"contexts,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` } func (c SlashCommandUpdate) MarshalJSON() ([]byte, error) { @@ -45,14 +46,12 @@ func (c SlashCommandUpdate) CommandName() *string { func (SlashCommandUpdate) applicationCommandUpdate() {} type UserCommandUpdate struct { - Name *string `json:"name,omitempty"` - NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` - DefaultMemberPermissions *json.Nullable[Permissions] `json:"default_member_permissions,omitempty"` - // Deprecated: Use Contexts instead - DMPermission *bool `json:"dm_permission,omitempty"` - IntegrationTypes *[]ApplicationIntegrationType `json:"integration_types,omitempty"` - Contexts *[]InteractionContextType `json:"contexts,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` + Name *string `json:"name,omitempty"` + NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` + DefaultMemberPermissions omit.Omit[*Permissions] `json:"default_member_permissions,omitzero"` + IntegrationTypes *[]ApplicationIntegrationType `json:"integration_types,omitempty"` + Contexts *[]InteractionContextType `json:"contexts,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` } func (c UserCommandUpdate) MarshalJSON() ([]byte, error) { @@ -77,14 +76,12 @@ func (c UserCommandUpdate) CommandName() *string { func (UserCommandUpdate) applicationCommandUpdate() {} type MessageCommandUpdate struct { - Name *string `json:"name,omitempty"` - NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` - DefaultMemberPermissions *json.Nullable[Permissions] `json:"default_member_permissions,omitempty"` - // Deprecated: Use Contexts instead - DMPermission *bool `json:"dm_permission,omitempty"` - IntegrationTypes *[]ApplicationIntegrationType `json:"integration_types,omitempty"` - Contexts *[]InteractionContextType `json:"contexts,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` + Name *string `json:"name,omitempty"` + NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` + DefaultMemberPermissions omit.Omit[*Permissions] `json:"default_member_permissions,omitzero"` + IntegrationTypes *[]ApplicationIntegrationType `json:"integration_types,omitempty"` + Contexts *[]InteractionContextType `json:"contexts,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` } func (c MessageCommandUpdate) MarshalJSON() ([]byte, error) { @@ -109,15 +106,13 @@ func (c MessageCommandUpdate) CommandName() *string { func (MessageCommandUpdate) applicationCommandUpdate() {} type EntryPointCommandUpdate struct { - Name *string `json:"name,omitempty"` - NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` - DefaultMemberPermissions *json.Nullable[Permissions] `json:"default_member_permissions,omitempty"` - // Deprecated: Use Contexts instead - DMPermission *bool `json:"dm_permission,omitempty"` - IntegrationTypes *[]ApplicationIntegrationType `json:"integration_types,omitempty"` - Contexts *[]InteractionContextType `json:"contexts,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` - Handler *EntryPointCommandHandlerType `json:"handler,omitempty"` + Name *string `json:"name,omitempty"` + NameLocalizations *map[Locale]string `json:"name_localizations,omitempty"` + DefaultMemberPermissions omit.Omit[*Permissions] `json:"default_member_permissions,omitzero"` + IntegrationTypes *[]ApplicationIntegrationType `json:"integration_types,omitempty"` + Contexts *[]InteractionContextType `json:"contexts,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` + Handler *EntryPointCommandHandlerType `json:"handler,omitempty"` } func (c EntryPointCommandUpdate) MarshalJSON() ([]byte, error) { diff --git a/discord/audit_log.go b/discord/audit_log.go index 50b5b4dec..1eb901ee2 100644 --- a/discord/audit_log.go +++ b/discord/audit_log.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/channel.go b/discord/channel.go index 627e5cf47..c8c158e81 100644 --- a/discord/channel.go +++ b/discord/channel.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/internal/flags" diff --git a/discord/channel_create.go b/discord/channel_create.go index 63e99b2f3..33f41dc92 100644 --- a/discord/channel_create.go +++ b/discord/channel_create.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/channel_update.go b/discord/channel_update.go index 975c78d0f..518d17f85 100644 --- a/discord/channel_update.go +++ b/discord/channel_update.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" ) @@ -98,37 +98,37 @@ func (GuildStageVoiceChannelUpdate) channelUpdate() {} func (GuildStageVoiceChannelUpdate) guildChannelUpdate() {} type GuildForumChannelUpdate struct { - Name *string `json:"name,omitempty"` - Position *int `json:"position,omitempty"` - Topic *string `json:"topic,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` - PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` - ParentID *snowflake.ID `json:"parent_id,omitempty"` - RateLimitPerUser *int `json:"rate_limit_per_user"` - AvailableTags *[]ChannelTag `json:"available_tags,omitempty"` - Flags *ChannelFlags `json:"flags,omitempty"` - DefaultReactionEmoji *json.Nullable[DefaultReactionEmoji] `json:"default_reaction_emoji,omitempty"` - DefaultThreadRateLimitPerUser *int `json:"default_thread_rate_limit_per_user,omitempty"` - DefaultSortOrder *json.Nullable[DefaultSortOrder] `json:"default_sort_order,omitempty"` - DefaultForumLayout *json.Nullable[DefaultForumLayout] `json:"default_forum_layout,omitempty"` + Name *string `json:"name,omitempty"` + Position *int `json:"position,omitempty"` + Topic *string `json:"topic,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` + PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID *snowflake.ID `json:"parent_id,omitempty"` + RateLimitPerUser *int `json:"rate_limit_per_user"` + AvailableTags *[]ChannelTag `json:"available_tags,omitempty"` + Flags *ChannelFlags `json:"flags,omitempty"` + DefaultReactionEmoji omit.Omit[*DefaultReactionEmoji] `json:"default_reaction_emoji,omitzero"` + DefaultThreadRateLimitPerUser *int `json:"default_thread_rate_limit_per_user,omitempty"` + DefaultSortOrder omit.Omit[*DefaultSortOrder] `json:"default_sort_order,omitzero"` + DefaultForumLayout omit.Omit[*DefaultForumLayout] `json:"default_forum_layout,omitzero"` } func (GuildForumChannelUpdate) channelUpdate() {} func (GuildForumChannelUpdate) guildChannelUpdate() {} type GuildMediaChannelUpdate struct { - Name *string `json:"name,omitempty"` - Position *int `json:"position,omitempty"` - Topic *string `json:"topic,omitempty"` - NSFW *bool `json:"nsfw,omitempty"` - PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` - ParentID *snowflake.ID `json:"parent_id,omitempty"` - RateLimitPerUser *int `json:"rate_limit_per_user"` - AvailableTags *[]ChannelTag `json:"available_tags,omitempty"` - Flags *ChannelFlags `json:"flags,omitempty"` - DefaultReactionEmoji *json.Nullable[DefaultReactionEmoji] `json:"default_reaction_emoji,omitempty"` - DefaultThreadRateLimitPerUser *int `json:"default_thread_rate_limit_per_user,omitempty"` - DefaultSortOrder *json.Nullable[DefaultSortOrder] `json:"default_sort_order,omitempty"` + Name *string `json:"name,omitempty"` + Position *int `json:"position,omitempty"` + Topic *string `json:"topic,omitempty"` + NSFW *bool `json:"nsfw,omitempty"` + PermissionOverwrites *[]PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID *snowflake.ID `json:"parent_id,omitempty"` + RateLimitPerUser *int `json:"rate_limit_per_user"` + AvailableTags *[]ChannelTag `json:"available_tags,omitempty"` + Flags *ChannelFlags `json:"flags,omitempty"` + DefaultReactionEmoji omit.Omit[*DefaultReactionEmoji] `json:"default_reaction_emoji,omitzero"` + DefaultThreadRateLimitPerUser *int `json:"default_thread_rate_limit_per_user,omitempty"` + DefaultSortOrder omit.Omit[*DefaultSortOrder] `json:"default_sort_order,omitzero"` } func (GuildMediaChannelUpdate) channelUpdate() {} @@ -149,8 +149,8 @@ func (GuildPostUpdate) channelUpdate() {} func (GuildPostUpdate) guildChannelUpdate() {} type GuildChannelPositionUpdate struct { - ID snowflake.ID `json:"id"` - Position *json.Nullable[int] `json:"position"` - LockPermissions *json.Nullable[bool] `json:"lock_permissions,omitempty"` - ParentID *snowflake.ID `json:"parent_id,omitempty"` + ID snowflake.ID `json:"id"` + Position omit.Omit[*int] `json:"position,omitzero"` + LockPermissions omit.Omit[*bool] `json:"lock_permissions,omitzero"` + ParentID *snowflake.ID `json:"parent_id,omitempty"` } diff --git a/discord/channels_raw.go b/discord/channels_raw.go index 1dba72b2f..a5a55ab44 100644 --- a/discord/channels_raw.go +++ b/discord/channels_raw.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/component.go b/discord/component.go index f43611e74..5093c58c5 100644 --- a/discord/component.go +++ b/discord/component.go @@ -2,8 +2,9 @@ package discord import ( "fmt" + "iter" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) @@ -20,26 +21,116 @@ const ( ComponentTypeRoleSelectMenu ComponentTypeMentionableSelectMenu ComponentTypeChannelSelectMenu + ComponentTypeSection + ComponentTypeTextDisplay + ComponentTypeThumbnail + ComponentTypeMediaGallery + ComponentTypeFile + ComponentTypeSeparator + _ + // ComponentTypeContentInventoryEntry cannot be used by bots. + ComponentTypeContentInventoryEntry + ComponentTypeContainer ) +// Component is an interface for all components. +// [ActionRowComponent] +// [ButtonComponent] +// [StringSelectMenuComponent] +// [TextInputComponent] +// [UserSelectMenuComponent] +// [RoleSelectMenuComponent] +// [MentionableSelectMenuComponent] +// [ChannelSelectMenuComponent] +// [SectionComponent] +// [TextDisplayComponent] +// [ThumbnailComponent] +// [MediaGalleryComponent] +// [FileComponent] +// [SeparatorComponent] +// [ContainerComponent] +// [UnknownComponent] type Component interface { json.Marshaler + // Type returns the ComponentType of the Component. Type() ComponentType + // GetID returns the id of the Component. This is used to uniquely identify a Component in a [Message] and needs to be unique. + GetID() int + // component is a marker to simulate unions. component() } -type ContainerComponent interface { - Component - Components() []InteractiveComponent - containerComponent() +// ComponentIter is an optional interface a Component can implement to return an iterator over its sub components. +type ComponentIter interface { + SubComponents() iter.Seq[Component] } +// InteractiveComponent is an interface for all components that can be present in an [ActionRowComponent]. +// [ButtonComponent] +// [StringSelectMenuComponent] +// [TextInputComponent] (currently only supported in modals) +// [UserSelectMenuComponent] +// [RoleSelectMenuComponent] +// [MentionableSelectMenuComponent] +// [ChannelSelectMenuComponent] +// [ButtonComponent] +// [SelectMenuComponent] +// [UnknownComponent] type InteractiveComponent interface { Component - ID() string + // GetCustomID returns the customID of the Component. This can be used to identify or transport data with the Component. + GetCustomID() string + // interactiveComponent is a marker to simulate unions. interactiveComponent() } +// LayoutComponent is an interface for all components that can be present as a top level component in a [Message]. +// [ActionRowComponent] +// [SectionComponent] +// [TextDisplayComponent] +// [MediaGalleryComponent] +// [FileComponent] +// [SeparatorComponent] +// [ContainerComponent] +// [UnknownComponent] +type LayoutComponent interface { + Component + layoutComponent() +} + +// SectionSubComponent is an interface for all components that can be present in a [SectionComponent]. +// [TextDisplayComponent] +// [UnknownComponent] +type SectionSubComponent interface { + Component + // sectionSubComponent is a marker to simulate unions. + sectionSubComponent() +} + +// SectionAccessoryComponent is an interface for all components that can be present as an accessory in [SectionComponent.Accessory]. +// [ButtonComponent] +// [ThumbnailComponent] +// [UnknownComponent] +type SectionAccessoryComponent interface { + Component + // sectionAccessoryComponent is a marker to simulate unions. + sectionAccessoryComponent() +} + +// ContainerSubComponent is an interface for all components that can be present in a [ContainerComponent]. +// [ActionRowComponent] +// [SectionComponent] +// [TextDisplayComponent] +// [MediaGalleryComponent] +// [FileComponent] +// [SeparatorComponent] +// [UnknownComponent] +type ContainerSubComponent interface { + Component + // containerSubComponent is a marker to simulate unions. + containerSubComponent() +} + type UnmarshalComponent struct { Component } @@ -60,42 +151,77 @@ func (u *UnmarshalComponent) UnmarshalJSON(data []byte) error { switch cType.Type { case ComponentTypeActionRow: - v := ActionRowComponent{} + var v ActionRowComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeButton: - v := ButtonComponent{} + var v ButtonComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeStringSelectMenu: - v := StringSelectMenuComponent{} + var v StringSelectMenuComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeTextInput: - v := TextInputComponent{} + var v TextInputComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeUserSelectMenu: - v := UserSelectMenuComponent{} + var v UserSelectMenuComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeRoleSelectMenu: - v := RoleSelectMenuComponent{} + var v RoleSelectMenuComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeMentionableSelectMenu: - v := MentionableSelectMenuComponent{} + var v MentionableSelectMenuComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeChannelSelectMenu: - v := ChannelSelectMenuComponent{} + var v ChannelSelectMenuComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeSection: + var v SectionComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeTextDisplay: + var v TextDisplayComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeThumbnail: + var v ThumbnailComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeMediaGallery: + var v MediaGalleryComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeFile: + var v FileComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeSeparator: + var v SeparatorComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeContainer: + var v ContainerComponent err = json.Unmarshal(data, &v) component = v @@ -116,43 +242,50 @@ type ComponentEmoji struct { Animated bool `json:"animated,omitempty"` } +func NewActionRow(components ...InteractiveComponent) ActionRowComponent { + return ActionRowComponent{ + Components: components, + } +} + var ( - _ Component = (*ActionRowComponent)(nil) - _ ContainerComponent = (*ActionRowComponent)(nil) + _ Component = (*ActionRowComponent)(nil) + _ LayoutComponent = (*ActionRowComponent)(nil) + _ ContainerSubComponent = (*ActionRowComponent)(nil) + _ ComponentIter = (*ActionRowComponent)(nil) ) -func NewActionRow(components ...InteractiveComponent) ActionRowComponent { - return components +type ActionRowComponent struct { + ID int `json:"id,omitempty"` + Components []InteractiveComponent `json:"components"` } -type ActionRowComponent []InteractiveComponent - func (c ActionRowComponent) MarshalJSON() ([]byte, error) { + type actionRowComponent ActionRowComponent return json.Marshal(struct { - Type ComponentType `json:"type"` - Components []InteractiveComponent `json:"components"` + Type ComponentType `json:"type"` + actionRowComponent }{ - Type: c.Type(), - Components: c, + Type: c.Type(), + actionRowComponent: actionRowComponent(c), }) } func (c *ActionRowComponent) UnmarshalJSON(data []byte) error { - var actionRow struct { + var actionRowComponent struct { + ID int `json:"id,omitempty"` Components []UnmarshalComponent `json:"components"` } - - if err := json.Unmarshal(data, &actionRow); err != nil { + if err := json.Unmarshal(data, &actionRowComponent); err != nil { return err } - if len(actionRow.Components) > 0 { - *c = make([]InteractiveComponent, len(actionRow.Components)) - for i, component := range actionRow.Components { - (*c)[i] = component.Component.(InteractiveComponent) - } + c.ID = actionRowComponent.ID + components := make([]InteractiveComponent, 0, len(actionRowComponent.Components)) + for _, component := range actionRowComponent.Components { + components = append(components, component.Component.(InteractiveComponent)) } - + c.Components = components return nil } @@ -160,51 +293,42 @@ func (ActionRowComponent) Type() ComponentType { return ComponentTypeActionRow } -func (ActionRowComponent) component() {} -func (ActionRowComponent) containerComponent() {} - -func (c ActionRowComponent) Components() []InteractiveComponent { - return c +func (c ActionRowComponent) GetID() int { + return c.ID } -// Buttons returns all ButtonComponent(s) in the ActionRowComponent -func (c ActionRowComponent) Buttons() []ButtonComponent { - var buttons []ButtonComponent - for i := range c { - if button, ok := c[i].(ButtonComponent); ok { - buttons = append(buttons, button) +func (ActionRowComponent) component() {} +func (ActionRowComponent) layoutComponent() {} +func (ActionRowComponent) containerSubComponent() {} + +// SubComponents returns an [iter.Seq[Component]] over the sub Component(s) of the ActionRowComponent. +func (c ActionRowComponent) SubComponents() iter.Seq[Component] { + return func(yield func(Component) bool) { + for _, cc := range c.Components { + if !yield(cc) { + return + } } } - return buttons } -// SelectMenus returns all SelectMenuComponent(s) in the ActionRowComponent -func (c ActionRowComponent) SelectMenus() []SelectMenuComponent { - var selectMenus []SelectMenuComponent - for i := range c { - if selectMenu, ok := c[i].(SelectMenuComponent); ok { - selectMenus = append(selectMenus, selectMenu) - } - } - return selectMenus +// WithID returns a new ActionRowComponent with the provided id +func (c ActionRowComponent) WithID(id int) ActionRowComponent { + c.ID = id + return c } -// TextInputs returns all TextInputComponent(s) in the ActionRowComponent -func (c ActionRowComponent) TextInputs() []TextInputComponent { - var textInputs []TextInputComponent - for i := range c { - if textInput, ok := c[i].(TextInputComponent); ok { - textInputs = append(textInputs, textInput) - } - } - return textInputs +// WithComponents returns a new ActionRowComponent with the provided Component(s) +func (c ActionRowComponent) WithComponents(components ...InteractiveComponent) ActionRowComponent { + c.Components = components + return c } -// UpdateComponent returns a new ActionRowComponent with the Component which has the customID replaced -func (c ActionRowComponent) UpdateComponent(customID string, component InteractiveComponent) ActionRowComponent { - for i, cc := range c { - if cc.ID() == customID { - c[i] = component +// UpdateComponent returns a new ActionRowComponent with the Component which has the id replaced with the provided Component. +func (c ActionRowComponent) UpdateComponent(id int, component InteractiveComponent) ActionRowComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components[i] = component return c } } @@ -213,18 +337,24 @@ func (c ActionRowComponent) UpdateComponent(customID string, component Interacti // AddComponents returns a new ActionRowComponent with the provided Component(s) added func (c ActionRowComponent) AddComponents(components ...InteractiveComponent) ActionRowComponent { - return append(c, components...) + c.Components = append(c.Components, components...) + return c } -// RemoveComponent returns a new ActionRowComponent with the provided Component at the index removed -func (c ActionRowComponent) RemoveComponent(index int) ActionRowComponent { - if len(c) > index { - return append(c[:index], c[index+1:]...) +// RemoveComponent returns a new ActionRowComponent with the provided Component which has the provided id removed. +func (c ActionRowComponent) RemoveComponent(id int) ActionRowComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components = append(c.Components[:i], c.Components[i+1:]...) + return c + } } return c } -// ButtonStyle defines how the ButtonComponent looks like (https://discord.com/assets/7bb017ce52cfd6575e21c058feb3883b.png) +// ButtonStyle defines how the ButtonComponent looks like. [Discord Docs] +// +// [Discord Docs]: https://discord.com/developers/docs/interactions/message-components#button-object-button-styles type ButtonStyle int // Supported ButtonStyle(s) @@ -237,7 +367,7 @@ const ( ButtonStylePremium ) -// NewButton creates a new ButtonComponent with the provided parameters. Link ButtonComponent(s) need a URL and other ButtonComponent(s) need a customID +// NewButton creates a new [ButtonComponent] with the provided parameters. Link ButtonComponent(s) need a URL and other ButtonComponent(s) need a customID func NewButton(style ButtonStyle, label string, customID string, url string, skuID snowflake.ID) ButtonComponent { return ButtonComponent{ Style: style, @@ -302,11 +432,13 @@ func NewPremiumButton(skuID snowflake.ID) ButtonComponent { } var ( - _ Component = (*ButtonComponent)(nil) - _ InteractiveComponent = (*ButtonComponent)(nil) + _ Component = (*ButtonComponent)(nil) + _ InteractiveComponent = (*ButtonComponent)(nil) + _ SectionAccessoryComponent = (*ButtonComponent)(nil) ) type ButtonComponent struct { + ID int `json:"id,omitempty"` Style ButtonStyle `json:"style"` Label string `json:"label,omitempty"` Emoji *ComponentEmoji `json:"emoji,omitempty"` @@ -331,18 +463,24 @@ func (ButtonComponent) Type() ComponentType { return ComponentTypeButton } -func (c ButtonComponent) ID() string { +func (c ButtonComponent) GetID() int { + return c.ID +} + +func (c ButtonComponent) GetCustomID() string { return c.CustomID } -func (c ButtonComponent) SetID(id string) InteractiveComponent { - c.CustomID = id +func (ButtonComponent) component() {} +func (ButtonComponent) interactiveComponent() {} +func (ButtonComponent) sectionAccessoryComponent() {} + +// WithID returns a new ButtonComponent with the provided id +func (c ButtonComponent) WithID(id int) ButtonComponent { + c.ID = id return c } -func (ButtonComponent) component() {} -func (ButtonComponent) interactiveComponent() {} - // WithStyle returns a new ButtonComponent with the provided style func (c ButtonComponent) WithStyle(style ButtonStyle) ButtonComponent { c.Style = style @@ -397,11 +535,14 @@ func (c ButtonComponent) WithDisabled(disabled bool) ButtonComponent { return c } -var ( - _ Component = (*TextInputComponent)(nil) - _ InteractiveComponent = (*TextInputComponent)(nil) +type TextInputStyle int + +const ( + TextInputStyleShort TextInputStyle = iota + 1 + TextInputStyleParagraph ) +// NewTextInput creates a new [TextInputComponent] with the provided parameters. func NewTextInput(customID string, style TextInputStyle, label string) TextInputComponent { return TextInputComponent{ CustomID: customID, @@ -410,15 +551,25 @@ func NewTextInput(customID string, style TextInputStyle, label string) TextInput } } +// NewShortTextInput creates a new [TextInputComponent] with [TextInputStyleShort] & the provided parameters func NewShortTextInput(customID string, label string) TextInputComponent { return NewTextInput(customID, TextInputStyleShort, label) } +// NewParagraphTextInput creates a new [TextInputComponent] with [TextInputStyleParagraph] & the provided parameters func NewParagraphTextInput(customID string, label string) TextInputComponent { return NewTextInput(customID, TextInputStyleParagraph, label) } +var ( + _ Component = (*TextInputComponent)(nil) +) + +// TextInputComponent is a component that allows users to input text. [Discord Docs] +// +// [Discord Docs]: https://discord.com/developers/docs/interactions/message-components#text-inputs type TextInputComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Style TextInputStyle `json:"style"` Label string `json:"label"` @@ -444,13 +595,23 @@ func (TextInputComponent) Type() ComponentType { return ComponentTypeTextInput } -func (c TextInputComponent) ID() string { +func (c TextInputComponent) GetID() int { + return c.ID +} + +func (c TextInputComponent) GetCustomID() string { return c.CustomID } func (TextInputComponent) component() {} func (TextInputComponent) interactiveComponent() {} +// WithID returns a new TextInputComponent with the provided id +func (c TextInputComponent) WithID(id int) TextInputComponent { + c.ID = id + return c +} + // WithCustomID returns a new SelectMenuComponent with the provided customID func (c TextInputComponent) WithCustomID(customID string) TextInputComponent { c.CustomID = customID @@ -493,9 +654,685 @@ func (c TextInputComponent) WithValue(value string) TextInputComponent { return c } -type TextInputStyle int +type UnfurledMediaItemLoadingState int const ( - TextInputStyleShort TextInputStyle = iota + 1 - TextInputStyleParagraph + UnfurledMediaItemLoadingStateUnknown UnfurledMediaItemLoadingState = iota + UnfurledMediaItemLoadingStateLoading + UnfurledMediaItemLoadingStateLoadedSuccess + UnfurledMediaItemLoadingStateLoadedNotFound +) + +// UnfurledMediaItem is a media item that can be displayed in a [ThumbnailComponent] or [FileComponent]. +type UnfurledMediaItem struct { + // URL supports arbitrary urls and attachment:// references + URL string `json:"url"` + // ProxyURL is a proxied version of the URL. This can't be set by bots. + ProxyURL string `json:"proxy_url,omitempty"` + // Height is the height of the media item in pixels. This can't be set by bots. + Height int `json:"height,omitempty"` + // Width is the width of the media item in pixels. This can't be set by bots. + Width int `json:"width,omitempty"` + // ContentType is the content type of the media item. This can't be set by bots. + ContentType string `json:"content_type,omitempty"` + // LoadingState is the loading state of the media item. This can't be set by bots. + LoadingState UnfurledMediaItemLoadingState `json:"loading_state,omitempty"` +} + +// NewSection creates a new [SectionComponent] with the provided components. +func NewSection(components ...SectionSubComponent) SectionComponent { + return SectionComponent{ + Components: components, + } +} + +var ( + _ Component = (*SectionComponent)(nil) + _ LayoutComponent = (*SectionComponent)(nil) + _ ContainerSubComponent = (*SectionComponent)(nil) +) + +// SectionComponent is a component that can contain up to 3 TextDisplayComponent(s) and an optional SectionAccessoryComponent. +type SectionComponent struct { + ID int `json:"id,omitempty"` + Components []SectionSubComponent `json:"components"` + Accessory SectionAccessoryComponent `json:"accessory"` +} + +func (c SectionComponent) MarshalJSON() ([]byte, error) { + type sectionComponent SectionComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + sectionComponent + }{ + Type: c.Type(), + sectionComponent: sectionComponent(c), + }) +} + +func (c *SectionComponent) UnmarshalJSON(data []byte) error { + var sectionComponent struct { + ID int `json:"id,omitempty"` + Components []UnmarshalComponent `json:"components"` + Accessory UnmarshalComponent `json:"accessory"` + } + if err := json.Unmarshal(data, §ionComponent); err != nil { + return err + } + + c.ID = sectionComponent.ID + + components := make([]SectionSubComponent, 0, len(sectionComponent.Components)) + for _, component := range sectionComponent.Components { + components = append(components, component.Component.(SectionSubComponent)) + } + c.Components = components + + c.Accessory = sectionComponent.Accessory.Component.(SectionAccessoryComponent) + return nil +} + +func (SectionComponent) Type() ComponentType { + return ComponentTypeSection +} + +func (c SectionComponent) GetID() int { + return c.ID +} + +func (SectionComponent) component() {} +func (SectionComponent) layoutComponent() {} +func (SectionComponent) containerSubComponent() {} + +// SubComponents returns an [iter.Seq[Component]] over the sub Component(s) and Accessory of the SectionComponent. +func (c SectionComponent) SubComponents() iter.Seq[Component] { + return func(yield func(Component) bool) { + for _, cc := range c.Components { + if !yield(cc) { + return + } + } + + if c.Accessory != nil { + if !yield(c.Accessory) { + return + } + } + } +} + +// WithID returns a new SectionComponent with the provided id +func (c SectionComponent) WithID(id int) SectionComponent { + c.ID = id + return c +} + +// WithAccessory returns a new SectionComponent with the provided accessory +func (c SectionComponent) WithAccessory(accessory SectionAccessoryComponent) SectionComponent { + c.Accessory = accessory + return c +} + +// WithComponents returns a new SectionComponent with the provided components +func (c SectionComponent) WithComponents(components ...SectionSubComponent) SectionComponent { + c.Components = components + return c +} + +// UpdateComponent returns a new SectionComponent with the Component which has the id replaced with the provided Component. +func (c SectionComponent) UpdateComponent(id int, component SectionSubComponent) SectionComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components[i] = component + return c + } + } + return c +} + +// AddComponents returns a new SectionComponent with the provided Component(s) added +func (c SectionComponent) AddComponents(components ...SectionSubComponent) SectionComponent { + c.Components = append(c.Components, components...) + return c +} + +// RemoveComponent returns a new SectionComponent with the provided Component which has the provided id removed. +func (c SectionComponent) RemoveComponent(id int) SectionComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components = append(c.Components[:i], c.Components[i+1:]...) + return c + } + } + return c +} + +// NewTextDisplayf creates a new [TextDisplayComponent] with the provided content and format. +func NewTextDisplayf(content string, a ...any) TextDisplayComponent { + return NewTextDisplay(fmt.Sprintf(content, a...)) +} + +// NewTextDisplay creates a new [TextDisplayComponent] with the provided content. +func NewTextDisplay(content string) TextDisplayComponent { + return TextDisplayComponent{ + Content: content, + } +} + +var ( + _ Component = (*TextDisplayComponent)(nil) + _ ContainerSubComponent = (*TextDisplayComponent)(nil) + _ SectionSubComponent = (*TextDisplayComponent)(nil) + _ LayoutComponent = (*TextDisplayComponent)(nil) +) + +// TextDisplayComponent is a component that displays text. +type TextDisplayComponent struct { + ID int `json:"id,omitempty"` + Content string `json:"content"` +} + +func (c TextDisplayComponent) MarshalJSON() ([]byte, error) { + type textDisplayComponent TextDisplayComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + textDisplayComponent + }{ + Type: c.Type(), + textDisplayComponent: textDisplayComponent(c), + }) +} + +func (TextDisplayComponent) Type() ComponentType { + return ComponentTypeTextDisplay +} + +func (c TextDisplayComponent) GetID() int { + return c.ID +} + +func (TextDisplayComponent) component() {} +func (TextDisplayComponent) sectionSubComponent() {} +func (TextDisplayComponent) containerSubComponent() {} +func (TextDisplayComponent) layoutComponent() {} + +func (c TextDisplayComponent) WithID(i int) SectionSubComponent { + c.ID = i + return c +} + +func (c TextDisplayComponent) WithContent(content string) TextDisplayComponent { + c.Content = content + return c +} + +func NewThumbnail(url string) ThumbnailComponent { + return ThumbnailComponent{ + Media: UnfurledMediaItem{ + URL: url, + }, + } +} + +var ( + _ Component = (*ThumbnailComponent)(nil) + _ SectionAccessoryComponent = (*ThumbnailComponent)(nil) ) + +type ThumbnailComponent struct { + ID int `json:"id,omitempty"` + Media UnfurledMediaItem `json:"media"` + Description string `json:"description,omitempty"` + Spoiler bool `json:"spoiler,omitempty"` +} + +func (c ThumbnailComponent) MarshalJSON() ([]byte, error) { + type thumbnailComponent ThumbnailComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + thumbnailComponent + }{ + Type: c.Type(), + thumbnailComponent: thumbnailComponent(c), + }) +} + +func (ThumbnailComponent) Type() ComponentType { + return ComponentTypeThumbnail +} + +func (c ThumbnailComponent) GetID() int { + return c.ID +} + +func (ThumbnailComponent) component() {} +func (ThumbnailComponent) sectionAccessoryComponent() {} + +func (c ThumbnailComponent) WithID(id int) ThumbnailComponent { + c.ID = id + return c +} + +func (c ThumbnailComponent) WithMediaURL(url string) ThumbnailComponent { + c.Media.URL = url + return c +} + +func (c ThumbnailComponent) WithDescription(description string) ThumbnailComponent { + c.Description = description + return c +} + +func (c ThumbnailComponent) WithSpoiler(spoiler bool) ThumbnailComponent { + c.Spoiler = spoiler + return c +} + +type MediaGalleryItem struct { + Media UnfurledMediaItem `json:"media"` + Description string `json:"description,omitempty"` + Spoiler bool `json:"spoiler,omitempty"` +} + +// NewMediaGallery creates a new [MediaGalleryComponent] with the provided items. +func NewMediaGallery(items ...MediaGalleryItem) MediaGalleryComponent { + return MediaGalleryComponent{ + Items: items, + } +} + +var ( + _ Component = (*MediaGalleryComponent)(nil) + _ LayoutComponent = (*MediaGalleryComponent)(nil) + _ ContainerSubComponent = (*MediaGalleryComponent)(nil) +) + +// MediaGalleryComponent is a component that displays a gallery of media. +type MediaGalleryComponent struct { + ID int `json:"id,omitempty"` + Items []MediaGalleryItem `json:"items"` +} + +func (c MediaGalleryComponent) MarshalJSON() ([]byte, error) { + type mediaGalleryComponent MediaGalleryComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + mediaGalleryComponent + }{ + Type: c.Type(), + mediaGalleryComponent: mediaGalleryComponent(c), + }) +} + +func (MediaGalleryComponent) Type() ComponentType { + return ComponentTypeMediaGallery +} + +func (c MediaGalleryComponent) GetID() int { + return c.ID +} + +func (MediaGalleryComponent) component() {} +func (MediaGalleryComponent) layoutComponent() {} +func (MediaGalleryComponent) containerSubComponent() {} + +// WithID returns a new MediaGalleryComponent with the provided id +func (c MediaGalleryComponent) WithID(id int) MediaGalleryComponent { + c.ID = id + return c +} + +// WithItems returns a new MediaGalleryComponent with the provided items +func (c MediaGalleryComponent) WithItems(items ...MediaGalleryItem) MediaGalleryComponent { + c.Items = items + return c +} + +type SeparatorSpacingSize int + +const ( + SeparatorSpacingSizeSmall SeparatorSpacingSize = iota + 1 + SeparatorSpacingSizeLarge +) + +// NewSmallSeparator creates a new [SeparatorComponent] with the small spacing. +func NewSmallSeparator() SeparatorComponent { + return NewSeparator(SeparatorSpacingSizeSmall) +} + +// NewLargeSeparator creates a new [SeparatorComponent] with the large spacing. +func NewLargeSeparator() SeparatorComponent { + return NewSeparator(SeparatorSpacingSizeLarge) +} + +// NewSeparator creates a new [SeparatorComponent] with the provided spacing. +func NewSeparator(spacing SeparatorSpacingSize) SeparatorComponent { + return SeparatorComponent{ + Spacing: spacing, + } +} + +var ( + _ Component = (*SeparatorComponent)(nil) + _ LayoutComponent = (*MediaGalleryComponent)(nil) + _ ContainerSubComponent = (*MediaGalleryComponent)(nil) +) + +// SeparatorComponent is a component that adds a visual divider between components. +type SeparatorComponent struct { + ID int `json:"id,omitempty"` + // Divider determines if the separator should have a divider line. (default: true) + Divider *bool `json:"divider,omitempty"` + Spacing SeparatorSpacingSize `json:"spacing,omitempty"` +} + +func (c SeparatorComponent) MarshalJSON() ([]byte, error) { + type separatorComponent SeparatorComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + separatorComponent + }{ + Type: c.Type(), + separatorComponent: separatorComponent(c), + }) +} + +func (SeparatorComponent) Type() ComponentType { + return ComponentTypeSeparator +} + +func (c SeparatorComponent) GetID() int { + return c.ID +} + +func (SeparatorComponent) component() {} +func (SeparatorComponent) layoutComponent() {} +func (SeparatorComponent) containerSubComponent() {} + +func (c SeparatorComponent) WithID(i int) LayoutComponent { + c.ID = i + return c +} + +func (c SeparatorComponent) WithDivider(divider bool) SeparatorComponent { + c.Divider = ÷r + return c +} + +func (c SeparatorComponent) WithSpacing(spacing SeparatorSpacingSize) SeparatorComponent { + c.Spacing = spacing + return c +} + +// NewFileComponent creates a new [FileComponent] with the provided url. +func NewFileComponent(url string) FileComponent { + return FileComponent{ + File: UnfurledMediaItem{ + URL: url, + }, + } +} + +var ( + _ Component = (*FileComponent)(nil) + _ ContainerSubComponent = (*FileComponent)(nil) + _ LayoutComponent = (*FileComponent)(nil) +) + +// FileComponent is a component that displays a file. +type FileComponent struct { + ID int `json:"id,omitempty"` + // File only supports attachment:// references + File UnfurledMediaItem `json:"file"` + Spoiler bool `json:"spoiler,omitempty"` +} + +func (c FileComponent) MarshalJSON() ([]byte, error) { + type fileComponent FileComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + fileComponent + }{ + Type: c.Type(), + fileComponent: fileComponent(c), + }) +} + +func (FileComponent) Type() ComponentType { + return ComponentTypeFile +} + +func (c FileComponent) GetID() int { + return c.ID +} + +func (FileComponent) component() {} +func (FileComponent) layoutComponent() {} +func (FileComponent) containerSubComponent() {} + +func (c FileComponent) WithID(id int) FileComponent { + c.ID = id + return c +} + +func (c FileComponent) WithMediaURL(url string) FileComponent { + c.File.URL = url + return c +} + +func (c FileComponent) WithSpoiler(spoiler bool) FileComponent { + c.Spoiler = spoiler + return c +} + +// NewContainer creates a new [ContainerComponent] with the provided components. +func NewContainer(components ...ContainerSubComponent) ContainerComponent { + return ContainerComponent{ + Components: components, + } +} + +var ( + _ Component = (*ContainerComponent)(nil) + _ LayoutComponent = (*ContainerComponent)(nil) +) + +// ContainerComponent is a component which lets you group components together. +// It looks similar to embeds and supports an accent color and spoiler state. +type ContainerComponent struct { + ID int `json:"id,omitempty"` + // AccentColor is the color of the left border of the container. (use 0 for no color) + AccentColor int `json:"accent_color,omitempty"` + Spoiler bool `json:"spoiler,omitempty"` + Components []ContainerSubComponent `json:"components"` +} + +func (c ContainerComponent) MarshalJSON() ([]byte, error) { + type containerComponent ContainerComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + containerComponent + }{ + Type: c.Type(), + containerComponent: containerComponent(c), + }) +} + +func (c *ContainerComponent) UnmarshalJSON(data []byte) error { + var containerComponent struct { + ID int `json:"id,omitempty"` + AccentColor int `json:"accent_color,omitempty"` + Spoiler bool `json:"spoiler,omitempty"` + Components []UnmarshalComponent `json:"components"` + } + if err := json.Unmarshal(data, &containerComponent); err != nil { + return err + } + + c.ID = containerComponent.ID + c.AccentColor = containerComponent.AccentColor + c.Spoiler = containerComponent.Spoiler + + components := make([]ContainerSubComponent, 0, len(containerComponent.Components)) + for _, component := range containerComponent.Components { + components = append(components, component.Component.(ContainerSubComponent)) + } + c.Components = components + return nil +} + +func (ContainerComponent) Type() ComponentType { + return ComponentTypeContainer +} + +func (c ContainerComponent) GetID() int { + return c.ID +} + +func (ContainerComponent) component() {} +func (ContainerComponent) layoutComponent() {} + +func (c ContainerComponent) WithID(id int) ContainerComponent { + c.ID = id + return c +} + +func (c ContainerComponent) WithAccentColor(accentColor int) ContainerComponent { + c.AccentColor = accentColor + return c +} + +func (c ContainerComponent) WithSpoiler(spoiler bool) ContainerComponent { + c.Spoiler = spoiler + return c +} + +func (c ContainerComponent) SubComponents() iter.Seq[Component] { + return func(yield func(Component) bool) { + for _, cc := range c.Components { + if !yield(cc) { + return + } + if ic, ok := cc.(ComponentIter); ok { + for cc := range ic.SubComponents() { + if !yield(cc) { + return + } + } + } + } + } +} + +func (c ContainerComponent) WithComponents(components ...ContainerSubComponent) ContainerComponent { + c.Components = components + return c +} + +func (c ContainerComponent) UpdateComponent(id int, component ContainerSubComponent) ContainerComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components[i] = component + return c + } + } + return c +} + +func (c ContainerComponent) AddComponents(components ...ContainerSubComponent) ContainerComponent { + c.Components = append(c.Components, components...) + return c +} + +func (c ContainerComponent) RemoveComponent(id int) ContainerComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components = append(c.Components[:i], c.Components[i+1:]...) + return c + } + } + return c +} + +// NewUnknownComponent creates a new [UnknownComponent] with the provided type and data. +// This is useful for handling unknown components, but should be avoided if possible. +func NewUnknownComponent(componentType ComponentType, data json.RawMessage) UnknownComponent { + return UnknownComponent{ + ComponentType: componentType, + Data: data, + } +} + +var ( + _ Component = (*UnknownComponent)(nil) + _ InteractiveComponent = (*UnknownComponent)(nil) + _ LayoutComponent = (*UnknownComponent)(nil) + _ SelectMenuComponent = (*UnknownComponent)(nil) + _ SectionSubComponent = (*UnknownComponent)(nil) + _ SectionAccessoryComponent = (*UnknownComponent)(nil) + _ ContainerSubComponent = (*UnknownComponent)(nil) +) + +// UnknownComponent is a component that is not recognized by the library. +type UnknownComponent struct { + ComponentType ComponentType + ID int + Data json.RawMessage +} + +func (c UnknownComponent) MarshalJSON() ([]byte, error) { + data, err := json.Marshal(struct { + Type ComponentType `json:"type"` + ID int `json:"id,omitempty"` + }{ + Type: c.ComponentType, + ID: c.ID, + }) + if err != nil { + return nil, err + } + + return json.Merge(c.Data, data) +} + +func (c *UnknownComponent) UnmarshalJSON(data []byte) error { + var unknownComponent struct { + Type ComponentType `json:"type"` + ID int `json:"id,omitempty"` + } + if err := json.Unmarshal(data, &unknownComponent); err != nil { + return err + } + + c.ComponentType = unknownComponent.Type + c.ID = unknownComponent.ID + c.Data = data + return nil +} + +func (c UnknownComponent) Type() ComponentType { + return c.ComponentType +} + +func (c UnknownComponent) GetID() int { + return c.ID +} + +func (c UnknownComponent) GetCustomID() string { + var data struct { + CustomID string `json:"custom_id"` + } + if err := json.Unmarshal(c.Data, &data); err != nil { + return "" + } + + return data.CustomID +} + +func (UnknownComponent) component() {} +func (UnknownComponent) interactiveComponent() {} +func (UnknownComponent) layoutComponent() {} +func (UnknownComponent) selectMenuComponent() {} +func (UnknownComponent) containerSubComponent() {} +func (UnknownComponent) sectionSubComponent() {} +func (UnknownComponent) sectionAccessoryComponent() {} diff --git a/discord/component_iter.go b/discord/component_iter.go new file mode 100644 index 000000000..5e5b125ab --- /dev/null +++ b/discord/component_iter.go @@ -0,0 +1,24 @@ +package discord + +import "iter" + +// componentIter returns an iterator over the given components and their children. +func componentIter(components []LayoutComponent) iter.Seq[Component] { + return func(yield func(Component) bool) { + for _, c := range components { + if !yield(c) { + return + } + ic, ok := c.(ComponentIter) + if !ok { + continue + } + + for cc := range ic.SubComponents() { + if !yield(cc) { + return + } + } + } + } +} diff --git a/discord/component_iter_test.go b/discord/component_iter_test.go new file mode 100644 index 000000000..6887c17ec --- /dev/null +++ b/discord/component_iter_test.go @@ -0,0 +1,40 @@ +package discord + +import "testing" + +func TestComponentIter(t *testing.T) { + components := []LayoutComponent{ + NewActionRow( + NewPrimaryButton("button1", "button1").WithID(2), + NewSecondaryButton("button2", "button2").WithID(3), + ).WithID(1), + NewSmallSeparator().WithID(4), + NewActionRow( + NewStringSelectMenu("select1", "select1", + StringSelectMenuOption{Label: "option1", Value: "option1"}, + StringSelectMenuOption{Label: "option2", Value: "option21"}, + ).WithID(6), + ).WithID(5), + NewSection( + NewTextDisplay("text1").WithID(8), + NewTextDisplay("text2").WithID(9), + ).WithID(7), + NewContainer( + NewActionRow( + NewPrimaryButton("button2", "button2").WithID(12), + ).WithID(11), + NewSection( + NewTextDisplay("text3").WithID(14), + NewTextDisplayf("text%d", 4).WithID(15), + ).WithID(13), + ).WithID(10), + } + + i := 1 + for c := range componentIter(components) { + if c.GetID() != i { + t.Errorf("expected component with ID %d, got %d", i, c.GetID()) + } + i++ + } +} diff --git a/discord/errors.go b/discord/errors.go index 1d7f54d16..8fa677b5e 100644 --- a/discord/errors.go +++ b/discord/errors.go @@ -12,24 +12,13 @@ var ( ErrGatewayAlreadyConnected = errors.New("gateway is already connected") ErrShardNotConnected = errors.New("shard is not connected") ErrShardNotFound = errors.New("shard not found in shard manager") - ErrGatewayCompressedData = errors.New("disgo does not currently support compressed gateway data") ErrNoHTTPServer = errors.New("no http server configured") - ErrNoDisgoInstance = errors.New("no disgo instance injected") - ErrInvalidBotToken = errors.New("token is not in a valid format") ErrNoBotToken = errors.New("please specify the token") - ErrSelfDM = errors.New("can't open a dm channel to yourself") - ErrInteractionAlreadyReplied = errors.New("you already replied to this interaction") ErrInteractionExpired = errors.New("this interaction has expired") - ErrChannelNotTypeNews = errors.New("channel type is not 'NEWS'") - ErrCheckFailed = errors.New("check failed") - - ErrMemberMustBeConnectedToChannel = errors.New("the member must be connected to the channel") - - ErrStickerTypeGuild = errors.New("sticker type must be of type StickerTypeGuild") ) diff --git a/discord/file.go b/discord/file.go index fa712a7e0..2e773df0f 100644 --- a/discord/file.go +++ b/discord/file.go @@ -7,7 +7,7 @@ import ( "mime/multipart" "net/textproto" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/disgo/internal/flags" ) diff --git a/discord/guild.go b/discord/guild.go index e2aa6d01a..6dfcad06b 100644 --- a/discord/guild.go +++ b/discord/guild.go @@ -3,7 +3,8 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/internal/flags" @@ -107,6 +108,7 @@ const ( GuildFeatureCreatorStorePage GuildFeature = "CREATOR_STORE_PAGE" GuildFeatureDeveloperSupportServer GuildFeature = "DEVELOPER_SUPPORT_SERVER" GuildFeatureDiscoverable GuildFeature = "DISCOVERABLE" + GuildFeatureEnhancedRoleColors GuildFeature = "ENHANCED_ROLE_COLORS" GuildFeatureFeaturable GuildFeature = "FEATURABLE" GuildFeatureInvitesDisabled GuildFeature = "INVITES_DISABLED" GuildFeatureInviteSplash GuildFeature = "INVITE_SPLASH" @@ -328,8 +330,8 @@ type GuildIncidentsData struct { } type GuildIncidentActionsUpdate struct { - InvitesDisabledUntil *json.Nullable[time.Time] `json:"invites_disabled_until,omitempty"` - DMsDisabledUntil *json.Nullable[time.Time] `json:"dms_disabled_until,omitempty"` + InvitesDisabledUntil omit.Omit[*time.Time] `json:"invites_disabled_until,omitzero"` + DMsDisabledUntil omit.Omit[*time.Time] `json:"dms_disabled_until,omitzero"` } // GuildCreate is the payload used to create a Guild @@ -349,26 +351,26 @@ type GuildCreate struct { // GuildUpdate is the payload used to update a Guild type GuildUpdate struct { - Name *string `json:"name,omitempty"` - VerificationLevel *json.Nullable[VerificationLevel] `json:"verification_level,omitempty"` - DefaultMessageNotifications *json.Nullable[MessageNotificationsLevel] `json:"default_message_notifications,omitempty"` - ExplicitContentFilter *json.Nullable[ExplicitContentFilterLevel] `json:"explicit_content_filter,omitempty"` - AFKChannelID *snowflake.ID `json:"afk_channel_id,omitempty"` - AFKTimeout *int `json:"afk_timeout,omitempty"` - Icon *json.Nullable[Icon] `json:"icon,omitempty"` - OwnerID *snowflake.ID `json:"owner_id,omitempty"` - Splash *json.Nullable[Icon] `json:"splash,omitempty"` - DiscoverySplash *json.Nullable[Icon] `json:"discovery_splash,omitempty"` - Banner *json.Nullable[Icon] `json:"banner,omitempty"` - SystemChannelID *snowflake.ID `json:"system_channel_id,omitempty"` - SystemChannelFlags *SystemChannelFlags `json:"system_channel_flags,omitempty"` - RulesChannelID *snowflake.ID `json:"rules_channel_id,omitempty"` - PublicUpdatesChannelID *snowflake.ID `json:"public_updates_channel_id,omitempty"` - SafetyAlertsChannelID *snowflake.ID `json:"safety_alerts_channel_id,omitempty"` - PreferredLocale *string `json:"preferred_locale,omitempty"` - Features *[]GuildFeature `json:"features,omitempty"` - Description *string `json:"description,omitempty"` - PremiumProgressBarEnabled *bool `json:"premium_progress_bar_enabled,omitempty"` + Name *string `json:"name,omitempty"` + VerificationLevel omit.Omit[*VerificationLevel] `json:"verification_level,omitzero"` + DefaultMessageNotifications omit.Omit[*MessageNotificationsLevel] `json:"default_message_notification,omitzero"` + ExplicitContentFilter omit.Omit[*ExplicitContentFilterLevel] `json:"explicit_content_filter,omitzero"` + AFKChannelID *snowflake.ID `json:"afk_channel_id,omitempty"` + AFKTimeout *int `json:"afk_timeout,omitempty"` + Icon omit.Omit[*Icon] `json:"icon,omitzero"` + OwnerID *snowflake.ID `json:"owner_id,omitempty"` + Splash omit.Omit[*Icon] `json:"splash,omitzero"` + DiscoverySplash omit.Omit[*Icon] `json:"discovery_splash,omitzero"` + Banner omit.Omit[*Icon] `json:"banner,omitzero"` + SystemChannelID *snowflake.ID `json:"system_channel_id,omitempty"` + SystemChannelFlags *SystemChannelFlags `json:"system_channel_flags,omitempty"` + RulesChannelID *snowflake.ID `json:"rules_channel_id,omitempty"` + PublicUpdatesChannelID *snowflake.ID `json:"public_updates_channel_id,omitempty"` + SafetyAlertsChannelID *snowflake.ID `json:"safety_alerts_channel_id,omitempty"` + PreferredLocale *string `json:"preferred_locale,omitempty"` + Features *[]GuildFeature `json:"features,omitempty"` + Description *string `json:"description,omitempty"` + PremiumProgressBarEnabled *bool `json:"premium_progress_bar_enabled,omitempty"` } type NSFWLevel int diff --git a/discord/guild_onboarding.go b/discord/guild_onboarding.go index a3dbd17de..311598e70 100644 --- a/discord/guild_onboarding.go +++ b/discord/guild_onboarding.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/guild_scheduled_event.go b/discord/guild_scheduled_event.go index dbd673e9d..94eda5507 100644 --- a/discord/guild_scheduled_event.go +++ b/discord/guild_scheduled_event.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" ) @@ -55,17 +55,17 @@ type GuildScheduledEventCreate struct { } type GuildScheduledEventUpdate struct { - ChannelID *snowflake.ID `json:"channel_id,omitempty"` - EntityMetaData *EntityMetaData `json:"entity_metadata,omitempty"` - Name string `json:"name,omitempty"` - PrivacyLevel *ScheduledEventPrivacyLevel `json:"privacy_level,omitempty"` - ScheduledStartTime *time.Time `json:"scheduled_start_time,omitempty"` - ScheduledEndTime *time.Time `json:"scheduled_end_time,omitempty"` - Description *string `json:"description,omitempty"` - EntityType *ScheduledEventEntityType `json:"entity_type,omitempty"` - Status *ScheduledEventStatus `json:"status,omitempty"` - Image *json.Nullable[Icon] `json:"image,omitempty"` - RecurrenceRule *json.Nullable[ScheduledEventRecurrenceRule] `json:"recurrence_rule,omitempty"` + ChannelID *snowflake.ID `json:"channel_id,omitempty"` + EntityMetaData *EntityMetaData `json:"entity_metadata,omitempty"` + Name string `json:"name,omitempty"` + PrivacyLevel *ScheduledEventPrivacyLevel `json:"privacy_level,omitempty"` + ScheduledStartTime *time.Time `json:"scheduled_start_time,omitempty"` + ScheduledEndTime *time.Time `json:"scheduled_end_time,omitempty"` + Description *string `json:"description,omitempty"` + EntityType *ScheduledEventEntityType `json:"entity_type,omitempty"` + Status *ScheduledEventStatus `json:"status,omitempty"` + Image omit.Omit[*Icon] `json:"image,omitzero"` + RecurrenceRule omit.Omit[*ScheduledEventRecurrenceRule] `json:"recurrence_rule,omitzero"` } type GuildScheduledEventUser struct { diff --git a/discord/icon.go b/discord/icon.go index 5ac02a3ba..a1cea5723 100644 --- a/discord/icon.go +++ b/discord/icon.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" ) type IconType string diff --git a/discord/icon_test.go b/discord/icon_test.go index c960a68d6..334cf951d 100644 --- a/discord/icon_test.go +++ b/discord/icon_test.go @@ -3,7 +3,7 @@ package discord import ( "testing" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/stretchr/testify/assert" ) diff --git a/discord/integration.go b/discord/integration.go index cb2168203..a30f61cf4 100644 --- a/discord/integration.go +++ b/discord/integration.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/interaction.go b/discord/interaction.go index f0c13cbd8..8bdfacc06 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) @@ -29,15 +29,13 @@ const ( ) type rawInteraction struct { - ID snowflake.ID `json:"id"` - Type InteractionType `json:"type"` - ApplicationID snowflake.ID `json:"application_id"` - Token string `json:"token"` - Version int `json:"version"` - Guild *InteractionGuild `json:"guild,omitempty"` - GuildID *snowflake.ID `json:"guild_id,omitempty"` - // Deprecated: Use Channel instead - ChannelID snowflake.ID `json:"channel_id,omitempty"` + ID snowflake.ID `json:"id"` + Type InteractionType `json:"type"` + ApplicationID snowflake.ID `json:"application_id"` + Token string `json:"token"` + Version int `json:"version"` + Guild *InteractionGuild `json:"guild,omitempty"` + GuildID *snowflake.ID `json:"guild_id,omitempty"` Channel InteractionChannel `json:"channel,omitempty"` Locale Locale `json:"locale,omitempty"` GuildLocale *Locale `json:"guild_locale,omitempty"` @@ -59,8 +57,6 @@ type Interaction interface { Version() int PartialGuild() *InteractionGuild GuildID() *snowflake.ID - // Deprecated: Use Interaction.Channel instead - ChannelID() snowflake.ID Channel() InteractionChannel Locale() Locale GuildLocale() *Locale diff --git a/discord/interaction_application_command.go b/discord/interaction_application_command.go index f0b96007a..80e96d707 100644 --- a/discord/interaction_application_command.go +++ b/discord/interaction_application_command.go @@ -3,7 +3,7 @@ package discord import ( "fmt" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) @@ -76,7 +76,6 @@ func (i *ApplicationCommandInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.version = interaction.Version i.baseInteraction.guild = interaction.Guild i.baseInteraction.guildID = interaction.GuildID - i.baseInteraction.channelID = interaction.ChannelID i.baseInteraction.channel = interaction.Channel i.baseInteraction.locale = interaction.Locale i.baseInteraction.guildLocale = interaction.GuildLocale @@ -109,7 +108,6 @@ func (i ApplicationCommandInteraction) MarshalJSON() ([]byte, error) { Version: i.version, Guild: i.guild, GuildID: i.guildID, - ChannelID: i.channelID, Channel: i.channel, Locale: i.locale, GuildLocale: i.guildLocale, diff --git a/discord/interaction_autocomplete.go b/discord/interaction_autocomplete.go index 3783f560a..4ec286f57 100644 --- a/discord/interaction_autocomplete.go +++ b/discord/interaction_autocomplete.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) @@ -29,7 +29,6 @@ func (i *AutocompleteInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.version = interaction.Version i.baseInteraction.guild = interaction.Guild i.baseInteraction.guildID = interaction.GuildID - i.baseInteraction.channelID = interaction.ChannelID i.baseInteraction.channel = interaction.Channel i.baseInteraction.locale = interaction.Locale i.baseInteraction.guildLocale = interaction.GuildLocale @@ -62,7 +61,6 @@ func (i AutocompleteInteraction) MarshalJSON() ([]byte, error) { Version: i.version, Guild: i.guild, GuildID: i.guildID, - ChannelID: i.channelID, Channel: i.channel, Locale: i.locale, GuildLocale: i.guildLocale, diff --git a/discord/interaction_base.go b/discord/interaction_base.go index 6f21f052a..7badfe9bb 100644 --- a/discord/interaction_base.go +++ b/discord/interaction_base.go @@ -13,7 +13,6 @@ type baseInteraction struct { version int guild *InteractionGuild guildID *snowflake.ID - channelID snowflake.ID channel InteractionChannel locale Locale guildLocale *Locale @@ -44,11 +43,6 @@ func (i baseInteraction) PartialGuild() *InteractionGuild { func (i baseInteraction) GuildID() *snowflake.ID { return i.guildID } - -// Deprecated: Use Channel() instead -func (i baseInteraction) ChannelID() snowflake.ID { - return i.channelID -} func (i baseInteraction) Channel() InteractionChannel { return i.channel } diff --git a/discord/interaction_component.go b/discord/interaction_component.go index ea5565260..a229e96e8 100644 --- a/discord/interaction_component.go +++ b/discord/interaction_component.go @@ -3,7 +3,7 @@ package discord import ( "fmt" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) @@ -40,32 +40,32 @@ func (i *ComponentInteraction) UnmarshalJSON(data []byte) error { ) switch cType.Type { case ComponentTypeButton: - v := ButtonInteractionData{} + var v ButtonInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeStringSelectMenu: - v := StringSelectMenuInteractionData{} + var v StringSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeUserSelectMenu: - v := UserSelectMenuInteractionData{} + var v UserSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeRoleSelectMenu: - v := RoleSelectMenuInteractionData{} + var v RoleSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeMentionableSelectMenu: - v := MentionableSelectMenuInteractionData{} + var v MentionableSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeChannelSelectMenu: - v := ChannelSelectMenuInteractionData{} + var v ChannelSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v @@ -82,7 +82,6 @@ func (i *ComponentInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.version = interaction.Version i.baseInteraction.guild = interaction.Guild i.baseInteraction.guildID = interaction.GuildID - i.baseInteraction.channelID = interaction.ChannelID i.baseInteraction.channel = interaction.Channel i.baseInteraction.locale = interaction.Locale i.baseInteraction.guildLocale = interaction.GuildLocale @@ -118,7 +117,6 @@ func (i ComponentInteraction) MarshalJSON() ([]byte, error) { Version: i.version, Guild: i.guild, GuildID: i.guildID, - ChannelID: i.channelID, Channel: i.channel, Locale: i.locale, GuildLocale: i.guildLocale, diff --git a/discord/interaction_modal_submit.go b/discord/interaction_modal_submit.go index 5649b2be7..402849892 100644 --- a/discord/interaction_modal_submit.go +++ b/discord/interaction_modal_submit.go @@ -1,6 +1,10 @@ package discord -import "github.com/disgoorg/json" +import ( + "iter" + + "github.com/disgoorg/json/v2" +) var ( _ Interaction = (*ModalSubmitInteraction)(nil) @@ -26,7 +30,6 @@ func (i *ModalSubmitInteraction) UnmarshalJSON(data []byte) error { i.baseInteraction.version = interaction.Version i.baseInteraction.guild = interaction.Guild i.baseInteraction.guildID = interaction.GuildID - i.baseInteraction.channelID = interaction.ChannelID i.baseInteraction.channel = interaction.Channel i.baseInteraction.locale = interaction.Locale i.baseInteraction.guildLocale = interaction.GuildLocale @@ -59,7 +62,6 @@ func (i ModalSubmitInteraction) MarshalJSON() ([]byte, error) { Version: i.version, Guild: i.guild, GuildID: i.guildID, - ChannelID: i.channelID, Channel: i.channel, Locale: i.locale, GuildLocale: i.guildLocale, @@ -82,37 +84,41 @@ func (ModalSubmitInteraction) Type() InteractionType { func (ModalSubmitInteraction) interaction() {} type ModalSubmitInteractionData struct { - CustomID string `json:"custom_id"` - Components map[string]InteractiveComponent `json:"components"` + CustomID string `json:"custom_id"` + Components []LayoutComponent `json:"components"` } func (d *ModalSubmitInteractionData) UnmarshalJSON(data []byte) error { - type modalSubmitInteractionData ModalSubmitInteractionData var iData struct { + CustomID string `json:"custom_id"` Components []UnmarshalComponent `json:"components"` - modalSubmitInteractionData } if err := json.Unmarshal(data, &iData); err != nil { return err } - *d = ModalSubmitInteractionData(iData.modalSubmitInteractionData) + d.CustomID = iData.CustomID - if len(iData.Components) > 0 { - d.Components = make(map[string]InteractiveComponent, len(iData.Components)) - for _, containerComponent := range iData.Components { - for _, component := range containerComponent.Component.(ContainerComponent).Components() { - d.Components[component.ID()] = component - } - } + components := make([]LayoutComponent, 0, len(iData.Components)) + for _, containerComponent := range iData.Components { + components = append(components, containerComponent.Component.(LayoutComponent)) } + d.Components = components return nil } +func (d ModalSubmitInteractionData) AllComponents() iter.Seq[Component] { + return componentIter(d.Components) +} + func (d ModalSubmitInteractionData) Component(customID string) (InteractiveComponent, bool) { - component, ok := d.Components[customID] - return component, ok + for component := range d.AllComponents() { + if ic, ok := component.(InteractiveComponent); ok && ic.GetCustomID() == customID { + return ic, true + } + } + return nil, false } func (d ModalSubmitInteractionData) TextInputComponent(customID string) (TextInputComponent, bool) { diff --git a/discord/interaction_ping.go b/discord/interaction_ping.go index eb634958d..71f437725 100644 --- a/discord/interaction_ping.go +++ b/discord/interaction_ping.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/interaction_response.go b/discord/interaction_response.go index b21285a5e..facff06ed 100644 --- a/discord/interaction_response.go +++ b/discord/interaction_response.go @@ -19,7 +19,7 @@ const ( InteractionResponseTypeUpdateMessage InteractionResponseTypeAutocompleteResult InteractionResponseTypeModal - InteractionResponseTypePremiumRequired + _ _ InteractionResponseTypeLaunchActivity ) diff --git a/discord/member.go b/discord/member.go index 6338eff62..f521d504c 100644 --- a/discord/member.go +++ b/discord/member.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/internal/flags" @@ -110,13 +110,13 @@ type MemberAdd struct { // MemberUpdate is used to modify a member type MemberUpdate struct { - ChannelID *snowflake.ID `json:"channel_id,omitempty"` - Nick *string `json:"nick,omitempty"` - Roles *[]snowflake.ID `json:"roles,omitempty"` - Mute *bool `json:"mute,omitempty"` - Deaf *bool `json:"deaf,omitempty"` - Flags *MemberFlags `json:"flags,omitempty"` - CommunicationDisabledUntil *json.Nullable[time.Time] `json:"communication_disabled_until,omitempty"` + ChannelID *snowflake.ID `json:"channel_id,omitempty"` + Nick *string `json:"nick,omitempty"` + Roles *[]snowflake.ID `json:"roles,omitempty"` + Mute *bool `json:"mute,omitempty"` + Deaf *bool `json:"deaf,omitempty"` + Flags *MemberFlags `json:"flags,omitempty"` + CommunicationDisabledUntil omit.Omit[*time.Time] `json:"communication_disabled_until,omitzero"` } // CurrentMemberUpdate is used to update the current member diff --git a/discord/message.go b/discord/message.go index 57eccd2db..b7c8c345f 100644 --- a/discord/message.go +++ b/discord/message.go @@ -3,10 +3,11 @@ package discord import ( "bytes" "fmt" + "iter" "strconv" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/internal/flags" @@ -101,7 +102,7 @@ type Message struct { Attachments []Attachment `json:"attachments"` TTS bool `json:"tts"` Embeds []Embed `json:"embeds,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` + Components []LayoutComponent `json:"components,omitempty"` CreatedAt time.Time `json:"timestamp"` Mentions []User `json:"mentions"` MentionEveryone bool `json:"mention_everyone"` @@ -159,190 +160,12 @@ func (m *Message) UnmarshalJSON(data []byte) error { return nil } -// ActionRows returns all ActionRowComponent(s) from this Message -func (m Message) ActionRows() []ActionRowComponent { - var actionRows []ActionRowComponent - for i := range m.Components { - if actionRow, ok := m.Components[i].(ActionRowComponent); ok { - actionRows = append(actionRows, actionRow) - } - } - return actionRows -} - -// InteractiveComponents returns the InteractiveComponent(s) from this Message -func (m Message) InteractiveComponents() []InteractiveComponent { - var interactiveComponents []InteractiveComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - interactiveComponents = append(interactiveComponents, m.Components[i].Components()[ii]) - } - } - return interactiveComponents -} - -// ComponentByID returns the Component with the specific CustomID -func (m Message) ComponentByID(customID string) InteractiveComponent { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if m.Components[i].Components()[ii].ID() == customID { - return m.Components[i].Components()[ii] - } - } - } - return nil -} - -// Buttons returns all ButtonComponent(s) from this Message -func (m Message) Buttons() []ButtonComponent { - var buttons []ButtonComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if button, ok := m.Components[i].Components()[ii].(ButtonComponent); ok { - buttons = append(buttons, button) - } - } - } - return buttons -} - -// ButtonByID returns a ButtonComponent with the specific customID from this Message -func (m Message) ButtonByID(customID string) (ButtonComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if button, ok := m.Components[i].Components()[ii].(ButtonComponent); ok && button.ID() == customID { - return button, true - } - } - } - return ButtonComponent{}, false -} - -// SelectMenus returns all SelectMenuComponent(s) from this Message -func (m Message) SelectMenus() []SelectMenuComponent { - var selectMenus []SelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if selectMenu, ok := m.Components[i].Components()[ii].(SelectMenuComponent); ok { - selectMenus = append(selectMenus, selectMenu) - } - } - } - return selectMenus -} - -// SelectMenuByID returns a SelectMenuComponent with the specific customID from this Message -func (m Message) SelectMenuByID(customID string) (SelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if selectMenu, ok := m.Components[i].Components()[ii].(SelectMenuComponent); ok && selectMenu.ID() == customID { - return selectMenu, true - } - } - } - return nil, false -} - -// UserSelectMenus returns all UserSelectMenuComponent(s) from this Message -func (m Message) UserSelectMenus() []UserSelectMenuComponent { - var userSelectMenus []UserSelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if userSelectMenu, ok := m.Components[i].Components()[ii].(UserSelectMenuComponent); ok { - userSelectMenus = append(userSelectMenus, userSelectMenu) - } - } - } - return userSelectMenus -} - -// UserSelectMenuByID returns a UserSelectMenuComponent with the specific customID from this Message -func (m Message) UserSelectMenuByID(customID string) (UserSelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if userSelectMenu, ok := m.Components[i].Components()[ii].(UserSelectMenuComponent); ok && userSelectMenu.ID() == customID { - return userSelectMenu, true - } - } - } - return UserSelectMenuComponent{}, false -} - -// RoleSelectMenus returns all RoleSelectMenuComponent(s) from this Message -func (m Message) RoleSelectMenus() []RoleSelectMenuComponent { - var roleSelectMenus []RoleSelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if roleSelectMenu, ok := m.Components[i].Components()[ii].(RoleSelectMenuComponent); ok { - roleSelectMenus = append(roleSelectMenus, roleSelectMenu) - } - } - } - return roleSelectMenus -} - -// RoleSelectMenuByID returns a RoleSelectMenuComponent with the specific customID from this Message -func (m Message) RoleSelectMenuByID(customID string) (RoleSelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if roleSelectMenu, ok := m.Components[i].Components()[ii].(RoleSelectMenuComponent); ok && roleSelectMenu.ID() == customID { - return roleSelectMenu, true - } - } - } - return RoleSelectMenuComponent{}, false -} - -// MentionableSelectMenus returns all MentionableSelectMenuComponent(s) from this Message -func (m Message) MentionableSelectMenus() []MentionableSelectMenuComponent { - var mentionableSelectMenus []MentionableSelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if mentionableSelectMenu, ok := m.Components[i].Components()[ii].(MentionableSelectMenuComponent); ok { - mentionableSelectMenus = append(mentionableSelectMenus, mentionableSelectMenu) - } - } - } - return mentionableSelectMenus -} - -// MentionableSelectMenuByID returns a MentionableSelectMenuComponent with the specific customID from this Message -func (m Message) MentionableSelectMenuByID(customID string) (MentionableSelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if mentionableSelectMenu, ok := m.Components[i].Components()[ii].(MentionableSelectMenuComponent); ok && mentionableSelectMenu.ID() == customID { - return mentionableSelectMenu, true - } - } - } - return MentionableSelectMenuComponent{}, false -} - -// ChannelSelectMenus returns all ChannelSelectMenuComponent(s) from this Message -func (m Message) ChannelSelectMenus() []ChannelSelectMenuComponent { - var channelSelectMenus []ChannelSelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if channelSelectMenu, ok := m.Components[i].Components()[ii].(ChannelSelectMenuComponent); ok { - channelSelectMenus = append(channelSelectMenus, channelSelectMenu) - } - } - } - return channelSelectMenus -} - -// ChannelSelectMenuByID returns a ChannelSelectMenuComponent with the specific customID from this Message -func (m Message) ChannelSelectMenuByID(customID string) (ChannelSelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if channelSelectMenu, ok := m.Components[i].Components()[ii].(ChannelSelectMenuComponent); ok && channelSelectMenu.ID() == customID { - return channelSelectMenu, true - } - } - } - return ChannelSelectMenuComponent{}, false +// AllComponents returns an [iter.Seq] of all components in the message. +func (m Message) AllComponents() iter.Seq[Component] { + return componentIter(m.Components) } +// JumpURL returns the URL which can be used to jump to the message in the discord client. func (m Message) JumpURL() string { guildID := "@me" if m.GuildID != nil { @@ -439,18 +262,18 @@ type MessageSnapshot struct { } type PartialMessage struct { - Type MessageType `json:"type"` - Content string `json:"content,omitempty"` - Embeds []Embed `json:"embeds,omitempty"` - Attachments []Attachment `json:"attachments"` - CreatedAt time.Time `json:"timestamp"` - EditedTimestamp *time.Time `json:"edited_timestamp"` - Flags MessageFlags `json:"flags"` - Mentions []User `json:"mentions"` - MentionRoles []snowflake.ID `json:"mention_roles"` - Stickers []Sticker `json:"stickers"` - StickerItems []MessageSticker `json:"sticker_items,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` + Type MessageType `json:"type"` + Content string `json:"content,omitempty"` + Embeds []Embed `json:"embeds,omitempty"` + Attachments []Attachment `json:"attachments"` + CreatedAt time.Time `json:"timestamp"` + EditedTimestamp *time.Time `json:"edited_timestamp"` + Flags MessageFlags `json:"flags"` + Mentions []User `json:"mentions"` + MentionRoles []snowflake.ID `json:"mention_roles"` + Stickers []Sticker `json:"stickers"` + StickerItems []MessageSticker `json:"sticker_items,omitempty"` + Components []LayoutComponent `json:"components,omitempty"` } func (m *PartialMessage) UnmarshalJSON(data []byte) error { @@ -473,6 +296,10 @@ func (m *PartialMessage) UnmarshalJSON(data []byte) error { return nil } +func (m PartialMessage) AllComponents() iter.Seq[Component] { + return componentIter(m.Components) +} + // MessageInteraction is sent on the Message object when the message is a response to an interaction type MessageInteraction struct { ID snowflake.ID `json:"id"` @@ -505,6 +332,10 @@ const ( MessageFlagSuppressNotifications MessageFlagIsVoiceMessage MessageFlagHasSnapshot + // MessageFlagIsComponentsV2 should be set when you want to send v2 components. + // After setting this, you will not be allowed to send message content and embeds anymore. + // Once a message with the flag has been sent, it cannot be removed by editing the message. + MessageFlagIsComponentsV2 MessageFlagsNone MessageFlags = 0 ) @@ -560,12 +391,12 @@ type MessageCall struct { EndedTimestamp *time.Time `json:"ended_timestamp"` } -func unmarshalComponents(components []UnmarshalComponent) []ContainerComponent { - containerComponents := make([]ContainerComponent, len(components)) +func unmarshalComponents(components []UnmarshalComponent) []LayoutComponent { + c := make([]LayoutComponent, len(components)) for i := range components { - containerComponents[i] = components[i].Component.(ContainerComponent) + c[i] = components[i].Component.(LayoutComponent) } - return containerComponents + return c } // Nonce is a string or int used when sending a message to discord. diff --git a/discord/message_create.go b/discord/message_create.go index e21cc4cba..15f7e2b03 100644 --- a/discord/message_create.go +++ b/discord/message_create.go @@ -6,19 +6,19 @@ import ( // MessageCreate is the struct to create a new Message with type MessageCreate struct { - Nonce string `json:"nonce,omitempty"` - Content string `json:"content,omitempty"` - TTS bool `json:"tts,omitempty"` - Embeds []Embed `json:"embeds,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` - StickerIDs []snowflake.ID `json:"sticker_ids,omitempty"` - Files []*File `json:"-"` - Attachments []AttachmentCreate `json:"attachments,omitempty"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` - MessageReference *MessageReference `json:"message_reference,omitempty"` - Flags MessageFlags `json:"flags,omitempty"` - EnforceNonce bool `json:"enforce_nonce,omitempty"` - Poll *PollCreate `json:"poll,omitempty"` + Nonce string `json:"nonce,omitempty"` + Content string `json:"content,omitempty"` + TTS bool `json:"tts,omitempty"` + Embeds []Embed `json:"embeds,omitempty"` + Components []LayoutComponent `json:"components,omitempty"` + StickerIDs []snowflake.ID `json:"sticker_ids,omitempty"` + Files []*File `json:"-"` + Attachments []AttachmentCreate `json:"attachments,omitempty"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + MessageReference *MessageReference `json:"message_reference,omitempty"` + Flags MessageFlags `json:"flags,omitempty"` + EnforceNonce bool `json:"enforce_nonce,omitempty"` + Poll *PollCreate `json:"poll,omitempty"` } func (MessageCreate) interactionCallbackData() {} diff --git a/discord/message_create_builder.go b/discord/message_create_builder.go index cf1178c17..24070045d 100644 --- a/discord/message_create_builder.go +++ b/discord/message_create_builder.go @@ -84,14 +84,14 @@ func (b *MessageCreateBuilder) RemoveEmbed(i int) *MessageCreateBuilder { return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the Message -func (b *MessageCreateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *MessageCreateBuilder { - b.Components = containerComponents +// SetComponents sets the discord.LayoutComponent(s) of the Message +func (b *MessageCreateBuilder) SetComponents(components ...LayoutComponent) *MessageCreateBuilder { + b.Components = components return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *MessageCreateBuilder) SetContainerComponent(i int, container ContainerComponent) *MessageCreateBuilder { +// SetComponent sets the provided discord.LayoutComponent at the index of discord.LayoutComponent(s) +func (b *MessageCreateBuilder) SetComponent(i int, container LayoutComponent) *MessageCreateBuilder { if len(b.Components) > i { b.Components[i] = container } @@ -100,27 +100,27 @@ func (b *MessageCreateBuilder) SetContainerComponent(i int, container ContainerC // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the Message func (b *MessageCreateBuilder) AddActionRow(components ...InteractiveComponent) *MessageCreateBuilder { - b.Components = append(b.Components, ActionRowComponent(components)) + b.Components = append(b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the Message -func (b *MessageCreateBuilder) AddContainerComponents(containers ...ContainerComponent) *MessageCreateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the Message +func (b *MessageCreateBuilder) AddComponents(containers ...LayoutComponent) *MessageCreateBuilder { b.Components = append(b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ActionRowComponent from the Message -func (b *MessageCreateBuilder) RemoveContainerComponent(i int) *MessageCreateBuilder { +// RemoveComponent removes a discord.LayoutComponent from the Message +func (b *MessageCreateBuilder) RemoveComponent(i int) *MessageCreateBuilder { if len(b.Components) > i { b.Components = append(b.Components[:i], b.Components[i+1:]...) } return b } -// ClearContainerComponents removes all the discord.ContainerComponent(s) of the Message -func (b *MessageCreateBuilder) ClearContainerComponents() *MessageCreateBuilder { - b.Components = []ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the Message +func (b *MessageCreateBuilder) ClearComponents() *MessageCreateBuilder { + b.Components = []LayoutComponent{} return b } @@ -241,6 +241,17 @@ func (b *MessageCreateBuilder) SetEphemeral(ephemeral bool) *MessageCreateBuilde return b } +// SetIsComponentsV2 adds/removes discord.MessageFlagIsComponentsV2 to the Message flags. +// Once a message with the flag has been sent, it cannot be removed by editing the message. +func (b *MessageCreateBuilder) SetIsComponentsV2(isComponentV2 bool) *MessageCreateBuilder { + if isComponentV2 { + b.Flags = b.Flags.Add(MessageFlagIsComponentsV2) + } else { + b.Flags = b.Flags.Remove(MessageFlagIsComponentsV2) + } + return b +} + // SetSuppressEmbeds adds/removes discord.MessageFlagSuppressEmbeds to the Message flags func (b *MessageCreateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *MessageCreateBuilder { if suppressEmbeds { @@ -251,6 +262,16 @@ func (b *MessageCreateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *MessageCr return b } +// SetSuppressNotifications adds/removes discord.MessageFlagSuppressNotifications to the Message flags +func (b *MessageCreateBuilder) SetSuppressNotifications(suppressNotifications bool) *MessageCreateBuilder { + if suppressNotifications { + b.Flags = b.Flags.Add(MessageFlagSuppressNotifications) + } else { + b.Flags = b.Flags.Remove(MessageFlagSuppressNotifications) + } + return b +} + // SetPoll sets the Poll of the Message func (b *MessageCreateBuilder) SetPoll(poll PollCreate) *MessageCreateBuilder { b.Poll = &poll diff --git a/discord/message_update.go b/discord/message_update.go index ddcd48a09..b7454c713 100644 --- a/discord/message_update.go +++ b/discord/message_update.go @@ -2,12 +2,12 @@ package discord // MessageUpdate is used to edit a Message type MessageUpdate struct { - Content *string `json:"content,omitempty"` - Embeds *[]Embed `json:"embeds,omitempty"` - Components *[]ContainerComponent `json:"components,omitempty"` - Attachments *[]AttachmentUpdate `json:"attachments,omitempty"` - Files []*File `json:"-"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + Content *string `json:"content,omitempty"` + Embeds *[]Embed `json:"embeds,omitempty"` + Components *[]LayoutComponent `json:"components,omitempty"` + Attachments *[]AttachmentUpdate `json:"attachments,omitempty"` + Files []*File `json:"-"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` // Flags are the MessageFlags of the message. // Be careful not to override the current flags when editing messages from other users - this will result in a permission error. // Use MessageFlags.Add for flags like discord.MessageFlagSuppressEmbeds. diff --git a/discord/message_update_builder.go b/discord/message_update_builder.go index c81ca37c5..e40474c34 100644 --- a/discord/message_update_builder.go +++ b/discord/message_update_builder.go @@ -79,19 +79,19 @@ func (b *MessageUpdateBuilder) RemoveEmbed(i int) *MessageUpdateBuilder { return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the Message -func (b *MessageUpdateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *MessageUpdateBuilder { +// SetComponents sets the discord.LayoutComponent(s) of the Message +func (b *MessageUpdateBuilder) SetComponents(components ...LayoutComponent) *MessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } - *b.Components = containerComponents + *b.Components = components return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *MessageUpdateBuilder) SetContainerComponent(i int, container ContainerComponent) *MessageUpdateBuilder { +// SetComponent sets the provided discord.LayoutComponent at the index of discord.LayoutComponent(s) +func (b *MessageUpdateBuilder) SetComponent(i int, container LayoutComponent) *MessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } if len(*b.Components) > i { (*b.Components)[i] = container @@ -102,23 +102,23 @@ func (b *MessageUpdateBuilder) SetContainerComponent(i int, container ContainerC // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the Message func (b *MessageUpdateBuilder) AddActionRow(components ...InteractiveComponent) *MessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } - *b.Components = append(*b.Components, ActionRowComponent(components)) + *b.Components = append(*b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the Message -func (b *MessageUpdateBuilder) AddContainerComponents(containers ...ContainerComponent) *MessageUpdateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the Message +func (b *MessageUpdateBuilder) AddComponents(containers ...LayoutComponent) *MessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } *b.Components = append(*b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ContainerComponent from the Message -func (b *MessageUpdateBuilder) RemoveContainerComponent(i int) *MessageUpdateBuilder { +// RemoveComponent removes a discord.LayoutComponent from the Message +func (b *MessageUpdateBuilder) RemoveComponent(i int) *MessageUpdateBuilder { if b.Components == nil { return b } @@ -128,9 +128,9 @@ func (b *MessageUpdateBuilder) RemoveContainerComponent(i int) *MessageUpdateBui return b } -// ClearContainerComponents removes all the discord.ContainerComponent(s) of the Message -func (b *MessageUpdateBuilder) ClearContainerComponents() *MessageUpdateBuilder { - b.Components = &[]ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the Message +func (b *MessageUpdateBuilder) ClearComponents() *MessageUpdateBuilder { + b.Components = &[]LayoutComponent{} return b } @@ -254,6 +254,21 @@ func (b *MessageUpdateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *MessageUp return b } +// SetIsComponentsV2 adds/removes discord.MessageFlagIsComponentsV2 to the Message flags. +// Once a message with the flag has been sent, it cannot be removed by editing the message. +func (b *MessageUpdateBuilder) SetIsComponentsV2(isComponentV2 bool) *MessageUpdateBuilder { + if b.Flags == nil { + b.Flags = new(MessageFlags) + } + + if isComponentV2 { + *b.Flags = b.Flags.Add(MessageFlagIsComponentsV2) + } else { + *b.Flags = b.Flags.Remove(MessageFlagIsComponentsV2) + } + return b +} + // Build builds the MessageUpdateBuilder to a MessageUpdate struct func (b *MessageUpdateBuilder) Build() MessageUpdate { return b.MessageUpdate diff --git a/discord/modal_create.go b/discord/modal_create.go index 21291e362..d05b7bba9 100644 --- a/discord/modal_create.go +++ b/discord/modal_create.go @@ -3,9 +3,9 @@ package discord var _ InteractionResponseData = (*ModalCreate)(nil) type ModalCreate struct { - CustomID string `json:"custom_id"` - Title string `json:"title"` - Components []ContainerComponent `json:"components"` + CustomID string `json:"custom_id"` + Title string `json:"title"` + Components []LayoutComponent `json:"components"` } func (ModalCreate) interactionCallbackData() {} @@ -31,14 +31,14 @@ func (b *ModalCreateBuilder) SetTitle(title string) *ModalCreateBuilder { return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the ModalCreate -func (b *ModalCreateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *ModalCreateBuilder { - b.Components = containerComponents +// SetComponents sets the discord.LayoutComponent(s) of the ModalCreate +func (b *ModalCreateBuilder) SetComponents(components ...LayoutComponent) *ModalCreateBuilder { + b.Components = components return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *ModalCreateBuilder) SetContainerComponent(i int, container ContainerComponent) *ModalCreateBuilder { +// SetComponent sets the provided discord.LayoutComponent at the index of discord.LayoutComponent(s) +func (b *ModalCreateBuilder) SetComponent(i int, container LayoutComponent) *ModalCreateBuilder { if len(b.Components) > i { b.Components[i] = container } @@ -47,27 +47,27 @@ func (b *ModalCreateBuilder) SetContainerComponent(i int, container ContainerCom // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the ModalCreate func (b *ModalCreateBuilder) AddActionRow(components ...InteractiveComponent) *ModalCreateBuilder { - b.Components = append(b.Components, ActionRowComponent(components)) + b.Components = append(b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the ModalCreate -func (b *ModalCreateBuilder) AddContainerComponents(containers ...ContainerComponent) *ModalCreateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the ModalCreate +func (b *ModalCreateBuilder) AddComponents(containers ...LayoutComponent) *ModalCreateBuilder { b.Components = append(b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ActionRowComponent from the ModalCreate -func (b *ModalCreateBuilder) RemoveContainerComponent(i int) *ModalCreateBuilder { +// RemoveComponent removes a discord.LayoutComponent from the ModalCreate +func (b *ModalCreateBuilder) RemoveComponent(i int) *ModalCreateBuilder { if len(b.Components) > i { b.Components = append(b.Components[:i], b.Components[i+1:]...) } return b } -// ClearContainerComponents removes all the discord.ContainerComponent(s) of the ModalCreate -func (b *ModalCreateBuilder) ClearContainerComponents() *ModalCreateBuilder { - b.Components = []ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the ModalCreate +func (b *ModalCreateBuilder) ClearComponents() *ModalCreateBuilder { + b.Components = []LayoutComponent{} return b } diff --git a/discord/permission_overwrite.go b/discord/permission_overwrite.go index 83a91fdb8..2bc68d954 100644 --- a/discord/permission_overwrite.go +++ b/discord/permission_overwrite.go @@ -3,7 +3,7 @@ package discord import ( "fmt" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/permissions.go b/discord/permissions.go index 2206f295f..8ad934ae7 100644 --- a/discord/permissions.go +++ b/discord/permissions.go @@ -5,13 +5,11 @@ import ( "strconv" "strings" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/disgo/internal/flags" ) -var EmptyStringBytes = []byte(`""`) - // Permissions extends the Bit structure, and is used within roles and channels (https://discord.com/developers/docs/topics/permissions#permissions) type Permissions int64 @@ -201,7 +199,7 @@ func (p Permissions) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshalls permissions into an int64 func (p *Permissions) UnmarshalJSON(data []byte) error { - if bytes.Equal(data, EmptyStringBytes) || bytes.Equal(data, json.NullBytes) { + if bytes.Equal(data, []byte("")) || bytes.Equal(data, []byte("null")) { return nil } diff --git a/discord/permissions_test.go b/discord/permissions_test.go index 521e563a4..c2df85dd3 100644 --- a/discord/permissions_test.go +++ b/discord/permissions_test.go @@ -3,8 +3,7 @@ package discord import ( "testing" - "github.com/disgoorg/json" - + "github.com/disgoorg/json/v2" "github.com/stretchr/testify/assert" ) diff --git a/discord/poll.go b/discord/poll.go index d54d95475..d7e93e535 100644 --- a/discord/poll.go +++ b/discord/poll.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" ) type Poll struct { diff --git a/discord/role.go b/discord/role.go index 9e29c35cd..edc3ae3c6 100644 --- a/discord/role.go +++ b/discord/role.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" ) @@ -16,6 +16,7 @@ type Role struct { Name string `json:"name"` Description *string `json:"description,omitempty"` Color int `json:"color"` + RoleColors RoleColors `json:"colors"` Hoist bool `json:"hoist"` Position int `json:"position"` Permissions Permissions `json:"permissions"` @@ -47,6 +48,12 @@ func (r Role) CreatedAt() time.Time { return r.ID.Time() } +type RoleColors struct { + PrimaryColor int `json:"primary_color"` + SecondaryColor *int `json:"secondary_color"` + TertiaryColor *int `json:"tertiary_color"` +} + // RoleTag are tags a Role has type RoleTag struct { BotID *snowflake.ID `json:"bot_id,omitempty"` @@ -69,6 +76,7 @@ type RoleCreate struct { Name string `json:"name,omitempty"` Permissions *Permissions `json:"permissions,omitempty"` Color int `json:"color,omitempty"` + Colors RoleColors `json:"colors,omitempty"` Hoist bool `json:"hoist,omitempty"` Icon *Icon `json:"icon,omitempty"` Emoji string `json:"unicode_emoji,omitempty"` @@ -77,13 +85,14 @@ type RoleCreate struct { // RoleUpdate is the payload to update a Role type RoleUpdate struct { - Name *string `json:"name,omitempty"` - Permissions *Permissions `json:"permissions,omitempty"` - Color *int `json:"color,omitempty"` - Hoist *bool `json:"hoist,omitempty"` - Icon *json.Nullable[Icon] `json:"icon,omitempty"` - Emoji *string `json:"unicode_emoji,omitempty"` - Mentionable *bool `json:"mentionable,omitempty"` + Name *string `json:"name,omitempty"` + Permissions *Permissions `json:"permissions,omitempty"` + Color *int `json:"color,omitempty"` + Colors omit.Omit[*RoleColors] `json:"colors,omitzero"` + Hoist *bool `json:"hoist,omitempty"` + Icon omit.Omit[*Icon] `json:"icon,omitzero"` + Emoji *string `json:"unicode_emoji,omitempty"` + Mentionable *bool `json:"mentionable,omitempty"` } // RolePositionUpdate is the payload to update a Role(s) position diff --git a/discord/select_menu.go b/discord/select_menu.go index 6f7105dee..393d3eb14 100644 --- a/discord/select_menu.go +++ b/discord/select_menu.go @@ -1,13 +1,20 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) +// SelectMenuComponent is an interface for all components that are select menus. +// [StringSelectMenuComponent] +// [UserSelectMenuComponent] +// [RoleSelectMenuComponent] +// [MentionableSelectMenuComponent] +// [ChannelSelectMenuComponent] +// [UnknownComponent] type SelectMenuComponent interface { InteractiveComponent - selectMenu() + selectMenuComponent() } var ( @@ -26,6 +33,7 @@ func NewStringSelectMenu(customID string, placeholder string, options ...StringS } type StringSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` MinValues *int `json:"min_values,omitempty"` @@ -49,13 +57,17 @@ func (StringSelectMenuComponent) Type() ComponentType { return ComponentTypeStringSelectMenu } -func (c StringSelectMenuComponent) ID() string { +func (c StringSelectMenuComponent) GetID() int { + return c.ID +} + +func (c StringSelectMenuComponent) GetCustomID() string { return c.CustomID } func (StringSelectMenuComponent) component() {} func (StringSelectMenuComponent) interactiveComponent() {} -func (StringSelectMenuComponent) selectMenu() {} +func (StringSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new StringSelectMenuComponent with the provided customID func (c StringSelectMenuComponent) WithCustomID(customID string) StringSelectMenuComponent { @@ -130,6 +142,11 @@ func (c StringSelectMenuComponent) RemoveOption(index int) StringSelectMenuCompo return c } +func (c StringSelectMenuComponent) WithID(i int) InteractiveComponent { + c.ID = i + return c +} + // NewStringSelectMenuOption builds a new StringSelectMenuOption func NewStringSelectMenuOption(label string, value string) StringSelectMenuOption { return StringSelectMenuOption{ @@ -140,6 +157,7 @@ func NewStringSelectMenuOption(label string, value string) StringSelectMenuOptio // StringSelectMenuOption represents an option in a StringSelectMenuComponent type StringSelectMenuOption struct { + ID int `json:"id,omitempty"` Label string `json:"label"` Value string `json:"value"` Description string `json:"description,omitempty"` @@ -192,6 +210,7 @@ func NewUserSelectMenu(customID string, placeholder string) UserSelectMenuCompon } type UserSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` @@ -215,13 +234,17 @@ func (UserSelectMenuComponent) Type() ComponentType { return ComponentTypeUserSelectMenu } -func (c UserSelectMenuComponent) ID() string { +func (c UserSelectMenuComponent) GetID() int { + return c.ID +} + +func (c UserSelectMenuComponent) GetCustomID() string { return c.CustomID } func (UserSelectMenuComponent) component() {} func (UserSelectMenuComponent) interactiveComponent() {} -func (UserSelectMenuComponent) selectMenu() {} +func (UserSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new UserSelectMenuComponent with the provided customID func (c UserSelectMenuComponent) WithCustomID(customID string) UserSelectMenuComponent { @@ -304,6 +327,7 @@ func NewRoleSelectMenu(customID string, placeholder string) RoleSelectMenuCompon } type RoleSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` @@ -327,13 +351,17 @@ func (RoleSelectMenuComponent) Type() ComponentType { return ComponentTypeRoleSelectMenu } -func (c RoleSelectMenuComponent) ID() string { +func (c RoleSelectMenuComponent) GetID() int { + return c.ID +} + +func (c RoleSelectMenuComponent) GetCustomID() string { return c.CustomID } func (RoleSelectMenuComponent) component() {} func (RoleSelectMenuComponent) interactiveComponent() {} -func (RoleSelectMenuComponent) selectMenu() {} +func (RoleSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new RoleSelectMenuComponent with the provided customID func (c RoleSelectMenuComponent) WithCustomID(customID string) RoleSelectMenuComponent { @@ -416,6 +444,7 @@ func NewMentionableSelectMenu(customID string, placeholder string) MentionableSe } type MentionableSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` @@ -439,13 +468,17 @@ func (MentionableSelectMenuComponent) Type() ComponentType { return ComponentTypeMentionableSelectMenu } -func (c MentionableSelectMenuComponent) ID() string { +func (c MentionableSelectMenuComponent) GetID() int { + return c.ID +} + +func (c MentionableSelectMenuComponent) GetCustomID() string { return c.CustomID } func (MentionableSelectMenuComponent) component() {} func (MentionableSelectMenuComponent) interactiveComponent() {} -func (MentionableSelectMenuComponent) selectMenu() {} +func (MentionableSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new MentionableSelectMenuComponent with the provided customID func (c MentionableSelectMenuComponent) WithCustomID(customID string) MentionableSelectMenuComponent { @@ -525,6 +558,7 @@ func NewChannelSelectMenu(customID string, placeholder string) ChannelSelectMenu } type ChannelSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` @@ -549,13 +583,17 @@ func (ChannelSelectMenuComponent) Type() ComponentType { return ComponentTypeChannelSelectMenu } -func (c ChannelSelectMenuComponent) ID() string { +func (c ChannelSelectMenuComponent) GetID() int { + return c.ID +} + +func (c ChannelSelectMenuComponent) GetCustomID() string { return c.CustomID } func (ChannelSelectMenuComponent) component() {} func (ChannelSelectMenuComponent) interactiveComponent() {} -func (ChannelSelectMenuComponent) selectMenu() {} +func (ChannelSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new ChannelSelectMenuComponent with the provided customID func (c ChannelSelectMenuComponent) WithCustomID(customID string) ChannelSelectMenuComponent { diff --git a/discord/slash_command_option.go b/discord/slash_command_option.go index 513a549b1..39551318d 100644 --- a/discord/slash_command_option.go +++ b/discord/slash_command_option.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" ) type internalSlashCommandOption interface { diff --git a/discord/sound.go b/discord/sound.go index 0bd2908fe..ec13aef7c 100644 --- a/discord/sound.go +++ b/discord/sound.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" ) type SoundType string diff --git a/discord/soundboard.go b/discord/soundboard.go index 0dc4b72ac..9a8363897 100644 --- a/discord/soundboard.go +++ b/discord/soundboard.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" ) @@ -36,10 +36,10 @@ type SoundboardSoundCreate struct { } type SoundboardSoundUpdate struct { - Name *string `json:"name,omitempty"` - Volume *json.Nullable[float64] `json:"volume,omitempty"` - EmojiID *json.Nullable[snowflake.ID] `json:"emoji_id,omitempty"` - EmojiName *json.Nullable[string] `json:"emoji_name,omitempty"` + Name *string `json:"name,omitempty"` + Volume omit.Omit[*float64] `json:"volume,omitzero"` + EmojiID omit.Omit[*snowflake.ID] `json:"emoji_id,omitzero"` + EmojiName omit.Omit[*string] `json:"emoji_name,omitzero"` } type SendSoundboardSound struct { diff --git a/discord/thread.go b/discord/thread.go index 5a6c19da5..73d25aecf 100644 --- a/discord/thread.go +++ b/discord/thread.go @@ -1,7 +1,7 @@ package discord import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/discord/user.go b/discord/user.go index bdf74c4f0..3787368f0 100644 --- a/discord/user.go +++ b/discord/user.go @@ -4,7 +4,7 @@ import ( "strconv" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/internal/flags" @@ -180,9 +180,9 @@ const ( // UserUpdate is the payload used to update the OAuth2User type UserUpdate struct { - Username string `json:"username,omitempty"` - Avatar *json.Nullable[Icon] `json:"avatar,omitempty"` - Banner *json.Nullable[Icon] `json:"banner,omitempty"` + Username string `json:"username,omitempty"` + Avatar omit.Omit[*Icon] `json:"avatar,omitzero"` + Banner omit.Omit[*Icon] `json:"banner,omitzero"` } type ApplicationRoleConnection struct { diff --git a/discord/voice_state.go b/discord/voice_state.go index 2cec09236..5ca82c77b 100644 --- a/discord/voice_state.go +++ b/discord/voice_state.go @@ -3,7 +3,7 @@ package discord import ( "time" - "github.com/disgoorg/json" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" ) @@ -24,9 +24,9 @@ type VoiceState struct { } type CurrentUserVoiceStateUpdate struct { - ChannelID *snowflake.ID `json:"channel_id,omitempty"` - Suppress *bool `json:"suppress,omitempty"` - RequestToSpeakTimestamp *json.Nullable[time.Time] `json:"request_to_speak_timestamp,omitempty"` + ChannelID *snowflake.ID `json:"channel_id,omitempty"` + Suppress *bool `json:"suppress,omitempty"` + RequestToSpeakTimestamp omit.Omit[*time.Time] `json:"request_to_speak_timestamp,omitzero"` } type UserVoiceStateUpdate struct { diff --git a/discord/webhook.go b/discord/webhook.go index aa1639f4c..874da5a0f 100644 --- a/discord/webhook.go +++ b/discord/webhook.go @@ -4,7 +4,8 @@ import ( "fmt" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" + "github.com/disgoorg/omit" "github.com/disgoorg/snowflake/v2" ) @@ -331,9 +332,9 @@ func (w ApplicationWebhook) CreatedAt() time.Time { func (ApplicationWebhook) webhook() {} type WebhookSourceGuild struct { - ID snowflake.ID `json:"id"` - Name string `json:"name"` - Icon *string `json:"icon"` + ID snowflake.ID `json:"id"` + Name string `json:"name"` + Icon *string `json:"icon"` } type WebhookSourceChannel struct { @@ -349,13 +350,13 @@ type WebhookCreate struct { // WebhookUpdate is used to update a Webhook type WebhookUpdate struct { - Name *string `json:"name,omitempty"` - Avatar *json.Nullable[Icon] `json:"avatar,omitempty"` - ChannelID *snowflake.ID `json:"channel_id,omitempty"` + Name *string `json:"name,omitempty"` + Avatar omit.Omit[*Icon] `json:"avatar,omitzero"` + ChannelID *snowflake.ID `json:"channel_id,omitempty"` } // WebhookUpdateWithToken is used to update a Webhook with the token type WebhookUpdateWithToken struct { - Name *string `json:"name,omitempty"` - Avatar *json.Nullable[Icon] `json:"avatar,omitempty"` + Name *string `json:"name,omitempty"` + Avatar omit.Omit[*Icon] `json:"avatar,omitzero"` } diff --git a/discord/webhook_message_create.go b/discord/webhook_message_create.go index f84e4ea8d..7f7ac78ce 100644 --- a/discord/webhook_message_create.go +++ b/discord/webhook_message_create.go @@ -3,19 +3,19 @@ package discord import "github.com/disgoorg/snowflake/v2" type WebhookMessageCreate struct { - Content string `json:"content,omitempty"` - Username string `json:"username,omitempty"` - AvatarURL string `json:"avatar_url,omitempty"` - TTS bool `json:"tts,omitempty"` - Embeds []Embed `json:"embeds,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` - Attachments []AttachmentCreate `json:"attachments,omitempty"` - Files []*File `json:"-"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` - Flags MessageFlags `json:"flags,omitempty"` - ThreadName string `json:"thread_name,omitempty"` - AppliedTags []snowflake.ID `json:"applied_tags,omitempty"` - Poll *PollCreate `json:"poll,omitempty"` + Content string `json:"content,omitempty"` + Username string `json:"username,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + TTS bool `json:"tts,omitempty"` + Embeds []Embed `json:"embeds,omitempty"` + Components []LayoutComponent `json:"components,omitempty"` + Attachments []AttachmentCreate `json:"attachments,omitempty"` + Files []*File `json:"-"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + Flags MessageFlags `json:"flags,omitempty"` + ThreadName string `json:"thread_name,omitempty"` + AppliedTags []snowflake.ID `json:"applied_tags,omitempty"` + Poll *PollCreate `json:"poll,omitempty"` } // ToBody returns the MessageCreate ready for body diff --git a/discord/webhook_message_create_builder.go b/discord/webhook_message_create_builder.go index 4c8666959..01b937681 100644 --- a/discord/webhook_message_create_builder.go +++ b/discord/webhook_message_create_builder.go @@ -80,14 +80,14 @@ func (b *WebhookMessageCreateBuilder) RemoveEmbed(i int) *WebhookMessageCreateBu return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the Message -func (b *WebhookMessageCreateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *WebhookMessageCreateBuilder { - b.Components = containerComponents +// SetComponents sets the discord.LayoutComponent(s) of the Message +func (b *WebhookMessageCreateBuilder) SetComponents(components ...LayoutComponent) *WebhookMessageCreateBuilder { + b.Components = components return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *WebhookMessageCreateBuilder) SetContainerComponent(i int, container ContainerComponent) *WebhookMessageCreateBuilder { +// SetComponent sets the provided discord.LayoutComponent at the index of discord.LayoutComponent(s) +func (b *WebhookMessageCreateBuilder) SetComponent(i int, container LayoutComponent) *WebhookMessageCreateBuilder { if len(b.Components) > i { b.Components[i] = container } @@ -96,27 +96,27 @@ func (b *WebhookMessageCreateBuilder) SetContainerComponent(i int, container Con // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the Message func (b *WebhookMessageCreateBuilder) AddActionRow(components ...InteractiveComponent) *WebhookMessageCreateBuilder { - b.Components = append(b.Components, ActionRowComponent(components)) + b.Components = append(b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the Message -func (b *WebhookMessageCreateBuilder) AddContainerComponents(containers ...ContainerComponent) *WebhookMessageCreateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the Message +func (b *WebhookMessageCreateBuilder) AddComponents(containers ...LayoutComponent) *WebhookMessageCreateBuilder { b.Components = append(b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ActionRowComponent from the Message -func (b *WebhookMessageCreateBuilder) RemoveContainerComponent(i int) *WebhookMessageCreateBuilder { +// RemoveComponent removes a discord.LayoutComponent from the Message +func (b *WebhookMessageCreateBuilder) RemoveComponent(i int) *WebhookMessageCreateBuilder { if len(b.Components) > i { b.Components = append(b.Components[:i], b.Components[i+1:]...) } return b } -// ClearContainerComponents removes all the discord.ContainerComponent(s) of the Message -func (b *WebhookMessageCreateBuilder) ClearContainerComponents() *WebhookMessageCreateBuilder { - b.Components = []ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the Message +func (b *WebhookMessageCreateBuilder) ClearComponents() *WebhookMessageCreateBuilder { + b.Components = []LayoutComponent{} return b } @@ -194,6 +194,17 @@ func (b *WebhookMessageCreateBuilder) ClearFlags() *WebhookMessageCreateBuilder return b.SetFlags(MessageFlagsNone) } +// SetIsComponentsV2 adds/removes discord.MessageFlagIsComponentsV2 to the Message flags. +// Once a message with the flag has been sent, it cannot be removed by editing the message. +func (b *WebhookMessageCreateBuilder) SetIsComponentsV2(isComponentV2 bool) *WebhookMessageCreateBuilder { + if isComponentV2 { + b.Flags = b.Flags.Add(MessageFlagIsComponentsV2) + } else { + b.Flags = b.Flags.Remove(MessageFlagIsComponentsV2) + } + return b +} + // SetSuppressEmbeds adds/removes discord.MessageFlagSuppressEmbeds to the Message flags func (b *WebhookMessageCreateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *WebhookMessageCreateBuilder { if suppressEmbeds { @@ -204,6 +215,16 @@ func (b *WebhookMessageCreateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *We return b } +// SetSuppressNotifications adds/removes discord.MessageFlagSuppressNotifications to the Message flags +func (b *WebhookMessageCreateBuilder) SetSuppressNotifications(suppressNotifications bool) *WebhookMessageCreateBuilder { + if suppressNotifications { + b.Flags = b.Flags.Add(MessageFlagSuppressNotifications) + } else { + b.Flags = b.Flags.Remove(MessageFlagSuppressNotifications) + } + return b +} + // SetThreadName sets the thread name the new webhook message should create. func (b *WebhookMessageCreateBuilder) SetThreadName(threadName string) *WebhookMessageCreateBuilder { b.ThreadName = threadName diff --git a/discord/webhook_message_update.go b/discord/webhook_message_update.go index c756d1d39..e2b8c9ff9 100644 --- a/discord/webhook_message_update.go +++ b/discord/webhook_message_update.go @@ -2,13 +2,17 @@ package discord // WebhookMessageUpdate is used to edit a Message type WebhookMessageUpdate struct { - Content *string `json:"content,omitempty"` - Embeds *[]Embed `json:"embeds,omitempty"` - Components *[]ContainerComponent `json:"components,omitempty"` - Attachments *[]AttachmentUpdate `json:"attachments,omitempty"` - Files []*File `json:"-"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` - Poll *PollCreate `json:"poll,omitempty"` + Content *string `json:"content,omitempty"` + Embeds *[]Embed `json:"embeds,omitempty"` + Components *[]LayoutComponent `json:"components,omitempty"` + Attachments *[]AttachmentUpdate `json:"attachments,omitempty"` + Files []*File `json:"-"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + Poll *PollCreate `json:"poll,omitempty"` + // Flags are the MessageFlags of the message. + // Be careful not to override the current flags when editing messages from other users - this will result in a permission error. + // Use MessageFlags.Add for flags like discord.MessageFlagIsComponentsV2. + Flags *MessageFlags `json:"flags,omitempty"` } // ToBody returns the WebhookMessageUpdate ready for body diff --git a/discord/webhook_message_update_builder.go b/discord/webhook_message_update_builder.go index b031fa642..468c8f2ff 100644 --- a/discord/webhook_message_update_builder.go +++ b/discord/webhook_message_update_builder.go @@ -83,19 +83,19 @@ func (b *WebhookMessageUpdateBuilder) RemoveEmbed(i int) *WebhookMessageUpdateBu return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the Message -func (b *WebhookMessageUpdateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *WebhookMessageUpdateBuilder { +// SetComponents sets the discord.LayoutComponent(s) of the Message +func (b *WebhookMessageUpdateBuilder) SetComponents(components ...LayoutComponent) *WebhookMessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } - *b.Components = containerComponents + *b.Components = components return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *WebhookMessageUpdateBuilder) SetContainerComponent(i int, container ContainerComponent) *WebhookMessageUpdateBuilder { +// SetComponent sets the provided discord.LayoutComponent at the index of discord.LayoutComponent(s) +func (b *WebhookMessageUpdateBuilder) SetComponent(i int, container LayoutComponent) *WebhookMessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } if len(*b.Components) > i { (*b.Components)[i] = container @@ -106,23 +106,23 @@ func (b *WebhookMessageUpdateBuilder) SetContainerComponent(i int, container Con // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the Message func (b *WebhookMessageUpdateBuilder) AddActionRow(components ...InteractiveComponent) *WebhookMessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } - *b.Components = append(*b.Components, ActionRowComponent(components)) + *b.Components = append(*b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the Message -func (b *WebhookMessageUpdateBuilder) AddContainerComponents(containers ...ContainerComponent) *WebhookMessageUpdateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the Message +func (b *WebhookMessageUpdateBuilder) AddComponents(containers ...LayoutComponent) *WebhookMessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } *b.Components = append(*b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ContainerComponent from the Message -func (b *WebhookMessageUpdateBuilder) RemoveContainerComponent(i int) *WebhookMessageUpdateBuilder { +// RemoveComponent removes a discord.LayoutComponent from the Message +func (b *WebhookMessageUpdateBuilder) RemoveComponent(i int) *WebhookMessageUpdateBuilder { if b.Components == nil { return b } @@ -132,9 +132,9 @@ func (b *WebhookMessageUpdateBuilder) RemoveContainerComponent(i int) *WebhookMe return b } -// ClearContainerComponents removes all the discord.ContainerComponent(s) of the Message -func (b *WebhookMessageUpdateBuilder) ClearContainerComponents() *WebhookMessageUpdateBuilder { - b.Components = &[]ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the Message +func (b *WebhookMessageUpdateBuilder) ClearComponents() *WebhookMessageUpdateBuilder { + b.Components = &[]LayoutComponent{} return b } @@ -223,6 +223,68 @@ func (b *WebhookMessageUpdateBuilder) ClearPoll() *WebhookMessageUpdateBuilder { return b } +// SetFlags sets the MessageFlags of the Message. +// Be careful not to override the current flags when editing messages from other users - this will result in a permission error. +// Use SetIsComponentsV2 or AddFlags for flags like discord.MessageFlagIsComponentsV2. +func (b *WebhookMessageUpdateBuilder) SetFlags(flags MessageFlags) *WebhookMessageUpdateBuilder { + if b.Flags == nil { + b.Flags = new(MessageFlags) + } + *b.Flags = flags + return b +} + +// AddFlags adds the MessageFlags of the Message +func (b *WebhookMessageUpdateBuilder) AddFlags(flags ...MessageFlags) *WebhookMessageUpdateBuilder { + if b.Flags == nil { + b.Flags = new(MessageFlags) + } + *b.Flags = b.Flags.Add(flags...) + return b +} + +// RemoveFlags removes the MessageFlags of the Message +func (b *WebhookMessageUpdateBuilder) RemoveFlags(flags ...MessageFlags) *WebhookMessageUpdateBuilder { + if b.Flags == nil { + b.Flags = new(MessageFlags) + } + *b.Flags = b.Flags.Remove(flags...) + return b +} + +// ClearFlags clears the MessageFlags of the Message +func (b *WebhookMessageUpdateBuilder) ClearFlags() *WebhookMessageUpdateBuilder { + return b.SetFlags(MessageFlagsNone) +} + +// SetSuppressEmbeds adds/removes discord.MessageFlagSuppressEmbeds to the Message flags +func (b *WebhookMessageUpdateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *WebhookMessageUpdateBuilder { + if b.Flags == nil { + b.Flags = new(MessageFlags) + } + if suppressEmbeds { + *b.Flags = b.Flags.Add(MessageFlagSuppressEmbeds) + } else { + *b.Flags = b.Flags.Remove(MessageFlagSuppressEmbeds) + } + return b +} + +// SetIsComponentsV2 adds/removes discord.MessageFlagIsComponentsV2 to the Message flags. +// Once a message with the flag has been sent, it cannot be removed by editing the message. +func (b *WebhookMessageUpdateBuilder) SetIsComponentsV2(isComponentV2 bool) *WebhookMessageUpdateBuilder { + if b.Flags == nil { + b.Flags = new(MessageFlags) + } + + if isComponentV2 { + *b.Flags = b.Flags.Add(MessageFlagIsComponentsV2) + } else { + *b.Flags = b.Flags.Remove(MessageFlagIsComponentsV2) + } + return b +} + // Build builds the WebhookMessageUpdateBuilder to a MessageUpdate struct func (b *WebhookMessageUpdateBuilder) Build() WebhookMessageUpdate { return b.WebhookMessageUpdate diff --git a/events/guild_message_poll_events.go b/events/guild_message_poll_events.go index 9c9829ceb..b5820bacb 100644 --- a/events/guild_message_poll_events.go +++ b/events/guild_message_poll_events.go @@ -1,8 +1,9 @@ package events import ( - "github.com/disgoorg/disgo/discord" "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo/discord" ) // GenericGuildMessagePollVote is called upon receiving GuildMessagePollVoteAdd or GuildMessagePollVoteRemove (requires gateway.IntentGuildMessagePolls) diff --git a/events/guild_soundboard_events.go b/events/guild_soundboard_events.go index 8064fd9dc..c8244253d 100644 --- a/events/guild_soundboard_events.go +++ b/events/guild_soundboard_events.go @@ -1,8 +1,9 @@ package events import ( - "github.com/disgoorg/disgo/discord" "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo/discord" ) // GenericGuildSoundboardSound is called upon receiving GuildSoundboardSoundCreate and GuildSoundboardSoundUpdate (requires gateway.IntentGuildExpressions) diff --git a/events/interaction_events.go b/events/interaction_events.go index 4767c1525..764f9c84b 100644 --- a/events/interaction_events.go +++ b/events/interaction_events.go @@ -80,12 +80,6 @@ func (e *ApplicationCommandInteractionCreate) Modal(modalCreate discord.ModalCre return e.Respond(discord.InteractionResponseTypeModal, modalCreate, opts...) } -// Deprecated: Respond with a discord.ButtonStylePremium button instead. -// PremiumRequired responds to the interaction with an upgrade button if available. -func (e *ApplicationCommandInteractionCreate) PremiumRequired(opts ...rest.RequestOpt) error { - return e.Respond(discord.InteractionResponseTypePremiumRequired, nil, opts...) -} - // LaunchActivity responds to the interaction by launching activity associated with the app. func (e *ApplicationCommandInteractionCreate) LaunchActivity(opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeLaunchActivity, nil, opts...) @@ -156,12 +150,6 @@ func (e *ComponentInteractionCreate) Modal(modalCreate discord.ModalCreate, opts return e.Respond(discord.InteractionResponseTypeModal, modalCreate, opts...) } -// Deprecated: Respond with a discord.ButtonStylePremium button instead. -// PremiumRequired responds to the interaction with an upgrade button if available. -func (e *ComponentInteractionCreate) PremiumRequired(opts ...rest.RequestOpt) error { - return e.Respond(discord.InteractionResponseTypePremiumRequired, nil, opts...) -} - // LaunchActivity responds to the interaction by launching activity associated with the app. func (e *ComponentInteractionCreate) LaunchActivity(opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeLaunchActivity, nil, opts...) @@ -268,12 +256,6 @@ func (e *ModalSubmitInteractionCreate) DeferUpdateMessage(opts ...rest.RequestOp return e.Respond(discord.InteractionResponseTypeDeferredUpdateMessage, nil, opts...) } -// Deprecated: Respond with a discord.ButtonStylePremium button instead. -// PremiumRequired responds to the interaction with an upgrade button if available. -func (e *ModalSubmitInteractionCreate) PremiumRequired(opts ...rest.RequestOpt) error { - return e.Respond(discord.InteractionResponseTypePremiumRequired, nil, opts...) -} - // LaunchActivity responds to the interaction by launching activity associated with the app. func (e *ModalSubmitInteractionCreate) LaunchActivity(opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeLaunchActivity, nil, opts...) diff --git a/events/message_poll_events.go b/events/message_poll_events.go index e6dd72757..3171cd741 100644 --- a/events/message_poll_events.go +++ b/events/message_poll_events.go @@ -1,8 +1,9 @@ package events import ( - "github.com/disgoorg/disgo/discord" "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo/discord" ) // GenericMessagePollVote is a generic poll vote event (requires gateway.IntentGuildMessagePolls and/or gateway.IntentDirectMessagePolls) diff --git a/events/user_activity_events.go b/events/user_activity_events.go index 8a9e62683..7c37eca15 100644 --- a/events/user_activity_events.go +++ b/events/user_activity_events.go @@ -1,10 +1,10 @@ package events import ( - "github.com/disgoorg/disgo/gateway" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/gateway" ) type PresenceUpdate struct { diff --git a/gateway/gateway_events.go b/gateway/gateway_events.go index 3dcdd4f87..7c606c20c 100644 --- a/gateway/gateway_events.go +++ b/gateway/gateway_events.go @@ -4,7 +4,7 @@ import ( "io" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" @@ -43,6 +43,12 @@ type EventReady struct { func (EventReady) messageData() {} func (EventReady) eventData() {} +// EventResumed is the event sent by discord when you successfully resume +type EventResumed struct{} + +func (EventResumed) messageData() {} +func (EventResumed) eventData() {} + type EventApplicationCommandPermissionsUpdate struct { discord.ApplicationCommandPermissions } diff --git a/gateway/gateway_impl.go b/gateway/gateway_impl.go index ad8b8d0de..d8aec1764 100644 --- a/gateway/gateway_impl.go +++ b/gateway/gateway_impl.go @@ -13,7 +13,7 @@ import ( "syscall" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/gorilla/websocket" "github.com/disgoorg/disgo/discord" @@ -80,8 +80,8 @@ func (g *gatewayImpl) open(ctx context.Context) error { g.config.Logger.Debug("opening gateway connection") g.connMu.Lock() - defer g.connMu.Unlock() if g.conn != nil { + g.connMu.Unlock() return discord.ErrGatewayAlreadyConnected } g.status = StatusConnecting @@ -107,6 +107,7 @@ func (g *gatewayImpl) open(ctx context.Context) error { } g.config.Logger.Error("error connecting to the gateway", slog.Any("err", err), slog.String("url", gatewayURL), slog.String("body", body)) + g.connMu.Unlock() return err } @@ -115,13 +116,30 @@ func (g *gatewayImpl) open(ctx context.Context) error { }) g.conn = conn + g.connMu.Unlock() // reset rate limiter when connecting g.config.RateLimiter.Reset() g.status = StatusWaitingForHello - go g.listen(conn) + readyChan := make(chan error) + go g.listen(conn, readyChan) + + select { + case <-ctx.Done(): + closeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + g.Close(closeCtx) + return ctx.Err() + case err = <-readyChan: + if err != nil { + closeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + g.Close(closeCtx) + return fmt.Errorf("failed to open gateway connection: %w", err) + } + } return nil } @@ -216,6 +234,13 @@ func (g *gatewayImpl) reconnectTry(ctx context.Context, try int) error { } if err := g.open(ctx); err != nil { + var closeError *websocket.CloseError + if errors.As(err, &closeError) { + closeCode := CloseEventCodeByCode(closeError.Code) + if !closeCode.Reconnect { + return err + } + } if errors.Is(err, discord.ErrGatewayAlreadyConnected) { return err } @@ -276,7 +301,7 @@ func (g *gatewayImpl) sendHeartbeat() { g.lastHeartbeatSent = time.Now().UTC() } -func (g *gatewayImpl) identify() { +func (g *gatewayImpl) identify() error { g.status = StatusIdentifying g.config.Logger.Debug("sending Identify command") @@ -297,12 +322,13 @@ func (g *gatewayImpl) identify() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := g.Send(ctx, OpcodeIdentify, identify); err != nil { - g.config.Logger.Error("error sending Identify command", slog.Any("err", err)) + return err } g.status = StatusWaitingForReady + return nil } -func (g *gatewayImpl) resume() { +func (g *gatewayImpl) resume() error { g.status = StatusResuming resume := MessageDataResume{ Token: g.token, @@ -314,16 +340,22 @@ func (g *gatewayImpl) resume() { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := g.Send(ctx, OpcodeResume, resume); err != nil { - g.config.Logger.Error("error sending resume command", slog.Any("err", err)) + return err } + return nil } -func (g *gatewayImpl) listen(conn *websocket.Conn) { +func (g *gatewayImpl) listen(conn *websocket.Conn, readyChan chan<- error) { defer g.config.Logger.Debug("exiting listen goroutine") loop: for { mt, r, err := conn.NextReader() if err != nil { + if g.status != StatusReady { + readyChan <- err + close(readyChan) + break loop + } g.connMu.Lock() sameConnection := g.conn == conn g.connMu.Unlock() @@ -388,9 +420,14 @@ loop: go g.heartbeat() if g.config.LastSequenceReceived == nil || g.config.SessionID == nil { - g.identify() + err = g.identify() } else { - g.resume() + err = g.resume() + } + if err != nil { + readyChan <- err + close(readyChan) + return } case OpcodeDispatch: @@ -403,12 +440,18 @@ loop: continue } - // get session id here if readyEvent, ok := eventData.(EventReady); ok { g.config.SessionID = &readyEvent.SessionID g.config.ResumeURL = &readyEvent.ResumeGatewayURL - g.status = StatusReady g.config.Logger.Debug("ready message received") + g.status = StatusReady + readyChan <- nil + close(readyChan) + } else if _, ok = eventData.(EventResumed); ok { + g.config.Logger.Debug("resume message received") + g.status = StatusReady + readyChan <- nil + close(readyChan) } // push message to the command manager diff --git a/gateway/gateway_intents.go b/gateway/gateway_intents.go index 2945dd691..83ad9990b 100644 --- a/gateway/gateway_intents.go +++ b/gateway/gateway_intents.go @@ -10,8 +10,7 @@ const ( IntentGuilds Intents = 1 << iota IntentGuildMembers IntentGuildModeration - // Deprecated: Use IntentGuildExpressions instead - IntentGuildEmojisAndStickers + IntentGuildExpressions IntentGuildIntegrations IntentGuildWebhooks IntentGuildInvites @@ -35,8 +34,6 @@ const ( IntentGuildMessagePolls IntentDirectMessagePolls - IntentGuildExpressions = IntentGuildEmojisAndStickers - IntentsGuild = IntentGuilds | IntentGuildMembers | IntentGuildModeration | diff --git a/gateway/gateway_messages.go b/gateway/gateway_messages.go index 4904cc50c..539f31c3c 100644 --- a/gateway/gateway_messages.go +++ b/gateway/gateway_messages.go @@ -4,7 +4,7 @@ import ( "fmt" "strconv" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" @@ -120,7 +120,7 @@ func UnmarshalEventData(data []byte, eventType EventType) (EventData, error) { eventData = d case EventTypeResumed: - // no data + eventData = EventResumed{} case EventTypeApplicationCommandPermissionsUpdate: var d EventApplicationCommandPermissionsUpdate @@ -499,7 +499,7 @@ type MessageDataHeartbeat int func (m MessageDataHeartbeat) MarshalJSON() ([]byte, error) { if m == 0 { - return json.NullBytes, nil + return []byte("null"), nil } return []byte(strconv.Itoa(int(m))), nil } diff --git a/go.mod b/go.mod index 9f5df6e8b..826f5e814 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,20 @@ module github.com/disgoorg/disgo -go 1.21 +go 1.24 require ( - github.com/disgoorg/json v1.2.0 + github.com/disgoorg/json/v2 v2.0.0 + github.com/disgoorg/omit v1.0.0 github.com/disgoorg/snowflake/v2 v2.0.3 github.com/gorilla/websocket v1.5.3 github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad github.com/stretchr/testify v1.10.0 - golang.org/x/crypto v0.31.0 + golang.org/x/crypto v0.37.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a3c3cc2d1..b2f512de5 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/disgoorg/json v1.2.0 h1:6e/j4BCfSHIvucG1cd7tJPAOp1RgnnMFSqkvZUtEd1Y= -github.com/disgoorg/json v1.2.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= +github.com/disgoorg/json/v2 v2.0.0 h1:U16yy/ARK7/aEpzjjqK1b/KaqqGHozUdeVw/DViEzQI= +github.com/disgoorg/json/v2 v2.0.0/go.mod h1:jZTBC0nIE1WeetSEI3/Dka8g+qglb4FPVmp5I5HpEfI= +github.com/disgoorg/omit v1.0.0 h1:y0LkVUOyUHT8ZlnhIAeOZEA22UYykeysK8bLJ0SfT78= +github.com/disgoorg/omit v1.0.0/go.mod h1:RTmSARkf6PWT/UckwI0bV8XgWkWQoPppaT01rYKLcFQ= github.com/disgoorg/snowflake/v2 v2.0.3 h1:3B+PpFjr7j4ad7oeJu4RlQ+nYOTadsKapJIzgvSI2Ro= github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -12,10 +14,10 @@ github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxE github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/handler/interaction.go b/handler/interaction.go index 64901c096..46222217b 100644 --- a/handler/interaction.go +++ b/handler/interaction.go @@ -41,12 +41,6 @@ func (e *InteractionEvent) DeferUpdateMessage(opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeDeferredUpdateMessage, nil, opts...) } -// Deprecated: Respond with a discord.ButtonStylePremium button instead. -// PremiumRequired responds to the interaction with an upgrade button if available. -func (e *InteractionEvent) PremiumRequired(opts ...rest.RequestOpt) error { - return e.Respond(discord.InteractionResponseTypePremiumRequired, nil, opts...) -} - // LaunchActivity responds to the interaction by launching activity associated with the app. func (e *InteractionEvent) LaunchActivity(opts ...rest.RequestOpt) error { return e.Respond(discord.InteractionResponseTypeLaunchActivity, nil, opts...) diff --git a/handlers/guild_emojis_update_handler.go b/handlers/guild_emojis_update_handler.go index b1b608335..6d42fbbcc 100644 --- a/handlers/guild_emojis_update_handler.go +++ b/handlers/guild_emojis_update_handler.go @@ -3,12 +3,13 @@ package handlers import ( "slices" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/cache" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/gateway" - "github.com/disgoorg/snowflake/v2" ) type updatedEmoji struct { @@ -30,9 +31,9 @@ func gatewayHandlerGuildEmojisUpdate(client bot.Client, sequenceNumber int, shar deletedEmojis := map[snowflake.ID]discord.Emoji{} updatedEmojis := map[snowflake.ID]updatedEmoji{} - client.Caches().EmojisForEach(event.GuildID, func(emoji discord.Emoji) { + for emoji := range client.Caches().Emojis(event.GuildID) { deletedEmojis[emoji.ID] = emoji - }) + } for _, newEmoji := range event.Emojis { oldEmoji, ok := deletedEmojis[newEmoji.ID] diff --git a/handlers/guild_handlers.go b/handlers/guild_handlers.go index 84013c376..253351e39 100644 --- a/handlers/guild_handlers.go +++ b/handlers/guild_handlers.go @@ -1,6 +1,7 @@ package handlers import ( + "context" "log/slog" "github.com/disgoorg/disgo/bot" @@ -85,7 +86,7 @@ func gatewayHandlerGuildCreate(client bot.Client, sequenceNumber int, shardID in } if client.MemberChunkingManager().MemberChunkingFilter()(event.ID) { go func() { - if _, err := client.MemberChunkingManager().RequestMembersWithQuery(event.ID, "", 0); err != nil { + if _, err := client.MemberChunkingManager().RequestMembersWithQuery(context.Background(), event.ID, "", 0); err != nil { client.Logger().Error("failed to chunk guild on guild_create", slog.Any("err", err)) } }() @@ -130,11 +131,11 @@ func gatewayHandlerGuildDelete(client bot.Client, sequenceNumber int, shardID in client.Caches().RemoveVoiceStatesByGuildID(event.ID) client.Caches().RemovePresencesByGuildID(event.ID) // TODO: figure out a better way to remove thread members from cache via guild id without requiring cached GuildThreads - client.Caches().ChannelsForEach(func(channel discord.GuildChannel) { + for channel := range client.Caches().Channels() { if guildThread, ok := channel.(discord.GuildThread); ok && guildThread.GuildID() == event.ID { client.Caches().RemoveThreadMembersByThreadID(guildThread.ID()) } - }) + } client.Caches().RemoveChannelsByGuildID(event.ID) client.Caches().RemoveEmojisByGuildID(event.ID) client.Caches().RemoveStickersByGuildID(event.ID) diff --git a/handlers/guild_stickers_update_handler.go b/handlers/guild_stickers_update_handler.go index 9abeffd13..9b229bdd2 100644 --- a/handlers/guild_stickers_update_handler.go +++ b/handlers/guild_stickers_update_handler.go @@ -29,9 +29,9 @@ func gatewayHandlerGuildStickersUpdate(client bot.Client, sequenceNumber int, sh deletedStickers := map[snowflake.ID]discord.Sticker{} updatedStickers := map[snowflake.ID]updatedSticker{} - client.Caches().StickersForEach(event.GuildID, func(sticker discord.Sticker) { + for sticker := range client.Caches().Stickers(event.GuildID) { deletedStickers[sticker.ID] = sticker - }) + } for _, newSticker := range event.Stickers { oldSticker, ok := deletedStickers[newSticker.ID] diff --git a/handlers/presence_update_handler.go b/handlers/presence_update_handler.go index 997082596..a9fdfdf95 100644 --- a/handlers/presence_update_handler.go +++ b/handlers/presence_update_handler.go @@ -3,12 +3,13 @@ package handlers import ( "slices" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/cache" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" "github.com/disgoorg/disgo/gateway" - "github.com/disgoorg/snowflake/v2" ) func gatewayHandlerPresenceUpdate(client bot.Client, sequenceNumber int, shardID int, event gateway.EventPresenceUpdate) { diff --git a/httpserver/server.go b/httpserver/server.go index af38d3cc0..424740938 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/disgo/discord" ) diff --git a/internal/insecurerandstr/random_str.go b/internal/insecurerandstr/random_str.go index 3f5698a27..a7206dffa 100644 --- a/internal/insecurerandstr/random_str.go +++ b/internal/insecurerandstr/random_str.go @@ -2,21 +2,16 @@ package insecurerandstr import ( - "math/rand" - "time" + "math/rand/v2" ) -var ( - letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - - randStr = rand.New(rand.NewSource(time.Now().UnixNano())) -) +var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") // RandStr returns a random string of the given length. func RandStr(n int) string { - b := make([]rune, n) - for i := range b { - b[i] = letters[randStr.Intn(len(letters))] + b := make([]byte, n) + for i := range n { + b[i] = letters[rand.IntN(len(letters))] } return string(b) } diff --git a/rest/applications.go b/rest/applications.go index 773e3b600..c7a56520d 100644 --- a/rest/applications.go +++ b/rest/applications.go @@ -1,10 +1,10 @@ package rest import ( - "github.com/disgoorg/disgo/internal/slicehelper" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/internal/slicehelper" ) var _ Applications = (*applicationsImpl)(nil) diff --git a/rest/guilds.go b/rest/guilds.go index e4180086b..56a8e4be0 100644 --- a/rest/guilds.go +++ b/rest/guilds.go @@ -3,10 +3,10 @@ package rest import ( "time" - "github.com/disgoorg/disgo/internal/slicehelper" "github.com/disgoorg/snowflake/v2" "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/internal/slicehelper" ) var _ Guilds = (*guildImpl)(nil) diff --git a/rest/rest_client.go b/rest/rest_client.go index 2e7585cae..047fa8c17 100644 --- a/rest/rest_client.go +++ b/rest/rest_client.go @@ -10,7 +10,7 @@ import ( "net/url" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/disgo/discord" ) diff --git a/rest/rest_error.go b/rest/rest_error.go index 773712a6d..e2d701a93 100644 --- a/rest/rest_error.go +++ b/rest/rest_error.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" ) // JSONErrorCode is the error code returned by the Discord API. diff --git a/rest/skus.go b/rest/skus.go index f81a38c14..7b30e2a49 100644 --- a/rest/skus.go +++ b/rest/skus.go @@ -1,8 +1,9 @@ package rest import ( - "github.com/disgoorg/disgo/discord" "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo/discord" ) var _ SKUs = (*skusImpl)(nil) diff --git a/rest/soundboard_sounds.go b/rest/soundboard_sounds.go index 7886e6b68..8818f6e15 100644 --- a/rest/soundboard_sounds.go +++ b/rest/soundboard_sounds.go @@ -1,8 +1,9 @@ package rest import ( - "github.com/disgoorg/disgo/discord" "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo/discord" ) var _ SoundboardSounds = (*soundsImpl)(nil) diff --git a/sharding/shard_manager.go b/sharding/shard_manager.go index 236303751..a2664e254 100644 --- a/sharding/shard_manager.go +++ b/sharding/shard_manager.go @@ -2,20 +2,27 @@ package sharding import ( "context" + "errors" + "fmt" + "iter" + "log/slog" + "sync" "github.com/disgoorg/snowflake/v2" + "github.com/gorilla/websocket" "github.com/disgoorg/disgo/gateway" ) -// ShardSplitCount is the default count a shard should be split into when it needs re-sharding. -const ShardSplitCount = 2 +// DefaultShardSplitCount is the default count a shard should be split into when it needs re-sharding. +const DefaultShardSplitCount = 2 // ShardManager manages multiple gateway.Gateway connections. // For more information on sharding see: https://discord.com/developers/docs/topics/gateway#sharding type ShardManager interface { // Open opens all configured shards. Open(ctx context.Context) + // Close closes all shards. Close(ctx context.Context) @@ -31,11 +38,193 @@ type ShardManager interface { // Shard returns the gateway.Gateway for the given shard ID. Shard(shardID int) gateway.Gateway - // Shards returns a copy of all shards as a map. - Shards() map[int]gateway.Gateway + // Shards returns all shards. This function is thread-safe. + Shards() iter.Seq[gateway.Gateway] } // ShardIDByGuild returns the shard ID for the given guildID and shardCount. func ShardIDByGuild(guildID snowflake.ID, shardCount int) int { return int((uint64(guildID) >> 22) % uint64(shardCount)) } + +var _ ShardManager = (*shardManagerImpl)(nil) + +// New creates a new default ShardManager with the given token, eventHandlerFunc and ConfigOpt(s). +func New(token string, eventHandlerFunc gateway.EventHandlerFunc, opts ...ConfigOpt) ShardManager { + config := DefaultConfig() + config.Apply(opts) + config.Logger = config.Logger.With(slog.String("name", "sharding")) + + return &shardManagerImpl{ + shards: map[int]gateway.Gateway{}, + token: token, + eventHandlerFunc: eventHandlerFunc, + config: *config, + } +} + +type shardManagerImpl struct { + shards map[int]gateway.Gateway + shardsMu sync.Mutex + + token string + eventHandlerFunc gateway.EventHandlerFunc + config Config +} + +func (m *shardManagerImpl) closeHandler(shard gateway.Gateway, err error) { + var closeError *websocket.CloseError + if !m.config.AutoScaling || !errors.As(err, &closeError) || gateway.CloseEventCodeByCode(closeError.Code) != gateway.CloseEventCodeShardingRequired { + return + } + m.config.Logger.Debug("shard requires re-sharding", slog.Int("shardID", shard.ShardID())) + // make sure shard is closed + shard.Close(context.TODO()) + + m.shardsMu.Lock() + defer m.shardsMu.Unlock() + + delete(m.shards, shard.ShardID()) + delete(m.config.ShardIDs, shard.ShardID()) + + newShardCount := shard.ShardCount() * m.config.ShardSplitCount + if newShardCount > m.config.ShardCount { + m.config.ShardCount = newShardCount + } + + newShardID := shard.ShardID() + var newShardIDs []int + for len(newShardIDs) < m.config.ShardSplitCount { + newShardIDs = append(newShardIDs, newShardID) + newShardID += m.config.ShardSplitCount + } + + var wg sync.WaitGroup + for i := range newShardIDs { + shardID := newShardIDs[i] + wg.Add(1) + go func() { + defer wg.Done() + if err := m.config.RateLimiter.WaitBucket(context.TODO(), shardID); err != nil { + m.config.Logger.Error("failed to wait shard bucket", slog.Any("err", err), slog.Int("shard_id", shardID)) + return + } + defer m.config.RateLimiter.UnlockBucket(shardID) + + newShard := m.config.GatewayCreateFunc(m.token, m.eventHandlerFunc, m.closeHandler, append(m.config.GatewayConfigOpts, gateway.WithShardID(shardID), gateway.WithShardCount(newShardCount))...) + m.shards[shardID] = newShard + if err := newShard.Open(context.TODO()); err != nil { + m.config.Logger.Error("failed to re shard", slog.Any("err", err), slog.Int("shard_id", shardID)) + } + }() + } + wg.Wait() + m.config.Logger.Debug("re-sharded shard", slog.Int("shard_id", shard.ShardID()), slog.String("new_shard_ids", fmt.Sprint(newShardIDs)), slog.Int("new_shard_count", newShardCount)) +} + +func (m *shardManagerImpl) Open(ctx context.Context) { + m.config.Logger.Debug("opening shards", slog.String("shard_ids", fmt.Sprint(m.config.ShardIDs))) + var wg sync.WaitGroup + + m.shardsMu.Lock() + defer m.shardsMu.Unlock() + for shardID := range m.config.ShardIDs { + if _, ok := m.shards[shardID]; ok { + continue + } + + wg.Add(1) + go func() { + defer wg.Done() + if err := m.config.RateLimiter.WaitBucket(ctx, shardID); err != nil { + m.config.Logger.Error("failed to wait shard bucket", slog.Any("err", err), slog.Int("shard_id", shardID)) + return + } + defer m.config.RateLimiter.UnlockBucket(shardID) + + shard := m.config.GatewayCreateFunc(m.token, m.eventHandlerFunc, m.closeHandler, append(m.config.GatewayConfigOpts, gateway.WithShardID(shardID), gateway.WithShardCount(m.config.ShardCount))...) + m.shards[shardID] = shard + if err := shard.Open(ctx); err != nil { + m.config.Logger.Error("failed to open shard", slog.Any("err", err), slog.Int("shard_id", shardID)) + } + }() + } + wg.Wait() +} + +func (m *shardManagerImpl) Close(ctx context.Context) { + m.config.Logger.Debug("closing shards", slog.String("shard_ids", fmt.Sprint(m.config.ShardIDs))) + var wg sync.WaitGroup + + m.shardsMu.Lock() + defer m.shardsMu.Unlock() + for shardID := range m.shards { + shard := m.shards[shardID] + delete(m.shards, shardID) + wg.Add(1) + go func() { + defer wg.Done() + shard.Close(ctx) + }() + } + wg.Wait() +} + +func (m *shardManagerImpl) OpenShard(ctx context.Context, shardID int) error { + return m.openShard(ctx, shardID, m.config.ShardCount) +} + +func (m *shardManagerImpl) openShard(ctx context.Context, shardID int, shardCount int) error { + m.config.Logger.Debug("opening shard", slog.Int("shard_id", shardID)) + + if err := m.config.RateLimiter.WaitBucket(ctx, shardID); err != nil { + return err + } + defer m.config.RateLimiter.UnlockBucket(shardID) + shard := m.config.GatewayCreateFunc(m.token, m.eventHandlerFunc, m.closeHandler, append(m.config.GatewayConfigOpts, gateway.WithShardID(shardID), gateway.WithShardCount(shardCount))...) + + m.shardsMu.Lock() + defer m.shardsMu.Unlock() + m.config.ShardIDs[shardID] = struct{}{} + m.shards[shardID] = shard + return shard.Open(ctx) +} + +func (m *shardManagerImpl) CloseShard(ctx context.Context, shardID int) { + m.config.Logger.Debug("closing shard", slog.Int("shard_id", shardID)) + m.shardsMu.Lock() + defer m.shardsMu.Unlock() + shard, ok := m.shards[shardID] + if ok { + shard.Close(ctx) + delete(m.shards, shardID) + } +} + +func (m *shardManagerImpl) ShardByGuildID(guildId snowflake.ID) gateway.Gateway { + shardCount := m.config.ShardCount + var shard gateway.Gateway + for shard == nil || shardCount != 0 { + shard = m.Shard(ShardIDByGuild(guildId, shardCount)) + shardCount /= m.config.ShardSplitCount + } + return shard +} + +func (m *shardManagerImpl) Shard(shardID int) gateway.Gateway { + m.shardsMu.Lock() + defer m.shardsMu.Unlock() + return m.shards[shardID] +} + +func (m *shardManagerImpl) Shards() iter.Seq[gateway.Gateway] { + return func(yield func(gateway.Gateway) bool) { + m.shardsMu.Lock() + defer m.shardsMu.Unlock() + for _, shard := range m.shards { + if !yield(shard) { + return + } + } + } +} diff --git a/sharding/shard_manager_config.go b/sharding/shard_manager_config.go index a78dc3605..e024f8441 100644 --- a/sharding/shard_manager_config.go +++ b/sharding/shard_manager_config.go @@ -11,7 +11,7 @@ func DefaultConfig() *Config { return &Config{ Logger: slog.Default(), GatewayCreateFunc: gateway.New, - ShardSplitCount: ShardSplitCount, + ShardSplitCount: DefaultShardSplitCount, } } diff --git a/sharding/shard_manager_impl.go b/sharding/shard_manager_impl.go deleted file mode 100644 index f3002c44b..000000000 --- a/sharding/shard_manager_impl.go +++ /dev/null @@ -1,195 +0,0 @@ -package sharding - -import ( - "context" - "errors" - "fmt" - "log/slog" - "sync" - - "github.com/disgoorg/snowflake/v2" - "github.com/gorilla/websocket" - - "github.com/disgoorg/disgo/gateway" -) - -var _ ShardManager = (*shardManagerImpl)(nil) - -// New creates a new default ShardManager with the given token, eventHandlerFunc and ConfigOpt(s). -func New(token string, eventHandlerFunc gateway.EventHandlerFunc, opts ...ConfigOpt) ShardManager { - config := DefaultConfig() - config.Apply(opts) - config.Logger = config.Logger.With(slog.String("name", "sharding")) - - return &shardManagerImpl{ - shards: map[int]gateway.Gateway{}, - token: token, - eventHandlerFunc: eventHandlerFunc, - config: *config, - } -} - -type shardManagerImpl struct { - shards map[int]gateway.Gateway - shardsMu sync.Mutex - - token string - eventHandlerFunc gateway.EventHandlerFunc - config Config -} - -func (m *shardManagerImpl) closeHandler(shard gateway.Gateway, err error) { - var closeError *websocket.CloseError - if !m.config.AutoScaling || !errors.As(err, &closeError) || gateway.CloseEventCodeByCode(closeError.Code) != gateway.CloseEventCodeShardingRequired { - return - } - m.config.Logger.Debug("shard requires re-sharding", slog.Int("shardID", shard.ShardID())) - // make sure shard is closed - shard.Close(context.TODO()) - - m.shardsMu.Lock() - defer m.shardsMu.Unlock() - - delete(m.shards, shard.ShardID()) - delete(m.config.ShardIDs, shard.ShardID()) - - newShardCount := shard.ShardCount() * m.config.ShardSplitCount - if newShardCount > m.config.ShardCount { - m.config.ShardCount = newShardCount - } - - newShardID := shard.ShardID() - var newShardIDs []int - for len(newShardIDs) < m.config.ShardSplitCount { - newShardIDs = append(newShardIDs, newShardID) - newShardID += m.config.ShardSplitCount - } - - var wg sync.WaitGroup - for i := range newShardIDs { - shardID := newShardIDs[i] - wg.Add(1) - go func() { - defer wg.Done() - if err := m.config.RateLimiter.WaitBucket(context.TODO(), shardID); err != nil { - m.config.Logger.Error("failed to wait shard bucket", slog.Any("err", err), slog.Int("shard_id", shardID)) - return - } - defer m.config.RateLimiter.UnlockBucket(shardID) - - newShard := m.config.GatewayCreateFunc(m.token, m.eventHandlerFunc, m.closeHandler, append(m.config.GatewayConfigOpts, gateway.WithShardID(shardID), gateway.WithShardCount(newShardCount))...) - m.shards[shardID] = newShard - if err := newShard.Open(context.TODO()); err != nil { - m.config.Logger.Error("failed to re shard", slog.Any("err", err), slog.Int("shard_id", shardID)) - } - }() - } - wg.Wait() - m.config.Logger.Debug("re-sharded shard", slog.Int("shard_id", shard.ShardID()), slog.String("new_shard_ids", fmt.Sprint(newShardIDs)), slog.Int("new_shard_count", newShardCount)) -} - -func (m *shardManagerImpl) Open(ctx context.Context) { - m.config.Logger.Debug("opening shards", slog.String("shard_ids", fmt.Sprint(m.config.ShardIDs))) - var wg sync.WaitGroup - - m.shardsMu.Lock() - defer m.shardsMu.Unlock() - for shardInt := range m.config.ShardIDs { - shardID := shardInt - if _, ok := m.shards[shardID]; ok { - continue - } - - wg.Add(1) - go func() { - defer wg.Done() - if err := m.config.RateLimiter.WaitBucket(ctx, shardID); err != nil { - m.config.Logger.Error("failed to wait shard bucket", slog.Any("err", err), slog.Int("shard_id", shardID)) - return - } - defer m.config.RateLimiter.UnlockBucket(shardID) - - shard := m.config.GatewayCreateFunc(m.token, m.eventHandlerFunc, m.closeHandler, append(m.config.GatewayConfigOpts, gateway.WithShardID(shardID), gateway.WithShardCount(m.config.ShardCount))...) - m.shards[shardID] = shard - if err := shard.Open(ctx); err != nil { - m.config.Logger.Error("failed to open shard", slog.Any("err", err), slog.Int("shard_id", shardID)) - } - }() - } - wg.Wait() -} - -func (m *shardManagerImpl) Close(ctx context.Context) { - m.config.Logger.Debug("closing shards", slog.String("shard_ids", fmt.Sprint(m.config.ShardIDs))) - var wg sync.WaitGroup - - m.shardsMu.Lock() - defer m.shardsMu.Unlock() - for shardID := range m.shards { - shard := m.shards[shardID] - delete(m.shards, shardID) - wg.Add(1) - go func() { - defer wg.Done() - shard.Close(ctx) - }() - } - wg.Wait() -} - -func (m *shardManagerImpl) OpenShard(ctx context.Context, shardID int) error { - return m.openShard(ctx, shardID, m.config.ShardCount) -} - -func (m *shardManagerImpl) openShard(ctx context.Context, shardID int, shardCount int) error { - m.config.Logger.Debug("opening shard", slog.Int("shard_id", shardID)) - - if err := m.config.RateLimiter.WaitBucket(ctx, shardID); err != nil { - return err - } - defer m.config.RateLimiter.UnlockBucket(shardID) - shard := m.config.GatewayCreateFunc(m.token, m.eventHandlerFunc, m.closeHandler, append(m.config.GatewayConfigOpts, gateway.WithShardID(shardID), gateway.WithShardCount(shardCount))...) - - m.shardsMu.Lock() - defer m.shardsMu.Unlock() - m.config.ShardIDs[shardID] = struct{}{} - m.shards[shardID] = shard - return shard.Open(ctx) -} - -func (m *shardManagerImpl) CloseShard(ctx context.Context, shardID int) { - m.config.Logger.Debug("closing shard", slog.Int("shard_id", shardID)) - m.shardsMu.Lock() - defer m.shardsMu.Unlock() - shard, ok := m.shards[shardID] - if ok { - shard.Close(ctx) - delete(m.shards, shardID) - } -} - -func (m *shardManagerImpl) ShardByGuildID(guildId snowflake.ID) gateway.Gateway { - shardCount := m.config.ShardCount - var shard gateway.Gateway - for shard == nil || shardCount != 0 { - shard = m.Shard(ShardIDByGuild(guildId, shardCount)) - shardCount /= m.config.ShardSplitCount - } - return shard -} - -func (m *shardManagerImpl) Shard(shardID int) gateway.Gateway { - m.shardsMu.Lock() - defer m.shardsMu.Unlock() - return m.shards[shardID] -} - -func (m *shardManagerImpl) Shards() map[int]gateway.Gateway { - m.shardsMu.Lock() - defer m.shardsMu.Unlock() - shards := make(map[int]gateway.Gateway, len(m.shards)) - for shardID, shard := range m.shards { - shards[shardID] = shard - } - return m.shards -} diff --git a/sharding/shard_rate_limiter_impl_test.go b/sharding/shard_rate_limiter_impl_test.go index b816b379f..d74b6594e 100644 --- a/sharding/shard_rate_limiter_impl_test.go +++ b/sharding/shard_rate_limiter_impl_test.go @@ -17,8 +17,7 @@ func TestShardRateLimiterImpl(t *testing.T) { start := time.Now() var wg sync.WaitGroup - for i := 0; i < 3; i++ { - shardID := i + for shardID := range 3 { wg.Add(1) go func() { defer wg.Done() @@ -41,8 +40,7 @@ func TestShardRateLimiterImpl_WithMaxConcurrency(t *testing.T) { start := time.Now() var wg sync.WaitGroup - for i := 0; i < 6; i++ { - shardID := i + for shardID := range 6 { wg.Add(1) go func() { defer wg.Done() diff --git a/voice/gateway.go b/voice/gateway.go index ecd879717..28e29db05 100644 --- a/voice/gateway.go +++ b/voice/gateway.go @@ -11,7 +11,7 @@ import ( "syscall" "time" - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" "github.com/gorilla/websocket" @@ -142,7 +142,9 @@ func (g *gatewayImpl) Open(ctx context.Context, state State) error { conn, rs, err := g.config.Dialer.DialContext(ctx, gatewayURL, nil) if err != nil { g.Close() - defer rs.Body.Close() + if rs != nil { + defer rs.Body.Close() + } return fmt.Errorf("error connecting to voice gateway: %w", err) } diff --git a/voice/gateway_messages.go b/voice/gateway_messages.go index 1a80c75f0..1b92a5edf 100644 --- a/voice/gateway_messages.go +++ b/voice/gateway_messages.go @@ -1,7 +1,7 @@ package voice import ( - "github.com/disgoorg/json" + "github.com/disgoorg/json/v2" "github.com/disgoorg/snowflake/v2" ) diff --git a/voice/manager.go b/voice/manager.go index 2f6ab66c1..c060f7859 100644 --- a/voice/manager.go +++ b/voice/manager.go @@ -2,6 +2,7 @@ package voice import ( "context" + "iter" "log/slog" "sync" @@ -28,8 +29,8 @@ type ( // GetConn returns the voice connection for the given guild. GetConn(guildID snowflake.ID) Conn - // ForEachCon runs the given function for each voice connection. This is thread-safe. - ForEachCon(f func(connection Conn)) + // Conns returns all voice connections. This function is thread-safe. + Conns() iter.Seq[Conn] // RemoveConn removes the voice connection for the given guild. RemoveConn(guildID snowflake.ID) @@ -106,11 +107,15 @@ func (m *managerImpl) GetConn(guildID snowflake.ID) Conn { return m.conns[guildID] } -func (m *managerImpl) ForEachCon(f func(connection Conn)) { - m.connsMu.Lock() - defer m.connsMu.Unlock() - for _, connection := range m.conns { - f(connection) +func (m *managerImpl) Conns() iter.Seq[Conn] { + return func(yield func(Conn) bool) { + m.connsMu.Lock() + defer m.connsMu.Unlock() + for _, connection := range m.conns { + if !yield(connection) { + return + } + } } } diff --git a/webhook/README.md b/webhook/README.md index 78deb61b0..d7b88b9be 100644 --- a/webhook/README.md +++ b/webhook/README.md @@ -48,11 +48,12 @@ message, err := client.CreateEmbeds(discord.NewEmbedBuilder(). message, err := client.CreateMessage(webhook.NewWebhookMessageCreateBuilder(). SetContent("hello world!"). Build(), + rest.CreateWebhookMessageParams{}, ) message, err := client.CreateMessage(discord.WebhookMessageCreate{ Content: "hello world!", -}) +}, rest.CreateWebhookMessageParams{}) ``` ### Edit Message @@ -72,11 +73,12 @@ message, err := client.UpdateEmbeds("870741249114652722", discord.NewEmbedBuilde message, err := client.UpdateMessage("870741249114652722", discord.NewWebhookMessageUpdateBuilder(). SetContent("hello world!"). Build(), + rest.UpdateWebhookMessageParams{}, ) message, err := client.UpdateMessage("870741249114652722", discord.WebhookMessageUpdate{ Content: json.Ptr("hello world!"), -}) +}, rest.UpdateWebhookMessageParams{}) ``` ### Delete Message diff --git a/webhook/webhook_client.go b/webhook/webhook_client.go index 1d19b5027..5b8369dc4 100644 --- a/webhook/webhook_client.go +++ b/webhook/webhook_client.go @@ -36,7 +36,7 @@ type Client interface { GetMessage(messageID snowflake.ID, opts ...rest.RequestOpt) (*discord.Message, error) // CreateMessage creates a new Message from the discord.WebhookMessageCreate - CreateMessage(messageCreate discord.WebhookMessageCreate, opts ...rest.RequestOpt) (*discord.Message, error) + CreateMessage(messageCreate discord.WebhookMessageCreate, params rest.CreateWebhookMessageParams, opts ...rest.RequestOpt) (*discord.Message, error) // CreateMessageInThread creates a new Message from the discord.WebhookMessageCreate in the provided thread CreateMessageInThread(messageCreate discord.WebhookMessageCreate, threadID snowflake.ID, opts ...rest.RequestOpt) (*discord.Message, error) // CreateContent creates a new Message from the provided content @@ -45,7 +45,7 @@ type Client interface { CreateEmbeds(embeds []discord.Embed, opts ...rest.RequestOpt) (*discord.Message, error) // UpdateMessage updates an already sent Webhook Message with the discord.WebhookMessageUpdate - UpdateMessage(messageID snowflake.ID, messageUpdate discord.WebhookMessageUpdate, opts ...rest.RequestOpt) (*discord.Message, error) + UpdateMessage(messageID snowflake.ID, messageUpdate discord.WebhookMessageUpdate, params rest.UpdateWebhookMessageParams, opts ...rest.RequestOpt) (*discord.Message, error) // UpdateMessageInThread updates an already sent Webhook Message with the discord.WebhookMessageUpdate in the provided thread UpdateMessageInThread(messageID snowflake.ID, messageUpdate discord.WebhookMessageUpdate, threadID snowflake.ID, opts ...rest.RequestOpt) (*discord.Message, error) // UpdateContent updates an already sent Webhook Message with the content diff --git a/webhook/webhook_client_impl.go b/webhook/webhook_client_impl.go index 266c08bb9..5fad290fd 100644 --- a/webhook/webhook_client_impl.go +++ b/webhook/webhook_client_impl.go @@ -96,36 +96,36 @@ func (c *clientImpl) GetMessage(messageID snowflake.ID, opts ...rest.RequestOpt) return c.Rest().GetWebhookMessage(c.id, c.token, messageID, opts...) } -func (c *clientImpl) CreateMessageInThread(messageCreate discord.WebhookMessageCreate, threadID snowflake.ID, opts ...rest.RequestOpt) (*discord.Message, error) { - return c.Rest().CreateWebhookMessage(c.id, c.token, messageCreate, rest.CreateWebhookMessageParams{Wait: true, ThreadID: threadID}, opts...) +func (c *clientImpl) CreateMessage(messageCreate discord.WebhookMessageCreate, params rest.CreateWebhookMessageParams, opts ...rest.RequestOpt) (*discord.Message, error) { + return c.Rest().CreateWebhookMessage(c.id, c.token, messageCreate, params, opts...) } -func (c *clientImpl) CreateMessage(messageCreate discord.WebhookMessageCreate, opts ...rest.RequestOpt) (*discord.Message, error) { - return c.CreateMessageInThread(messageCreate, 0, opts...) +func (c *clientImpl) CreateMessageInThread(messageCreate discord.WebhookMessageCreate, threadID snowflake.ID, opts ...rest.RequestOpt) (*discord.Message, error) { + return c.CreateMessage(messageCreate, rest.CreateWebhookMessageParams{Wait: true, ThreadID: threadID}, opts...) } func (c *clientImpl) CreateContent(content string, opts ...rest.RequestOpt) (*discord.Message, error) { - return c.CreateMessage(discord.WebhookMessageCreate{Content: content}, opts...) + return c.CreateMessage(discord.WebhookMessageCreate{Content: content}, rest.CreateWebhookMessageParams{}, opts...) } func (c *clientImpl) CreateEmbeds(embeds []discord.Embed, opts ...rest.RequestOpt) (*discord.Message, error) { - return c.CreateMessage(discord.WebhookMessageCreate{Embeds: embeds}, opts...) + return c.CreateMessage(discord.WebhookMessageCreate{Embeds: embeds}, rest.CreateWebhookMessageParams{}, opts...) } -func (c *clientImpl) UpdateMessage(messageID snowflake.ID, messageUpdate discord.WebhookMessageUpdate, opts ...rest.RequestOpt) (*discord.Message, error) { - return c.UpdateMessageInThread(messageID, messageUpdate, 0, opts...) +func (c *clientImpl) UpdateMessage(messageID snowflake.ID, messageUpdate discord.WebhookMessageUpdate, params rest.UpdateWebhookMessageParams, opts ...rest.RequestOpt) (*discord.Message, error) { + return c.Rest().UpdateWebhookMessage(c.id, c.token, messageID, messageUpdate, params, opts...) } func (c *clientImpl) UpdateMessageInThread(messageID snowflake.ID, messageUpdate discord.WebhookMessageUpdate, threadID snowflake.ID, opts ...rest.RequestOpt) (*discord.Message, error) { - return c.Rest().UpdateWebhookMessage(c.id, c.token, messageID, messageUpdate, rest.UpdateWebhookMessageParams{ThreadID: threadID}, opts...) + return c.UpdateMessage(messageID, messageUpdate, rest.UpdateWebhookMessageParams{ThreadID: threadID}, opts...) } func (c *clientImpl) UpdateContent(messageID snowflake.ID, content string, opts ...rest.RequestOpt) (*discord.Message, error) { - return c.UpdateMessage(messageID, discord.WebhookMessageUpdate{Content: &content}, opts...) + return c.UpdateMessage(messageID, discord.WebhookMessageUpdate{Content: &content}, rest.UpdateWebhookMessageParams{}, opts...) } func (c *clientImpl) UpdateEmbeds(messageID snowflake.ID, embeds []discord.Embed, opts ...rest.RequestOpt) (*discord.Message, error) { - return c.UpdateMessage(messageID, discord.WebhookMessageUpdate{Embeds: &embeds}, opts...) + return c.UpdateMessage(messageID, discord.WebhookMessageUpdate{Embeds: &embeds}, rest.UpdateWebhookMessageParams{}, opts...) } func (c *clientImpl) DeleteMessage(messageID snowflake.ID, opts ...rest.RequestOpt) error {