├── .gitignore
├── examples
├── airhorn
│ ├── airhorn.dca
│ ├── README.md
│ └── main.go
├── voice_receive
│ ├── go.mod
│ ├── README.md
│ ├── main.go
│ └── go.sum
├── linked_roles
│ ├── go.mod
│ ├── main.go
│ └── go.sum
├── README.md
├── threads
│ ├── README.md
│ └── main.go
├── stage_instance
│ ├── README.md
│ └── main.go
├── scheduled_events
│ ├── README.md
│ └── main.go
├── auto_moderation
│ ├── README.md
│ └── main.go
├── pingpong
│ ├── README.md
│ └── main.go
├── components
│ └── README.md
├── dm_pingpong
│ ├── README.md
│ └── main.go
├── context_menus
│ ├── README.md
│ └── main.go
├── autocomplete
│ ├── README.md
│ └── main.go
├── modals
│ ├── README.md
│ └── main.go
├── avatar
│ ├── README.md
│ └── main.go
└── slash_commands
│ └── README.md
├── go.mod
├── .github
├── release.yml
└── workflows
│ └── ci.yml
├── .golangci.yml
├── user_test.go
├── mkdocs.yml
├── .travis.yml
├── util_test.go
├── go.sum
├── message_test.go
├── LICENSE
├── oauth2_test.go
├── docs
├── index.md
├── GettingStarted.md
└── img
│ └── discordgo.svg
├── interactions_test.go
├── webhook.go
├── discord.go
├── locales.go
├── util.go
├── logging.go
├── ratelimit_test.go
├── tools
└── cmd
│ └── eventhandlers
│ └── main.go
├── CONTRIBUTING.md
├── user.go
├── README.md
├── oauth2.go
├── ratelimit.go
├── restapi_test.go
├── event.go
├── components.go
├── discord_test.go
├── events.go
└── endpoints.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE-specific metadata
2 | .idea/
3 |
4 | # Environment variables. Useful for examples.
5 | .env
6 |
--------------------------------------------------------------------------------
/examples/airhorn/airhorn.dca:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/botlabs-gg/discordgo/HEAD/examples/airhorn/airhorn.dca
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/bwmarrin/discordgo
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/gorilla/websocket v1.4.2
7 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
8 | )
9 |
--------------------------------------------------------------------------------
/examples/voice_receive/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/bwmarrin/discordgo/examples/voice_receive
2 |
3 | require (
4 | github.com/bwmarrin/discordgo v0.21.1
5 | github.com/pion/rtp v1.6.0
6 | github.com/pion/webrtc/v3 v3.0.0-20200721060053-ca3cc9d940bc
7 | )
8 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | categories:
3 | - title: "Breaking changes"
4 | labels:
5 | - breaking changes
6 |
7 | - title: "New features"
8 | labels:
9 | - feature
10 |
11 | - title: "Other changes"
12 | labels:
13 | - "*"
14 |
--------------------------------------------------------------------------------
/examples/linked_roles/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/bwmarrin/discordgo/examples/linked_roles
2 |
3 | go 1.13
4 |
5 | replace github.com/bwmarrin/discordgo v0.26.1 => ../../
6 |
7 | require (
8 | github.com/bwmarrin/discordgo v0.26.1
9 | github.com/joho/godotenv v1.4.0
10 | golang.org/x/oauth2 v0.3.0
11 | )
12 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | disable-all: true
3 | enable:
4 | # - staticcheck
5 | # - unused
6 | - golint
7 |
8 | linters-settings:
9 | staticcheck:
10 | go: "1.13"
11 |
12 | checks: ["all"]
13 |
14 | unused:
15 | go: "1.13"
16 |
17 | issues:
18 | include:
19 | - EXC0002
20 |
--------------------------------------------------------------------------------
/user_test.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import "testing"
4 |
5 | func TestUser(t *testing.T) {
6 | t.Parallel()
7 |
8 | user := &User{
9 | Username: "bob",
10 | Discriminator: "8192",
11 | }
12 |
13 | if user.String() != "bob#8192" {
14 | t.Errorf("user.String() == %v", user.String())
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # DiscordGo Examples
4 |
5 | These examples demonstrate how to utilize DiscordGo.
6 |
7 | Please explore the individual folders and give them a try!
8 |
9 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
10 | Discord chat channel for support.**
11 |
12 |
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: DiscordGo
2 | site_author: Bruce Marriner
3 | site_url: http://bwmarrin.github.io/discordgo/
4 | repo_url: https://github.com/bwmarrin/discordgo
5 |
6 | dev_addr: 0.0.0.0:8000
7 | theme: yeti
8 |
9 | markdown_extensions:
10 | - smarty
11 | - toc:
12 | permalink: True
13 | - sane_lists
14 |
15 | pages:
16 | - 'Home': 'index.md'
17 | - 'Getting Started': 'GettingStarted.md'
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - 1.13.x
4 | - 1.14.x
5 | - 1.15.x
6 | - 1.16.x
7 | - 1.17.x
8 | - 1.18.x
9 | env:
10 | - GO111MODULE=on
11 | install:
12 | - go get github.com/bwmarrin/discordgo
13 | - go get -v .
14 | - go get -v golang.org/x/lint/golint
15 | script:
16 | - diff <(gofmt -d .) <(echo -n)
17 | - go vet -x ./...
18 | - golint -set_exit_status ./...
19 | - go test -v -race ./...
20 |
--------------------------------------------------------------------------------
/util_test.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | func TestSnowflakeTimestamp(t *testing.T) {
9 | // #discordgo channel ID :)
10 | id := "155361364909621248"
11 | parsedTimestamp, err := SnowflakeTimestamp(id)
12 |
13 | if err != nil {
14 | t.Errorf("returned error incorrect: got %v, want nil", err)
15 | }
16 |
17 | correctTimestamp := time.Date(2016, time.March, 4, 17, 10, 35, 869*1000000, time.UTC)
18 | if !parsedTimestamp.Equal(correctTimestamp) {
19 | t.Errorf("parsed time incorrect: got %v, want %v", parsedTimestamp, correctTimestamp)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/threads/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Threads Example
4 |
5 | This example demonstrates how to utilize DiscordGo to manage channel threads.
6 |
7 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
8 | Discord chat channel for support.**
9 |
10 | ### Build
11 |
12 | This assumes you already have a working Go environment setup and that
13 | DiscordGo is correctly installed on your system.
14 |
15 | From within the threads example folder, run the below command to compile the
16 | example.
17 |
18 | ```sh
19 | go build
20 | ```
21 |
22 | ### Usage
23 |
24 | ```
25 | Usage of threads:
26 | -token string
27 | Bot token
28 | ```
29 |
30 | The below example shows how to start the bot from the threads example folder.
31 |
32 | ```sh
33 | ./threads -token YOUR_BOT_TOKEN
34 | ```
35 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
2 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
3 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
4 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
5 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
6 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
7 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
8 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
9 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
10 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
11 |
--------------------------------------------------------------------------------
/examples/stage_instance/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Stage Instance Example
4 |
5 | This example demonstrates how to utilize DiscordGo to manage stage instances.
6 |
7 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
8 | Discord chat channel for support.**
9 |
10 | ### Build
11 |
12 | This assumes you already have a working Go environment setup and that
13 | DiscordGo is correctly installed on your system.
14 |
15 | From within the stage_instance example folder, run the below command to compile the
16 | example.
17 |
18 | ```sh
19 | go build
20 | ```
21 |
22 | ### Usage
23 |
24 | ```
25 | Usage of stage_instance:
26 | -guild string
27 | Test guild ID
28 | -stage string
29 | Test stage channel ID
30 | -token string
31 | Bot token
32 | ```
33 |
34 | The below example shows how to start the bot from the stage_instance example folder.
35 |
36 | ```sh
37 | ./stage_instance -guild YOUR_TESTING_GUILD -stage STAGE_CHANNEL_ID -token YOUR_BOT_TOKEN
38 | ```
39 | ```
40 |
--------------------------------------------------------------------------------
/examples/scheduled_events/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Scheduled Events Example
4 |
5 | This example demonstrates how to utilize DiscordGo to manage scheduled events
6 | in a guild.
7 |
8 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
9 | Discord chat channel for support.**
10 |
11 | ### Build
12 |
13 | This assumes you already have a working Go environment setup and that
14 | DiscordGo is correctly installed on your system.
15 |
16 | From within the scheduled_events example folder, run the below command to compile the
17 | example.
18 |
19 | ```sh
20 | go build
21 | ```
22 |
23 | ### Usage
24 |
25 | ```
26 | Usage of scheduled_events:
27 | -guild string
28 | Test guild ID
29 | -token string
30 | Bot token
31 | -voice string
32 | Test voice channel ID
33 | ```
34 |
35 | The below example shows how to start the bot from the scheduled_events example folder.
36 |
37 | ```sh
38 | ./scheduled_events -guild YOUR_TESTING_GUILD -token YOUR_BOT_TOKEN -voice YOUR_TESTING_CHANNEL
39 | ```
40 |
--------------------------------------------------------------------------------
/examples/auto_moderation/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Auto Moderation Example
4 |
5 | This example demonstrates how to utilize DiscordGo to manage auto moderation
6 | rules and triggers.
7 |
8 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
9 | Discord chat channel for support.**
10 |
11 | ### Build
12 |
13 | This assumes you already have a working Go environment setup and that
14 | DiscordGo is correctly installed on your system.
15 |
16 | From within the auto_moderation example folder, run the below command to compile the
17 | example.
18 |
19 | ```sh
20 | go build
21 | ```
22 |
23 | ### Usage
24 |
25 | ```
26 | Usage of auto_moderation:
27 | -channel string
28 | ID of the testing channel
29 | -guild string
30 | ID of the testing guild
31 | -token string
32 | Bot authorization token
33 | ```
34 |
35 | The below example shows how to start the bot from the auto_moderation example folder.
36 |
37 | ```sh
38 | ./auto_moderation -channel YOUR_TESTING_CHANNEL -guild YOUR_TESTING_GUILD -token YOUR_BOT_TOKEN
39 | ```
40 |
--------------------------------------------------------------------------------
/examples/pingpong/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Ping Pong Example
4 |
5 | This example demonstrates how to utilize DiscordGo to create a Ping Pong Bot.
6 |
7 | This Bot will respond to "ping" with "Pong!" and "pong" with "Ping!".
8 |
9 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
10 | Discord chat channel for support.**
11 |
12 | ### Build
13 |
14 | This assumes you already have a working Go environment setup and that
15 | DiscordGo is correctly installed on your system.
16 |
17 |
18 | From within the pingpong example folder, run the below command to compile the
19 | example.
20 |
21 | ```sh
22 | go build
23 | ```
24 |
25 | ### Usage
26 |
27 | This example uses bot tokens for authentication only. While user/password is
28 | supported by DiscordGo, it is not recommended for bots.
29 |
30 | ```
31 | ./pingpong --help
32 | Usage of ./pingpong:
33 | -t string
34 | Bot Token
35 | ```
36 |
37 | The below example shows how to start the bot
38 |
39 | ```sh
40 | ./pingpong -t YOUR_BOT_TOKEN
41 | Bot is now running. Press CTRL-C to exit.
42 | ```
43 |
--------------------------------------------------------------------------------
/examples/components/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Components Example
4 |
5 | This example demonstrates how to utilize DiscordGo to create and use message
6 | components, such as buttons and select menus. For usage of the text input
7 | component and modals, please refer to the `modals` example.
8 |
9 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
10 | Discord chat channel for support.**
11 |
12 | ### Build
13 |
14 | This assumes you already have a working Go environment setup and that
15 | DiscordGo is correctly installed on your system.
16 |
17 | From within the components example folder, run the below command to compile the
18 | example.
19 |
20 | ```sh
21 | go build
22 | ```
23 |
24 | ### Usage
25 |
26 | ```
27 | Usage of components:
28 | -app string
29 | Application ID
30 | -guild string
31 | Test guild ID
32 | -token string
33 | Bot access token
34 | ```
35 |
36 | The below example shows how to start the bot from the components example folder.
37 |
38 | ```sh
39 | ./components -app YOUR_APPLICATION_ID -guild YOUR_TESTING_GUILD -token YOUR_BOT_TOKEN
40 | ```
41 |
--------------------------------------------------------------------------------
/examples/dm_pingpong/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Direct Message Ping Pong Example
4 |
5 | This example demonstrates how to utilize DiscordGo to create a Ping Pong Bot
6 | that sends the response through Direct Message.
7 |
8 | This Bot will respond to "ping" in any server it's in with "Pong!" in the
9 | sender's DM.
10 |
11 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
12 | Discord chat channel for support.**
13 |
14 | ### Build
15 |
16 | This assumes you already have a working Go environment setup and that
17 | DiscordGo is correctly installed on your system.
18 |
19 | From within the dm_pingpong example folder, run the below command to compile the
20 | example.
21 |
22 | ```sh
23 | go build
24 | ```
25 |
26 | ### Usage
27 |
28 | This example uses bot tokens for authentication only. While user/password is
29 | supported by DiscordGo, it is not recommended for bots.
30 |
31 | ```
32 | ./dm_pingpong --help
33 | Usage of ./dm_pingpong:
34 | -t string
35 | Bot Token
36 | ```
37 |
38 | The below example shows how to start the bot
39 |
40 | ```sh
41 | ./dm_pingpong -t YOUR_BOT_TOKEN
42 | Bot is now running. Press CTRL-C to exit.
43 | ```
44 |
--------------------------------------------------------------------------------
/examples/context_menus/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Context Menu Commands Example
4 |
5 | This example demonstrates how to utilize DiscordGo to create and use context
6 | menu commands. This example heavily relies on `slash_commands` example in
7 | command handling and registration, therefore it is recommended to be read
8 | before proceeding.
9 |
10 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
11 | Discord chat channel for support.**
12 |
13 | ### Build
14 |
15 | This assumes you already have a working Go environment setup and that
16 | DiscordGo is correctly installed on your system.
17 |
18 | From within the context_menus example folder, run the below command to compile the
19 | example.
20 |
21 | ```sh
22 | go build
23 | ```
24 |
25 | ### Usage
26 |
27 | ```
28 | Usage of context_menus:
29 | -app string
30 | Application ID
31 | -cleanup
32 | Cleanup of commands (default true)
33 | -guild string
34 | Test guild ID
35 | -token string
36 | Bot access token
37 | ```
38 |
39 | The below example shows how to start the bot from the context_menus example folder.
40 |
41 | ```sh
42 | ./context_menus -app YOUR_APPLICATION_ID -guild YOUR_TESTING_GUILD -token YOUR_BOT_TOKEN
43 | ```
44 |
--------------------------------------------------------------------------------
/examples/autocomplete/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Slash Command Autocomplete Option Example
4 |
5 | This example demonstrates how to utilize DiscordGo to create and use
6 | autocomplete options in Slash Commands. As this example uses interactions,
7 | slash commands and slash command options, it is recommended to read
8 | `slash_commands` example before proceeding.
9 |
10 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
11 | Discord chat channel for support.**
12 |
13 | ### Build
14 |
15 | This assumes you already have a working Go environment setup and that
16 | DiscordGo is correctly installed on your system.
17 |
18 | From within the autocomplete example folder, run the below command to compile the
19 | example.
20 |
21 | ```sh
22 | go build
23 | ```
24 |
25 | ### Usage
26 |
27 | ```
28 | Usage of autocomplete:
29 | -guild string
30 | Test guild ID. If not passed - bot registers commands globally
31 | -rmcmd
32 | Whether to remove all commands after shutting down (default true)
33 | -token string
34 | Bot access token
35 | ```
36 |
37 | The below example shows how to start the bot from the autocomplete example folder.
38 |
39 | ```sh
40 | ./autocomplete -guild YOUR_TESTING_GUILD -token YOUR_BOT_TOKEN
41 | ```
42 |
--------------------------------------------------------------------------------
/message_test.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestContentWithMoreMentionsReplaced(t *testing.T) {
8 | s := &Session{StateEnabled: true, State: NewState()}
9 |
10 | user := &User{
11 | ID: "user",
12 | Username: "User Name",
13 | }
14 |
15 | s.State.GuildAdd(&Guild{ID: "guild"})
16 | s.State.RoleAdd("guild", &Role{
17 | ID: "role",
18 | Name: "Role Name",
19 | Mentionable: true,
20 | })
21 | s.State.MemberAdd(&Member{
22 | User: user,
23 | Nick: "User Nick",
24 | GuildID: "guild",
25 | })
26 | s.State.ChannelAdd(&Channel{
27 | Name: "Channel Name",
28 | GuildID: "guild",
29 | ID: "channel",
30 | })
31 | m := &Message{
32 | Content: "<@&role> <@!user> <@user> <#channel>",
33 | ChannelID: "channel",
34 | MentionRoles: []string{"role"},
35 | Mentions: []*User{user},
36 | }
37 | if result, _ := m.ContentWithMoreMentionsReplaced(s); result != "@Role Name @User Nick @User Name #Channel Name" {
38 | t.Error(result)
39 | }
40 | }
41 | func TestGettingEmojisFromMessage(t *testing.T) {
42 | msg := "test test <:kitty14:811736565172011058> <:kitty4:811736468812595260>"
43 | m := &Message{
44 | Content: msg,
45 | }
46 | emojis := m.GetCustomEmojis()
47 | if len(emojis) < 1 {
48 | t.Error("No emojis found.")
49 | return
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/examples/modals/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Modals Example
4 |
5 | This example demonstrates how to utilize DiscordGo to send and process text
6 | inputs in modals. If you have not read `slash_commands` and `components`
7 | examples yet it is recommended to do so before proceesing. As this example
8 | is built using interactions and Slash Commands.
9 |
10 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
11 | Discord chat channel for support.**
12 |
13 | ### Build
14 |
15 | This assumes you already have a working Go environment setup and that
16 | DiscordGo is correctly installed on your system.
17 |
18 | From within the modals example folder, run the below command to compile the
19 | example.
20 |
21 | ```sh
22 | go build
23 | ```
24 |
25 | ### Usage
26 |
27 | ```
28 | Usage of modals:
29 | -app string
30 | Application ID
31 | -cleanup
32 | Cleanup of commands (default true)
33 | -guild string
34 | Test guild ID
35 | -results string
36 | Channel where send survey results to
37 | -token string
38 | Bot access token
39 | ```
40 |
41 | The below example shows how to start the bot from the modals example folder.
42 |
43 | ```sh
44 | ./modals -app YOUR_APPLICATION_ID -guild YOUR_TESTING_GUILD -results YOUR_TESTING_CHANNEL -token YOUR_BOT_TOKEN
45 | ```
46 |
--------------------------------------------------------------------------------
/examples/voice_receive/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Voice Receive Example
4 |
5 | This example experiments with receiving voice data from Discord. It joins
6 | a specified voice channel, listens for 10 seconds and saves .ogg files for each
7 | SSRC that it finds in the channel. An exercise left to the reader is to translate
8 | these SSRCs to user IDs; see speaking update events for this information. :)
9 |
10 | This example makes heavy use of the [Pion](https://github.com/pion) family of libraries.
11 | Go check them out for anything to do with voice, video or WebRTC; it's a great
12 | group of people maintaining the project!
13 |
14 | Please note that voice receive is **not** officially supported, any may break
15 | at essentially any time (and has in the past). This code works at the time of
16 | its writing, but YMMV in the future.
17 |
18 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
19 | Discord chat channel for support.**
20 |
21 | ### Build
22 |
23 | To build, make sure that modules are enabled, and run:
24 |
25 | ```sh
26 | go build
27 | ```
28 |
29 | ### Usage
30 |
31 | Three flags are required: the bot's token, the guild ID containing the voice channel to join, and the ID of the voice channel to join.
32 |
33 | ```sh
34 | ./voice_receive -t MY_TOKEN -g 1234123412341234 -c 5678567856785678
35 | ```
36 |
--------------------------------------------------------------------------------
/examples/avatar/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Avatar Example
4 |
5 | This example demonstrates how to utilize DiscordGo to change the avatar for
6 | a Discord account. This example works both with a local file or the URL of
7 | an image.
8 |
9 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
10 | Discord chat channel for support.**
11 |
12 | ### Build
13 |
14 | This assumes you already have a working Go environment setup and that
15 | DiscordGo is correctly installed on your system.
16 |
17 | From within the avatar example folder, run the below command to compile the
18 | example.
19 |
20 | ```sh
21 | go build
22 | ```
23 |
24 | ### Usage
25 |
26 | This example uses bot tokens for authentication only. While email/password is
27 | supported by DiscordGo, it is not recommended to use them.
28 |
29 | ```
30 | ./avatar --help
31 | Usage of ./avatar:
32 | -f string
33 | Avatar File Name
34 | -t string
35 | Bot Token
36 | -u string
37 | URL to the avatar image
38 | ```
39 |
40 | The below example shows how to set your Avatar from a local file.
41 |
42 | ```sh
43 | ./avatar -t TOKEN -f avatar.png
44 | ```
45 | The below example shows how to set your Avatar from a URL.
46 |
47 | ```sh
48 | ./avatar -t TOKEN -u https://raw.githubusercontent.com/bwmarrin/discordgo/master/docs/img/discordgo.svg
49 | ```
50 |
--------------------------------------------------------------------------------
/examples/slash_commands/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Slash Commands Example
4 |
5 | This example demonstrates how to utilize DiscordGo to create a Slash Command based bot,
6 | which would be able to listen and respond to interactions. This example covers all aspects
7 | of slash command interactions: options, choices, responses and followup messages.
8 | To avoid confusion, this example is more of a **step-by-step tutorial**, than a demonstration bot.
9 |
10 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
11 | Discord chat channel for support.**
12 |
13 | ### Build
14 |
15 | This assumes you already have a working Go environment setup and that
16 | DiscordGo is correctly installed on your system.
17 |
18 | From within the slash_commands example folder, run the below command to compile the
19 | example.
20 |
21 | ```sh
22 | go build
23 | ```
24 |
25 | ### Usage
26 |
27 | ```
28 | Usage of slash_commands:
29 | -guild string
30 | Test guild ID. If not passed - bot registers commands globally
31 | -rmcmd
32 | Whether to remove all commands after shutting down (default true)
33 | -token string
34 | Bot access token
35 | ```
36 |
37 | The below example shows how to start the bot from the slash_commands example folder.
38 |
39 | ```sh
40 | ./slash_commands -guild YOUR_TESTING_GUILD -token YOUR_BOT_TOKEN
41 | ```
42 |
--------------------------------------------------------------------------------
/examples/airhorn/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## DiscordGo Airhorn Example
4 |
5 | This example demonstrates how to utilize DiscordGo to listen for an !airhorn
6 | command in a channel and then play a sound to that user's current voice channel.
7 |
8 | **Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
9 | Discord chat channel for support.**
10 |
11 | ### Build
12 |
13 | This assumes you already have a working Go environment setup and that
14 | DiscordGo is correctly installed on your system.
15 |
16 | From within the airhorn example folder, run the below command to compile the
17 | example.
18 |
19 | ```sh
20 | go build
21 | ```
22 |
23 | ### Usage
24 |
25 | ```
26 | Usage of ./airhorn:
27 | -t string
28 | Bot Token
29 | ```
30 |
31 | The below example shows how to start the bot from the airhorn example folder.
32 |
33 | ```sh
34 | ./airhorn -t YOUR_BOT_TOKEN
35 | ```
36 |
37 | ### Creating sounds
38 |
39 | Airhorn bot uses [DCA](https://github.com/bwmarrin/dca) files, which are
40 | pre-computed files that are easy to send to Discord.
41 |
42 |
43 | See the below example of creating a DCA file from a MP3 file. This also works
44 | with WAV, FLAC, and many other file formats. Of course, you will need to [install](https://github.com/bwmarrin/dca/tree/master/cmd/dca#Getting-Started)
45 | FFmpeg and the DCA CLI first.
46 |
47 | ```sh
48 | ffmpeg -i test.mp3 -f s16le -ar 48000 -ac 2 pipe:1 | dca > test.dca
49 | ```
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Bruce Marriner
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of discordgo nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 |
--------------------------------------------------------------------------------
/oauth2_test.go:
--------------------------------------------------------------------------------
1 | package discordgo_test
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/bwmarrin/discordgo"
8 | )
9 |
10 | func ExampleApplication() {
11 |
12 | // Authentication Token pulled from environment variable DGU_TOKEN
13 | Token := os.Getenv("DGU_TOKEN")
14 | if Token == "" {
15 | return
16 | }
17 |
18 | // Create a new Discordgo session
19 | dg, err := discordgo.New(Token)
20 | if err != nil {
21 | log.Println(err)
22 | return
23 | }
24 |
25 | // Create an new Application
26 | ap := &discordgo.Application{}
27 | ap.Name = "TestApp"
28 | ap.Description = "TestDesc"
29 | ap, err = dg.ApplicationCreate(ap)
30 | log.Printf("ApplicationCreate: err: %+v, app: %+v\n", err, ap)
31 |
32 | // Get a specific Application by it's ID
33 | ap, err = dg.Application(ap.ID)
34 | log.Printf("Application: err: %+v, app: %+v\n", err, ap)
35 |
36 | // Update an existing Application with new values
37 | ap.Description = "Whooooa"
38 | ap, err = dg.ApplicationUpdate(ap.ID, ap)
39 | log.Printf("ApplicationUpdate: err: %+v, app: %+v\n", err, ap)
40 |
41 | // create a new bot account for this application
42 | bot, err := dg.ApplicationBotCreate(ap.ID)
43 | log.Printf("BotCreate: err: %+v, bot: %+v\n", err, bot)
44 |
45 | // Get a list of all applications for the authenticated user
46 | apps, err := dg.Applications()
47 | log.Printf("Applications: err: %+v, apps : %+v\n", err, apps)
48 | for k, v := range apps {
49 | log.Printf("Applications: %d : %+v\n", k, v)
50 | }
51 |
52 | // Delete the application we created.
53 | err = dg.ApplicationDelete(ap.ID)
54 | log.Printf("Delete: err: %+v\n", err)
55 |
56 | return
57 | }
58 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ## DiscordGo
2 |
3 |
4 |
5 | [Go](https://golang.org/) (Golang) interface for the [Discord](https://discord.com/)
6 | chat service. Provides both low-level direct bindings to the
7 | Discord API and helper functions that allow you to make custom clients and chat
8 | bot applications easily.
9 |
10 | [Discord](https://discord.com/) is an all-in-one voice and text chat for
11 | gamers that's free, secure, and works on both your desktop and phone.
12 |
13 | ### Why DiscordGo?
14 | * High Performance
15 | * Minimal Memory & CPU Load
16 | * Low-level bindings to Discord REST API Endpoints
17 | * Support for the data websocket interface
18 | * Multi-Server voice connections (send and receive)
19 | * State tracking and caching
20 |
21 | ### Learn More
22 | * Check out the [Getting Started](GettingStarted.md) section
23 | * Read the reference docs on [Godoc](https://godoc.org/github.com/bwmarrin/discordgo) or [GoWalker](https://gowalker.org/github.com/bwmarrin/discordgo)
24 | * Try the [examples](https://github.com/bwmarrin/discordgo/tree/master/examples)
25 | * Explore [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo)
26 |
27 | ### Join Us!
28 | Both of the below links take you to chat channels where you can get more
29 | information and support for DiscordGo. There's also a chance to make some
30 | friends :).
31 |
32 | * Join the [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) chat server dedicated to Go programming.
33 | * Join the [Discord API](https://discord.com/invite/discord-API) chat server dedicated to the Discord API.
34 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 |
5 | name: CI
6 |
7 | jobs:
8 | format:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Install Go
12 | uses: actions/setup-go@v3
13 | with:
14 | go-version: 1.18
15 | - name: Code
16 | uses: actions/checkout@v3
17 | - name: Check diff between gofmt and code
18 | run: diff <(gofmt -d .) <(echo -n)
19 |
20 | test:
21 | runs-on: ubuntu-latest
22 | strategy:
23 | matrix:
24 | go-version: [1.13, 1.14, 1.15, 1.16, 1.17, 1.18]
25 | steps:
26 | - name: Install Go
27 | uses: actions/setup-go@v3
28 | with:
29 | go-version: ${{ matrix.go-version }}
30 | - name: Code
31 | uses: actions/checkout@v3
32 | - run: go test -v -race ./...
33 |
34 | lint:
35 | runs-on: ubuntu-latest
36 | steps:
37 | - name: Install Go
38 | uses: actions/setup-go@v3
39 | with:
40 | go-version: 1.18
41 | - name: Code
42 | uses: actions/checkout@v3
43 | - name: Go vet
44 | run: go vet -x ./...
45 |
46 | - name: GolangCI-Lint
47 | uses: golangci/golangci-lint-action@v3
48 | if: github.event.name == 'pull_request'
49 | with:
50 | only-new-issues: true
51 | skip-pkg-cache: true
52 | skip-build-cache: true
53 |
54 | - name: GolangCI-Lint
55 | if: github.event.name != 'pull_request' # See https://github.com/golangci/golangci-lint-action/issues/362
56 | run: |
57 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2
58 |
59 | $(go env GOPATH)/bin/golangci-lint run
60 |
61 |
--------------------------------------------------------------------------------
/examples/stage_instance/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "time"
8 |
9 | "github.com/bwmarrin/discordgo"
10 | )
11 |
12 | // Flags
13 | var (
14 | GuildID = flag.String("guild", "", "Test guild ID")
15 | StageChannelID = flag.String("stage", "", "Test stage channel ID")
16 | BotToken = flag.String("token", "", "Bot token")
17 | )
18 |
19 | func init() { flag.Parse() }
20 |
21 | // To be correctly used, the bot needs to be in a guild.
22 | // All actions must be done on a stage channel event
23 | func main() {
24 | s, _ := discordgo.New("Bot " + *BotToken)
25 | s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
26 | fmt.Println("Bot is ready")
27 | })
28 |
29 | err := s.Open()
30 | if err != nil {
31 | log.Fatalf("Cannot open the session: %v", err)
32 | }
33 | defer s.Close()
34 |
35 | // Create a new Stage instance on the previous channel
36 | si, err := s.StageInstanceCreate(&discordgo.StageInstanceParams{
37 | ChannelID: *StageChannelID,
38 | Topic: "Amazing topic",
39 | PrivacyLevel: discordgo.StageInstancePrivacyLevelGuildOnly,
40 | SendStartNotification: true,
41 | })
42 | if err != nil {
43 | log.Fatalf("Cannot create stage instance: %v", err)
44 | }
45 | log.Printf("Stage Instance %s has been successfully created", si.Topic)
46 |
47 | // Edit the stage instance with a new Topic
48 | si, err = s.StageInstanceEdit(*StageChannelID, &discordgo.StageInstanceParams{
49 | Topic: "New amazing topic",
50 | })
51 | if err != nil {
52 | log.Fatalf("Cannot edit stage instance: %v", err)
53 | }
54 | log.Printf("Stage Instance %s has been successfully edited", si.Topic)
55 |
56 | time.Sleep(5 * time.Second)
57 | if err = s.StageInstanceDelete(*StageChannelID); err != nil {
58 | log.Fatalf("Cannot delete stage instance: %v", err)
59 | }
60 | log.Printf("Stage Instance %s has been successfully deleted", si.Topic)
61 | }
62 |
--------------------------------------------------------------------------------
/examples/pingpong/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 | "os/signal"
8 | "syscall"
9 |
10 | "github.com/bwmarrin/discordgo"
11 | )
12 |
13 | // Variables used for command line parameters
14 | var (
15 | Token string
16 | )
17 |
18 | func init() {
19 |
20 | flag.StringVar(&Token, "t", "", "Bot Token")
21 | flag.Parse()
22 | }
23 |
24 | func main() {
25 |
26 | // Create a new Discord session using the provided bot token.
27 | dg, err := discordgo.New("Bot " + Token)
28 | if err != nil {
29 | fmt.Println("error creating Discord session,", err)
30 | return
31 | }
32 |
33 | // Register the messageCreate func as a callback for MessageCreate events.
34 | dg.AddHandler(messageCreate)
35 |
36 | // In this example, we only care about receiving message events.
37 | dg.Identify.Intents = discordgo.IntentsGuildMessages
38 |
39 | // Open a websocket connection to Discord and begin listening.
40 | err = dg.Open()
41 | if err != nil {
42 | fmt.Println("error opening connection,", err)
43 | return
44 | }
45 |
46 | // Wait here until CTRL-C or other term signal is received.
47 | fmt.Println("Bot is now running. Press CTRL-C to exit.")
48 | sc := make(chan os.Signal, 1)
49 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
50 | <-sc
51 |
52 | // Cleanly close down the Discord session.
53 | dg.Close()
54 | }
55 |
56 | // This function will be called (due to AddHandler above) every time a new
57 | // message is created on any channel that the authenticated bot has access to.
58 | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
59 |
60 | // Ignore all messages created by the bot itself
61 | // This isn't required in this specific example but it's a good practice.
62 | if m.Author.ID == s.State.User.ID {
63 | return
64 | }
65 | // If the message is "ping" reply with "Pong!"
66 | if m.Content == "ping" {
67 | s.ChannelMessageSend(m.ChannelID, "Pong!")
68 | }
69 |
70 | // If the message is "pong" reply with "Ping!"
71 | if m.Content == "pong" {
72 | s.ChannelMessageSend(m.ChannelID, "Ping!")
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/examples/threads/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 | "os/signal"
9 | "strings"
10 | "time"
11 |
12 | "github.com/bwmarrin/discordgo"
13 | )
14 |
15 | // Flags
16 | var (
17 | BotToken = flag.String("token", "", "Bot token")
18 | )
19 |
20 | const timeout time.Duration = time.Second * 10
21 |
22 | var games map[string]time.Time = make(map[string]time.Time)
23 |
24 | func init() { flag.Parse() }
25 |
26 | func main() {
27 | s, _ := discordgo.New("Bot " + *BotToken)
28 | s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
29 | fmt.Println("Bot is ready")
30 | })
31 | s.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
32 | if strings.Contains(m.Content, "ping") {
33 | if ch, err := s.State.Channel(m.ChannelID); err != nil || !ch.IsThread() {
34 | thread, err := s.MessageThreadStartComplex(m.ChannelID, m.ID, &discordgo.ThreadStart{
35 | Name: "Pong game with " + m.Author.Username,
36 | AutoArchiveDuration: 60,
37 | Invitable: false,
38 | RateLimitPerUser: 10,
39 | })
40 | if err != nil {
41 | panic(err)
42 | }
43 | _, _ = s.ChannelMessageSend(thread.ID, "pong")
44 | m.ChannelID = thread.ID
45 | } else {
46 | _, _ = s.ChannelMessageSendReply(m.ChannelID, "pong", m.Reference())
47 | }
48 | games[m.ChannelID] = time.Now()
49 | <-time.After(timeout)
50 | if time.Since(games[m.ChannelID]) >= timeout {
51 | archived := true
52 | locked := true
53 | _, err := s.ChannelEditComplex(m.ChannelID, &discordgo.ChannelEdit{
54 | Archived: &archived,
55 | Locked: &locked,
56 | })
57 | if err != nil {
58 | panic(err)
59 | }
60 | }
61 | }
62 | })
63 | s.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged)
64 |
65 | err := s.Open()
66 | if err != nil {
67 | log.Fatalf("Cannot open the session: %v", err)
68 | }
69 | defer s.Close()
70 |
71 | stop := make(chan os.Signal, 1)
72 | signal.Notify(stop, os.Interrupt)
73 | <-stop
74 | log.Println("Graceful shutdown")
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/interactions_test.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "bytes"
5 | "crypto/ed25519"
6 | "encoding/hex"
7 | "net/http/httptest"
8 | "strconv"
9 | "strings"
10 | "testing"
11 | "time"
12 | )
13 |
14 | func TestVerifyInteraction(t *testing.T) {
15 | pubkey, privkey, err := ed25519.GenerateKey(nil)
16 | if err != nil {
17 | t.Errorf("error generating signing keypair: %s", err)
18 | }
19 | timestamp := "1608597133"
20 |
21 | t.Run("success", func(t *testing.T) {
22 | body := "body"
23 | request := httptest.NewRequest("POST", "http://localhost/interaction", strings.NewReader(body))
24 | request.Header.Set("X-Signature-Timestamp", timestamp)
25 |
26 | var msg bytes.Buffer
27 | msg.WriteString(timestamp)
28 | msg.WriteString(body)
29 | signature := ed25519.Sign(privkey, msg.Bytes())
30 | request.Header.Set("X-Signature-Ed25519", hex.EncodeToString(signature[:ed25519.SignatureSize]))
31 |
32 | if !VerifyInteraction(request, pubkey) {
33 | t.Error("expected true, got false")
34 | }
35 | })
36 |
37 | t.Run("failure/modified body", func(t *testing.T) {
38 | body := "body"
39 | request := httptest.NewRequest("POST", "http://localhost/interaction", strings.NewReader("WRONG"))
40 | request.Header.Set("X-Signature-Timestamp", timestamp)
41 |
42 | var msg bytes.Buffer
43 | msg.WriteString(timestamp)
44 | msg.WriteString(body)
45 | signature := ed25519.Sign(privkey, msg.Bytes())
46 | request.Header.Set("X-Signature-Ed25519", hex.EncodeToString(signature[:ed25519.SignatureSize]))
47 |
48 | if VerifyInteraction(request, pubkey) {
49 | t.Error("expected false, got true")
50 | }
51 | })
52 |
53 | t.Run("failure/modified timestamp", func(t *testing.T) {
54 | body := "body"
55 | request := httptest.NewRequest("POST", "http://localhost/interaction", strings.NewReader("WRONG"))
56 | request.Header.Set("X-Signature-Timestamp", strconv.FormatInt(time.Now().Add(time.Minute).Unix(), 10))
57 |
58 | var msg bytes.Buffer
59 | msg.WriteString(timestamp)
60 | msg.WriteString(body)
61 | signature := ed25519.Sign(privkey, msg.Bytes())
62 | request.Header.Set("X-Signature-Ed25519", hex.EncodeToString(signature[:ed25519.SignatureSize]))
63 |
64 | if VerifyInteraction(request, pubkey) {
65 | t.Error("expected false, got true")
66 | }
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/examples/avatar/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/base64"
5 | "flag"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "os"
10 |
11 | "github.com/bwmarrin/discordgo"
12 | )
13 |
14 | // Variables used for command line parameters
15 | var (
16 | Token string
17 | AvatarFile string
18 | AvatarURL string
19 | )
20 |
21 | func init() {
22 |
23 | flag.StringVar(&Token, "t", "", "Bot Token")
24 | flag.StringVar(&AvatarFile, "f", "", "Avatar File Name")
25 | flag.StringVar(&AvatarURL, "u", "", "URL to the avatar image")
26 | flag.Parse()
27 |
28 | if Token == "" || (AvatarFile == "" && AvatarURL == "") {
29 | flag.Usage()
30 | os.Exit(1)
31 | }
32 | }
33 |
34 | func main() {
35 |
36 | // Create a new Discord session using the provided login information.
37 | dg, err := discordgo.New("Bot " + Token)
38 | if err != nil {
39 | fmt.Println("error creating Discord session,", err)
40 | return
41 | }
42 |
43 | // Declare these here so they can be used in the below two if blocks and
44 | // still carry over to the end of this function.
45 | var base64img string
46 | var contentType string
47 |
48 | // If we're using a URL link for the Avatar
49 | if AvatarURL != "" {
50 |
51 | resp, err := http.Get(AvatarURL)
52 | if err != nil {
53 | fmt.Println("Error retrieving the file, ", err)
54 | return
55 | }
56 |
57 | defer func() {
58 | _ = resp.Body.Close()
59 | }()
60 |
61 | img, err := ioutil.ReadAll(resp.Body)
62 | if err != nil {
63 | fmt.Println("Error reading the response, ", err)
64 | return
65 | }
66 |
67 | contentType = http.DetectContentType(img)
68 | base64img = base64.StdEncoding.EncodeToString(img)
69 | }
70 |
71 | // If we're using a local file for the Avatar
72 | if AvatarFile != "" {
73 | img, err := ioutil.ReadFile(AvatarFile)
74 | if err != nil {
75 | fmt.Println(err)
76 | }
77 |
78 | contentType = http.DetectContentType(img)
79 | base64img = base64.StdEncoding.EncodeToString(img)
80 | }
81 |
82 | // Now lets format our base64 image into the proper format Discord wants
83 | // and then call UserUpdate to set it as our user's Avatar.
84 | avatar := fmt.Sprintf("data:%s;base64,%s", contentType, base64img)
85 | _, err = dg.UserUpdate("", avatar)
86 | if err != nil {
87 | fmt.Println(err)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/webhook.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | // Webhook stores the data for a webhook.
4 | type Webhook struct {
5 | ID string `json:"id"`
6 | Type WebhookType `json:"type"`
7 | GuildID string `json:"guild_id"`
8 | ChannelID string `json:"channel_id"`
9 | User *User `json:"user"`
10 | Name string `json:"name"`
11 | Avatar string `json:"avatar"`
12 | Token string `json:"token"`
13 |
14 | // ApplicationID is the bot/OAuth2 application that created this webhook
15 | ApplicationID string `json:"application_id,omitempty"`
16 | }
17 |
18 | // WebhookType is the type of Webhook (see WebhookType* consts) in the Webhook struct
19 | // https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types
20 | type WebhookType int
21 |
22 | // Valid WebhookType values
23 | const (
24 | WebhookTypeIncoming WebhookType = 1
25 | WebhookTypeChannelFollower WebhookType = 2
26 | )
27 |
28 | // WebhookParams is a struct for webhook params, used in the WebhookExecute command.
29 | type WebhookParams struct {
30 | Content string `json:"content,omitempty"`
31 | Username string `json:"username,omitempty"`
32 | AvatarURL string `json:"avatar_url,omitempty"`
33 | TTS bool `json:"tts,omitempty"`
34 | Files []*File `json:"-"`
35 | Components []MessageComponent `json:"components"`
36 | Embeds []*MessageEmbed `json:"embeds,omitempty"`
37 | AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
38 | // Only MessageFlagsSuppressEmbeds and MessageFlagsEphemeral can be set.
39 | // MessageFlagsEphemeral can only be set when using Followup Message Create endpoint.
40 | Flags MessageFlags `json:"flags,omitempty"`
41 | }
42 |
43 | // WebhookEdit stores data for editing of a webhook message.
44 | type WebhookEdit struct {
45 | Content *string `json:"content,omitempty"`
46 | Components *[]MessageComponent `json:"components,omitempty"`
47 | Embeds *[]*MessageEmbed `json:"embeds,omitempty"`
48 | Files []*File `json:"-"`
49 | AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
50 | }
51 |
--------------------------------------------------------------------------------
/discord.go:
--------------------------------------------------------------------------------
1 | // Discordgo - Discord bindings for Go
2 | // Available at https://github.com/bwmarrin/discordgo
3 |
4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved.
5 | // Use of this source code is governed by a BSD-style
6 | // license that can be found in the LICENSE file.
7 |
8 | // This file contains high level helper functions and easy entry points for the
9 | // entire discordgo package. These functions are being developed and are very
10 | // experimental at this point. They will most likely change so please use the
11 | // low level functions if that's a problem.
12 |
13 | // Package discordgo provides Discord binding for Go
14 | package discordgo
15 |
16 | import (
17 | "net/http"
18 | "runtime"
19 | "time"
20 |
21 | "github.com/gorilla/websocket"
22 | )
23 |
24 | // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
25 | const VERSION = "0.27.0"
26 |
27 | // New creates a new Discord session with provided token.
28 | // If the token is for a bot, it must be prefixed with "Bot "
29 | // e.g. "Bot ..."
30 | // Or if it is an OAuth2 token, it must be prefixed with "Bearer "
31 | // e.g. "Bearer ..."
32 | func New(token string) (s *Session, err error) {
33 |
34 | // Create an empty Session interface.
35 | s = &Session{
36 | State: NewState(),
37 | Ratelimiter: NewRatelimiter(),
38 | StateEnabled: true,
39 | Compress: true,
40 | ShouldReconnectOnError: true,
41 | ShouldRetryOnRateLimit: true,
42 | ShardID: 0,
43 | ShardCount: 1,
44 | MaxRestRetries: 3,
45 | Client: &http.Client{Timeout: (20 * time.Second)},
46 | Dialer: websocket.DefaultDialer,
47 | UserAgent: "DiscordBot (https://github.com/bwmarrin/discordgo, v" + VERSION + ")",
48 | sequence: new(int64),
49 | LastHeartbeatAck: time.Now().UTC(),
50 | }
51 |
52 | // Initialize the Identify Package with defaults
53 | // These can be modified prior to calling Open()
54 | s.Identify.Compress = true
55 | s.Identify.LargeThreshold = 250
56 | s.Identify.Properties.OS = runtime.GOOS
57 | s.Identify.Properties.Browser = "DiscordGo v" + VERSION
58 | s.Identify.Intents = IntentsAllWithoutPrivileged
59 | s.Identify.Token = token
60 | s.Token = token
61 |
62 | return
63 | }
64 |
--------------------------------------------------------------------------------
/locales.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | // Locale represents the accepted languages for Discord.
4 | // https://discord.com/developers/docs/reference#locales
5 | type Locale string
6 |
7 | // String returns the human-readable string of the locale
8 | func (l Locale) String() string {
9 | if name, ok := Locales[l]; ok {
10 | return name
11 | }
12 | return Unknown.String()
13 | }
14 |
15 | // All defined locales in Discord
16 | const (
17 | EnglishUS Locale = "en-US"
18 | EnglishGB Locale = "en-GB"
19 | Bulgarian Locale = "bg"
20 | ChineseCN Locale = "zh-CN"
21 | ChineseTW Locale = "zh-TW"
22 | Croatian Locale = "hr"
23 | Czech Locale = "cs"
24 | Danish Locale = "da"
25 | Dutch Locale = "nl"
26 | Finnish Locale = "fi"
27 | French Locale = "fr"
28 | German Locale = "de"
29 | Greek Locale = "el"
30 | Hindi Locale = "hi"
31 | Hungarian Locale = "hu"
32 | Italian Locale = "it"
33 | Japanese Locale = "ja"
34 | Korean Locale = "ko"
35 | Lithuanian Locale = "lt"
36 | Norwegian Locale = "no"
37 | Polish Locale = "pl"
38 | PortugueseBR Locale = "pt-BR"
39 | Romanian Locale = "ro"
40 | Russian Locale = "ru"
41 | SpanishES Locale = "es-ES"
42 | Swedish Locale = "sv-SE"
43 | Thai Locale = "th"
44 | Turkish Locale = "tr"
45 | Ukrainian Locale = "uk"
46 | Vietnamese Locale = "vi"
47 | Unknown Locale = ""
48 | )
49 |
50 | // Locales is a map of all the languages codes to their names.
51 | var Locales = map[Locale]string{
52 | EnglishUS: "English (United States)",
53 | EnglishGB: "English (Great Britain)",
54 | Bulgarian: "Bulgarian",
55 | ChineseCN: "Chinese (China)",
56 | ChineseTW: "Chinese (Taiwan)",
57 | Croatian: "Croatian",
58 | Czech: "Czech",
59 | Danish: "Danish",
60 | Dutch: "Dutch",
61 | Finnish: "Finnish",
62 | French: "French",
63 | German: "German",
64 | Greek: "Greek",
65 | Hindi: "Hindi",
66 | Hungarian: "Hungarian",
67 | Italian: "Italian",
68 | Japanese: "Japanese",
69 | Korean: "Korean",
70 | Lithuanian: "Lithuanian",
71 | Norwegian: "Norwegian",
72 | Polish: "Polish",
73 | PortugueseBR: "Portuguese (Brazil)",
74 | Romanian: "Romanian",
75 | Russian: "Russian",
76 | SpanishES: "Spanish (Spain)",
77 | Swedish: "Swedish",
78 | Thai: "Thai",
79 | Turkish: "Turkish",
80 | Ukrainian: "Ukrainian",
81 | Vietnamese: "Vietnamese",
82 | Unknown: "unknown",
83 | }
84 |
--------------------------------------------------------------------------------
/examples/voice_receive/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/bwmarrin/discordgo"
9 | "github.com/pion/rtp"
10 | "github.com/pion/webrtc/v3/pkg/media"
11 | "github.com/pion/webrtc/v3/pkg/media/oggwriter"
12 | )
13 |
14 | // Variables used for command line parameters
15 | var (
16 | Token string
17 | ChannelID string
18 | GuildID string
19 | )
20 |
21 | func init() {
22 | flag.StringVar(&Token, "t", "", "Bot Token")
23 | flag.StringVar(&GuildID, "g", "", "Guild in which voice channel exists")
24 | flag.StringVar(&ChannelID, "c", "", "Voice channel to connect to")
25 | flag.Parse()
26 | }
27 |
28 | func createPionRTPPacket(p *discordgo.Packet) *rtp.Packet {
29 | return &rtp.Packet{
30 | Header: rtp.Header{
31 | Version: 2,
32 | // Taken from Discord voice docs
33 | PayloadType: 0x78,
34 | SequenceNumber: p.Sequence,
35 | Timestamp: p.Timestamp,
36 | SSRC: p.SSRC,
37 | },
38 | Payload: p.Opus,
39 | }
40 | }
41 |
42 | func handleVoice(c chan *discordgo.Packet) {
43 | files := make(map[uint32]media.Writer)
44 | for p := range c {
45 | file, ok := files[p.SSRC]
46 | if !ok {
47 | var err error
48 | file, err = oggwriter.New(fmt.Sprintf("%d.ogg", p.SSRC), 48000, 2)
49 | if err != nil {
50 | fmt.Printf("failed to create file %d.ogg, giving up on recording: %v\n", p.SSRC, err)
51 | return
52 | }
53 | files[p.SSRC] = file
54 | }
55 | // Construct pion RTP packet from DiscordGo's type.
56 | rtp := createPionRTPPacket(p)
57 | err := file.WriteRTP(rtp)
58 | if err != nil {
59 | fmt.Printf("failed to write to file %d.ogg, giving up on recording: %v\n", p.SSRC, err)
60 | }
61 | }
62 |
63 | // Once we made it here, we're done listening for packets. Close all files
64 | for _, f := range files {
65 | f.Close()
66 | }
67 | }
68 |
69 | func main() {
70 | s, err := discordgo.New("Bot " + Token)
71 | if err != nil {
72 | fmt.Println("error creating Discord session:", err)
73 | return
74 | }
75 | defer s.Close()
76 |
77 | // We only really care about receiving voice state updates.
78 | s.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsGuildVoiceStates)
79 |
80 | err = s.Open()
81 | if err != nil {
82 | fmt.Println("error opening connection:", err)
83 | return
84 | }
85 |
86 | v, err := s.ChannelVoiceJoin(GuildID, ChannelID, true, false)
87 | if err != nil {
88 | fmt.Println("failed to join voice channel:", err)
89 | return
90 | }
91 |
92 | go func() {
93 | time.Sleep(10 * time.Second)
94 | close(v.OpusRecv)
95 | v.Close()
96 | }()
97 |
98 | handleVoice(v.OpusRecv)
99 | }
100 |
--------------------------------------------------------------------------------
/examples/scheduled_events/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 | "os/signal"
9 | "time"
10 |
11 | "github.com/bwmarrin/discordgo"
12 | )
13 |
14 | // Flags
15 | var (
16 | GuildID = flag.String("guild", "", "Test guild ID")
17 | VoiceChannelID = flag.String("voice", "", "Test voice channel ID")
18 | BotToken = flag.String("token", "", "Bot token")
19 | )
20 |
21 | func init() { flag.Parse() }
22 |
23 | func main() {
24 | s, _ := discordgo.New("Bot " + *BotToken)
25 | s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
26 | fmt.Println("Bot is ready")
27 | })
28 |
29 | err := s.Open()
30 | if err != nil {
31 | log.Fatalf("Cannot open the session: %v", err)
32 | }
33 | defer s.Close()
34 |
35 | event := createAmazingEvent(s)
36 | transformEventToExternalEvent(s, event)
37 |
38 | stop := make(chan os.Signal, 1)
39 | signal.Notify(stop, os.Interrupt)
40 | <-stop
41 | log.Println("Graceful shutdown")
42 |
43 | }
44 |
45 | // Create a new event on guild
46 | func createAmazingEvent(s *discordgo.Session) *discordgo.GuildScheduledEvent {
47 | // Define the starting time (must be in future)
48 | startingTime := time.Now().Add(1 * time.Hour)
49 | // Define the ending time (must be after starting time)
50 | endingTime := startingTime.Add(30 * time.Minute)
51 | // Create the event
52 | scheduledEvent, err := s.GuildScheduledEventCreate(*GuildID, &discordgo.GuildScheduledEventParams{
53 | Name: "Amazing Event",
54 | Description: "This event will start in 1 hour and last 30 minutes",
55 | ScheduledStartTime: &startingTime,
56 | ScheduledEndTime: &endingTime,
57 | EntityType: discordgo.GuildScheduledEventEntityTypeVoice,
58 | ChannelID: *VoiceChannelID,
59 | PrivacyLevel: discordgo.GuildScheduledEventPrivacyLevelGuildOnly,
60 | })
61 | if err != nil {
62 | log.Printf("Error creating scheduled event: %v", err)
63 | return nil
64 | }
65 |
66 | fmt.Println("Created scheduled event:", scheduledEvent.Name)
67 | return scheduledEvent
68 | }
69 |
70 | func transformEventToExternalEvent(s *discordgo.Session, event *discordgo.GuildScheduledEvent) {
71 | scheduledEvent, err := s.GuildScheduledEventEdit(*GuildID, event.ID, &discordgo.GuildScheduledEventParams{
72 | Name: "Amazing Event @ Discord Website",
73 | EntityType: discordgo.GuildScheduledEventEntityTypeExternal,
74 | EntityMetadata: &discordgo.GuildScheduledEventEntityMetadata{
75 | Location: "https://discord.com",
76 | },
77 | })
78 | if err != nil {
79 | log.Printf("Error during transformation of scheduled voice event into external event: %v", err)
80 | return
81 | }
82 |
83 | fmt.Println("Created scheduled event:", scheduledEvent.Name)
84 | }
85 |
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "mime/multipart"
8 | "net/textproto"
9 | "strconv"
10 | "strings"
11 | "time"
12 | )
13 |
14 | // SnowflakeTimestamp returns the creation time of a Snowflake ID relative to the creation of Discord.
15 | func SnowflakeTimestamp(ID string) (t time.Time, err error) {
16 | i, err := strconv.ParseInt(ID, 10, 64)
17 | if err != nil {
18 | return
19 | }
20 | timestamp := (i >> 22) + 1420070400000
21 | t = time.Unix(0, timestamp*1000000)
22 | return
23 | }
24 |
25 | // MultipartBodyWithJSON returns the contentType and body for a discord request
26 | // data : The object to encode for payload_json in the multipart request
27 | // files : Files to include in the request
28 | func MultipartBodyWithJSON(data interface{}, files []*File) (requestContentType string, requestBody []byte, err error) {
29 | body := &bytes.Buffer{}
30 | bodywriter := multipart.NewWriter(body)
31 |
32 | payload, err := Marshal(data)
33 | if err != nil {
34 | return
35 | }
36 |
37 | var p io.Writer
38 |
39 | h := make(textproto.MIMEHeader)
40 | h.Set("Content-Disposition", `form-data; name="payload_json"`)
41 | h.Set("Content-Type", "application/json")
42 |
43 | p, err = bodywriter.CreatePart(h)
44 | if err != nil {
45 | return
46 | }
47 |
48 | if _, err = p.Write(payload); err != nil {
49 | return
50 | }
51 |
52 | for i, file := range files {
53 | h := make(textproto.MIMEHeader)
54 | h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="files[%d]"; filename="%s"`, i, quoteEscaper.Replace(file.Name)))
55 | contentType := file.ContentType
56 | if contentType == "" {
57 | contentType = "application/octet-stream"
58 | }
59 | h.Set("Content-Type", contentType)
60 |
61 | p, err = bodywriter.CreatePart(h)
62 | if err != nil {
63 | return
64 | }
65 |
66 | if _, err = io.Copy(p, file.Reader); err != nil {
67 | return
68 | }
69 | }
70 |
71 | err = bodywriter.Close()
72 | if err != nil {
73 | return
74 | }
75 |
76 | return bodywriter.FormDataContentType(), body.Bytes(), nil
77 | }
78 |
79 | func avatarURL(avatarHash, defaultAvatarURL, staticAvatarURL, animatedAvatarURL, size string) string {
80 | var URL string
81 | if avatarHash == "" {
82 | URL = defaultAvatarURL
83 | } else if strings.HasPrefix(avatarHash, "a_") {
84 | URL = animatedAvatarURL
85 | } else {
86 | URL = staticAvatarURL
87 | }
88 |
89 | if size != "" {
90 | return URL + "?size=" + size
91 | }
92 | return URL
93 | }
94 |
95 | func bannerURL(bannerHash, staticBannerURL, animatedBannerURL, size string) string {
96 | var URL string
97 | if bannerHash == "" {
98 | return ""
99 | } else if strings.HasPrefix(bannerHash, "a_") {
100 | URL = animatedBannerURL
101 | } else {
102 | URL = staticBannerURL
103 | }
104 |
105 | if size != "" {
106 | return URL + "?size=" + size
107 | }
108 | return URL
109 | }
110 |
111 | func iconURL(iconHash, staticIconURL, animatedIconURL, size string) string {
112 | var URL string
113 | if iconHash == "" {
114 | return ""
115 | } else if strings.HasPrefix(iconHash, "a_") {
116 | URL = animatedIconURL
117 | } else {
118 | URL = staticIconURL
119 | }
120 |
121 | if size != "" {
122 | return URL + "?size=" + size
123 | }
124 | return URL
125 | }
126 |
--------------------------------------------------------------------------------
/logging.go:
--------------------------------------------------------------------------------
1 | // Discordgo - Discord bindings for Go
2 | // Available at https://github.com/bwmarrin/discordgo
3 |
4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved.
5 | // Use of this source code is governed by a BSD-style
6 | // license that can be found in the LICENSE file.
7 |
8 | // This file contains code related to discordgo package logging
9 |
10 | package discordgo
11 |
12 | import (
13 | "fmt"
14 | "log"
15 | "runtime"
16 | "strings"
17 | )
18 |
19 | const (
20 |
21 | // LogError level is used for critical errors that could lead to data loss
22 | // or panic that would not be returned to a calling function.
23 | LogError int = iota
24 |
25 | // LogWarning level is used for very abnormal events and errors that are
26 | // also returned to a calling function.
27 | LogWarning
28 |
29 | // LogInformational level is used for normal non-error activity
30 | LogInformational
31 |
32 | // LogDebug level is for very detailed non-error activity. This is
33 | // very spammy and will impact performance.
34 | LogDebug
35 | )
36 |
37 | // Logger can be used to replace the standard logging for discordgo
38 | var Logger func(msgL, caller int, format string, a ...interface{})
39 |
40 | // msglog provides package wide logging consistency for discordgo
41 | // the format, a... portion this command follows that of fmt.Printf
42 | // msgL : LogLevel of the message
43 | // caller : 1 + the number of callers away from the message source
44 | // format : Printf style message format
45 | // a ... : comma separated list of values to pass
46 | func msglog(msgL, caller int, format string, a ...interface{}) {
47 |
48 | if Logger != nil {
49 | Logger(msgL, caller, format, a...)
50 | } else {
51 |
52 | pc, file, line, _ := runtime.Caller(caller)
53 |
54 | files := strings.Split(file, "/")
55 | file = files[len(files)-1]
56 |
57 | name := runtime.FuncForPC(pc).Name()
58 | fns := strings.Split(name, ".")
59 | name = fns[len(fns)-1]
60 |
61 | msg := fmt.Sprintf(format, a...)
62 |
63 | log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg)
64 | }
65 | }
66 |
67 | // helper function that wraps msglog for the Session struct
68 | // This adds a check to insure the message is only logged
69 | // if the session log level is equal or higher than the
70 | // message log level
71 | func (s *Session) log(msgL int, format string, a ...interface{}) {
72 |
73 | if msgL > s.LogLevel {
74 | return
75 | }
76 |
77 | msglog(msgL, 2, format, a...)
78 | }
79 |
80 | // helper function that wraps msglog for the VoiceConnection struct
81 | // This adds a check to insure the message is only logged
82 | // if the voice connection log level is equal or higher than the
83 | // message log level
84 | func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) {
85 |
86 | if msgL > v.LogLevel {
87 | return
88 | }
89 |
90 | msglog(msgL, 2, format, a...)
91 | }
92 |
93 | // printJSON is a helper function to display JSON data in an easy to read format.
94 | /* NOT USED ATM
95 | func printJSON(body []byte) {
96 | var prettyJSON bytes.Buffer
97 | error := json.Indent(&prettyJSON, body, "", "\t")
98 | if error != nil {
99 | log.Print("JSON parse error: ", error)
100 | }
101 | log.Println(string(prettyJSON.Bytes()))
102 | }
103 | */
104 |
--------------------------------------------------------------------------------
/examples/dm_pingpong/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 | "os/signal"
8 | "syscall"
9 |
10 | "github.com/bwmarrin/discordgo"
11 | )
12 |
13 | // Variables used for command line parameters
14 | var (
15 | Token string
16 | )
17 |
18 | func init() {
19 | flag.StringVar(&Token, "t", "", "Bot Token")
20 | flag.Parse()
21 | }
22 |
23 | func main() {
24 | // Create a new Discord session using the provided bot token.
25 | dg, err := discordgo.New("Bot " + Token)
26 | if err != nil {
27 | fmt.Println("error creating Discord session,", err)
28 | return
29 | }
30 |
31 | // Register the messageCreate func as a callback for MessageCreate events.
32 | dg.AddHandler(messageCreate)
33 |
34 | // Just like the ping pong example, we only care about receiving message
35 | // events in this example.
36 | dg.Identify.Intents = discordgo.IntentsGuildMessages
37 |
38 | // Open a websocket connection to Discord and begin listening.
39 | err = dg.Open()
40 | if err != nil {
41 | fmt.Println("error opening connection,", err)
42 | return
43 | }
44 |
45 | // Wait here until CTRL-C or other term signal is received.
46 | fmt.Println("Bot is now running. Press CTRL-C to exit.")
47 | sc := make(chan os.Signal, 1)
48 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
49 | <-sc
50 |
51 | // Cleanly close down the Discord session.
52 | dg.Close()
53 | }
54 |
55 | // This function will be called (due to AddHandler above) every time a new
56 | // message is created on any channel that the authenticated bot has access to.
57 | //
58 | // It is called whenever a message is created but only when it's sent through a
59 | // server as we did not request IntentsDirectMessages.
60 | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
61 | // Ignore all messages created by the bot itself
62 | // This isn't required in this specific example but it's a good practice.
63 | if m.Author.ID == s.State.User.ID {
64 | return
65 | }
66 | // In this example, we only care about messages that are "ping".
67 | if m.Content != "ping" {
68 | return
69 | }
70 |
71 | // We create the private channel with the user who sent the message.
72 | channel, err := s.UserChannelCreate(m.Author.ID)
73 | if err != nil {
74 | // If an error occurred, we failed to create the channel.
75 | //
76 | // Some common causes are:
77 | // 1. We don't share a server with the user (not possible here).
78 | // 2. We opened enough DM channels quickly enough for Discord to
79 | // label us as abusing the endpoint, blocking us from opening
80 | // new ones.
81 | fmt.Println("error creating channel:", err)
82 | s.ChannelMessageSend(
83 | m.ChannelID,
84 | "Something went wrong while sending the DM!",
85 | )
86 | return
87 | }
88 | // Then we send the message through the channel we created.
89 | _, err = s.ChannelMessageSend(channel.ID, "Pong!")
90 | if err != nil {
91 | // If an error occurred, we failed to send the message.
92 | //
93 | // It may occur either when we do not share a server with the
94 | // user (highly unlikely as we just received a message) or
95 | // the user disabled DM in their settings (more likely).
96 | fmt.Println("error sending DM message:", err)
97 | s.ChannelMessageSend(
98 | m.ChannelID,
99 | "Failed to send you a DM. "+
100 | "Did you disable DM in your privacy settings?",
101 | )
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/ratelimit_test.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "strconv"
7 | "testing"
8 | "time"
9 | )
10 |
11 | // This test takes ~2 seconds to run
12 | func TestRatelimitReset(t *testing.T) {
13 | rl := NewRatelimiter()
14 |
15 | sendReq := func(endpoint string) {
16 | bucket := rl.LockBucket(endpoint)
17 |
18 | headers := http.Header(make(map[string][]string))
19 |
20 | headers.Set("X-RateLimit-Remaining", "0")
21 | // Reset for approx 2 seconds from now
22 | headers.Set("X-RateLimit-Reset", fmt.Sprint(float64(time.Now().Add(time.Second*2).UnixNano())/1e9))
23 | headers.Set("Date", time.Now().Format(time.RFC850))
24 |
25 | err := bucket.Release(headers)
26 | if err != nil {
27 | t.Errorf("Release returned error: %v", err)
28 | }
29 | }
30 |
31 | sent := time.Now()
32 | sendReq("/guilds/99/channels")
33 | sendReq("/guilds/55/channels")
34 | sendReq("/guilds/66/channels")
35 |
36 | sendReq("/guilds/99/channels")
37 | sendReq("/guilds/55/channels")
38 | sendReq("/guilds/66/channels")
39 |
40 | // We hit the same endpoint 2 times, so we should only be ratelimited 2 second
41 | // And always less than 4 seconds (unless you're on a stoneage computer or using swap or something...)
42 | if time.Since(sent) >= time.Second && time.Since(sent) < time.Second*4 {
43 | t.Log("OK", time.Since(sent))
44 | } else {
45 | t.Error("Did not ratelimit correctly, got:", time.Since(sent))
46 | }
47 | }
48 |
49 | // This test takes ~1 seconds to run
50 | func TestRatelimitGlobal(t *testing.T) {
51 | rl := NewRatelimiter()
52 |
53 | sendReq := func(endpoint string) {
54 | bucket := rl.LockBucket(endpoint)
55 |
56 | headers := http.Header(make(map[string][]string))
57 |
58 | headers.Set("X-RateLimit-Global", "1")
59 | // Reset for approx 1 seconds from now
60 | headers.Set("X-RateLimit-Reset-After", "1")
61 |
62 | err := bucket.Release(headers)
63 | if err != nil {
64 | t.Errorf("Release returned error: %v", err)
65 | }
66 | }
67 |
68 | sent := time.Now()
69 |
70 | // This should trigger a global ratelimit
71 | sendReq("/guilds/99/channels")
72 | time.Sleep(time.Millisecond * 100)
73 |
74 | // This shouldn't go through in less than 1 second
75 | sendReq("/guilds/55/channels")
76 |
77 | if time.Since(sent) >= time.Second && time.Since(sent) < time.Second*2 {
78 | t.Log("OK", time.Since(sent))
79 | } else {
80 | t.Error("Did not ratelimit correctly, got:", time.Since(sent))
81 | }
82 | }
83 |
84 | func BenchmarkRatelimitSingleEndpoint(b *testing.B) {
85 | rl := NewRatelimiter()
86 | for i := 0; i < b.N; i++ {
87 | sendBenchReq("/guilds/99/channels", rl)
88 | }
89 | }
90 |
91 | func BenchmarkRatelimitParallelMultiEndpoints(b *testing.B) {
92 | rl := NewRatelimiter()
93 | b.RunParallel(func(pb *testing.PB) {
94 | i := 0
95 | for pb.Next() {
96 | sendBenchReq("/guilds/"+strconv.Itoa(i)+"/channels", rl)
97 | i++
98 | }
99 | })
100 | }
101 |
102 | // Does not actually send requests, but locks the bucket and releases it with made-up headers
103 | func sendBenchReq(endpoint string, rl *RateLimiter) {
104 | bucket := rl.LockBucket(endpoint)
105 |
106 | headers := http.Header(make(map[string][]string))
107 |
108 | headers.Set("X-RateLimit-Remaining", "10")
109 | headers.Set("X-RateLimit-Reset", fmt.Sprint(float64(time.Now().UnixNano())/1e9))
110 | headers.Set("Date", time.Now().Format(time.RFC850))
111 |
112 | bucket.Release(headers)
113 | }
114 |
--------------------------------------------------------------------------------
/docs/GettingStarted.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | This page is dedicated to helping you get started on your way to making the
4 | next great Discord bot or client with DiscordGo. Once you've done that please
5 | don't forget to submit it to the
6 | [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) list :).
7 |
8 |
9 | **First, lets cover a few topics so you can make the best choices on how to
10 | move forward from here.**
11 |
12 | #### Bot Application
13 | A bot application is a special program that interacts with the Discord servers
14 | to perform some form of automation or provide some type of service. Examples
15 | are things like number trivia games, music streaming, channel moderation,
16 | sending reminders, playing loud airhorn sounds, comic generators, YouTube
17 | integration, Twitch integration... You're *almost* only limited by your imagination.
18 |
19 | Bot applications require the use of a special Bot account. These accounts are
20 | tied to your personal user account. Bot accounts cannot login with the normal
21 | user clients and they cannot join servers the same way a user does. They do not
22 | have access to some user client specific features however they gain access to
23 | many Bot specific features.
24 |
25 | To create a new bot account first create yourself a normal user account on
26 | Discord then visit the [My Applications](https://discord.com/developers/applications/me)
27 | page and click on the **New Application** box. Follow the prompts from there
28 | to finish creating your account.
29 |
30 |
31 | **More information about Bot vs Client accounts can be found [here](https://discord.com/developers/docs/topics/oauth2#bot-vs-user-accounts).**
32 |
33 | # Requirements
34 |
35 | DiscordGo requires Go version 1.4 or higher. It has been tested to compile and
36 | run successfully on Debian Linux 8, FreeBSD 10, and Windows 7. It is expected
37 | that it should work anywhere Go 1.4 or higher works. If you run into problems
38 | please let us know :).
39 |
40 | You must already have a working Go environment setup to use DiscordGo. If you
41 | are new to Go and have not yet installed and tested it on your computer then
42 | please visit [this page](https://golang.org/doc/install) first then I highly
43 | recommend you walk though [A Tour of Go](https://tour.golang.org/welcome/1) to
44 | help get your familiar with the Go language. Also checkout the relevent Go plugin
45 | for your editor — they are hugely helpful when developing Go code.
46 |
47 | * Vim — [vim-go](https://github.com/fatih/vim-go)
48 | * Sublime — [GoSublime](https://github.com/DisposaBoy/GoSublime)
49 | * Atom — [go-plus](https://atom.io/packages/go-plus)
50 | * Visual Studio — [vscode-go](https://github.com/Microsoft/vscode-go)
51 |
52 |
53 | # Install DiscordGo
54 |
55 | Like any other Go package the fist step is to `go get` the package. This will
56 | always pull the latest tagged release from the master branch. Then run
57 | `go install` to compile and install the libraries on your system.
58 |
59 | #### Linux/BSD
60 |
61 | Run go get to download the package to your GOPATH/src folder.
62 |
63 | ```sh
64 | go get github.com/bwmarrin/discordgo
65 | ```
66 |
67 | Finally, compile and install the package into the GOPATH/pkg folder. This isn't
68 | absolutely required but doing this will allow the Go plugin for your editor to
69 | provide autocomplete for all DiscordGo functions.
70 |
71 | ```sh
72 | cd $GOPATH/src/github.com/bwmarrin/discordgo
73 | go install
74 | ```
75 |
76 | #### Windows
77 | Placeholder.
78 |
79 |
80 | # Next...
81 | More coming soon.
82 |
--------------------------------------------------------------------------------
/tools/cmd/eventhandlers/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "go/format"
6 | "go/parser"
7 | "go/token"
8 | "io/ioutil"
9 | "log"
10 | "path/filepath"
11 | "regexp"
12 | "sort"
13 | "strings"
14 | "text/template"
15 | )
16 |
17 | var eventHandlerTmpl = template.Must(template.New("eventHandler").Funcs(template.FuncMap{
18 | "constName": constName,
19 | "isDiscordEvent": isDiscordEvent,
20 | "privateName": privateName,
21 | }).Parse(`// Code generated by \"eventhandlers\"; DO NOT EDIT
22 | // See events.go
23 |
24 | package discordgo
25 |
26 | // Following are all the event types.
27 | // Event type values are used to match the events returned by Discord.
28 | // EventTypes surrounded by __ are synthetic and are internal to DiscordGo.
29 | const ({{range .}}
30 | {{privateName .}}EventType = "{{constName .}}"{{end}}
31 | )
32 | {{range .}}
33 | // {{privateName .}}EventHandler is an event handler for {{.}} events.
34 | type {{privateName .}}EventHandler func(*Session, *{{.}})
35 |
36 | // Type returns the event type for {{.}} events.
37 | func (eh {{privateName .}}EventHandler) Type() string {
38 | return {{privateName .}}EventType
39 | }
40 | {{if isDiscordEvent .}}
41 | // New returns a new instance of {{.}}.
42 | func (eh {{privateName .}}EventHandler) New() interface{} {
43 | return &{{.}}{}
44 | }{{end}}
45 | // Handle is the handler for {{.}} events.
46 | func (eh {{privateName .}}EventHandler) Handle(s *Session, i interface{}) {
47 | if t, ok := i.(*{{.}}); ok {
48 | eh(s, t)
49 | }
50 | }
51 |
52 | {{end}}
53 | func handlerForInterface(handler interface{}) EventHandler {
54 | switch v := handler.(type) {
55 | case func(*Session, interface{}):
56 | return interfaceEventHandler(v){{range .}}
57 | case func(*Session, *{{.}}):
58 | return {{privateName .}}EventHandler(v){{end}}
59 | }
60 |
61 | return nil
62 | }
63 |
64 | func init() { {{range .}}{{if isDiscordEvent .}}
65 | registerInterfaceProvider({{privateName .}}EventHandler(nil)){{end}}{{end}}
66 | }
67 | `))
68 |
69 | func main() {
70 | var buf bytes.Buffer
71 | dir := filepath.Dir(".")
72 |
73 | fs := token.NewFileSet()
74 | parsedFile, err := parser.ParseFile(fs, "events.go", nil, 0)
75 | if err != nil {
76 | log.Fatalf("warning: internal error: could not parse events.go: %s", err)
77 | return
78 | }
79 |
80 | names := []string{}
81 | for object := range parsedFile.Scope.Objects {
82 | names = append(names, object)
83 | }
84 | sort.Strings(names)
85 | eventHandlerTmpl.Execute(&buf, names)
86 |
87 | src, err := format.Source(buf.Bytes())
88 | if err != nil {
89 | log.Println("warning: internal error: invalid Go generated:", err)
90 | src = buf.Bytes()
91 | }
92 |
93 | err = ioutil.WriteFile(filepath.Join(dir, strings.ToLower("eventhandlers.go")), src, 0644)
94 | if err != nil {
95 | log.Fatal(buf, "writing output: %s", err)
96 | }
97 | }
98 |
99 | var constRegexp = regexp.MustCompile("([a-z])([A-Z])")
100 |
101 | func constCase(name string) string {
102 | return strings.ToUpper(constRegexp.ReplaceAllString(name, "${1}_${2}"))
103 | }
104 |
105 | func isDiscordEvent(name string) bool {
106 | switch {
107 | case name == "Connect", name == "Disconnect", name == "Event", name == "RateLimit", name == "Interface":
108 | return false
109 | default:
110 | return true
111 | }
112 | }
113 |
114 | func constName(name string) string {
115 | if !isDiscordEvent(name) {
116 | return "__" + constCase(name) + "__"
117 | }
118 |
119 | return constCase(name)
120 | }
121 |
122 | func privateName(name string) string {
123 | return strings.ToLower(string(name[0])) + name[1:]
124 | }
125 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | To start off you can check out existing Pull Requests and Issues to get a gasp of what problems we’re currently solving and what features you can implement.
4 |
5 | ## Issues
6 |
7 | Our issues are mostly used for bugs, however we welcome refactoring and conceptual issues.
8 |
9 | Any other conversation would belong and would be moved into “Discussions”.
10 |
11 | ## Discussions
12 |
13 | We use discussions for ideas, polls, announcements and help questions.
14 |
15 | Don’t hesitate to ask, we always would try to help.
16 |
17 | ## Pull Requests
18 |
19 | If you want to help us by improving existing or adding new features, you create what’s called a Pull Request (aka PR). It allows us to review your code, suggest changes and merge it.
20 |
21 | Here are some tips on how to make a good first PR:
22 |
23 | - When creating a PR, please consider a distinctive name and description for it, so the maintainers can understand what your PR changes / adds / removes.
24 | - It’s always a good idea to link documentation when implementing a new feature / endpoint
25 | - If you’re resolving an issue, don’t forget to [link it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) in the description.
26 | - Enable the checkbox to allow maintainers to edit your PR and make commits in the PR branch when necessary.
27 | - We may ask for changes, usually through suggestions or pull request comments. You can apply suggestions right in the UI. Any other change needs to be done manually.
28 | - Don’t forget to mark PR comments resolved when you’re done applying the changes.
29 | - Be patient and don’t close and reopen your PR when no one responds, sometimes it might be held for a while. There might be a lot of reasons: release preparation, the feature is not significant, maintainers are busy, etc.
30 |
31 |
32 | When your changes are still incomplete (i.e. in Work In Progress state), you can still create a PR, but consider making it a draft.
33 | To make a draft PR, you can change the type of PR by clicking to a triangle next to the “Create Pull Request” button.
34 |
35 | Once you’re done, you can mark it as “Ready for review”, and we’ll get right on it.
36 |
37 |
38 | # Code style
39 |
40 | To standardize and make things less messy we have a certain code style, that is persistent throughout the codebase.
41 |
42 | ## Naming
43 |
44 | ### REST methods
45 |
46 | When naming a REST method, while it might seem counterintuitive, we specify the entity before the action verb (for GET endpoints we don’t specify one however). Here’s an example:
47 |
48 | > Endpoint name: Get Channel Message
49 | >
50 | > Method name: `ChannelMessage`
51 |
52 | > Endpoint name: Edit Channel Message
53 | >
54 | > Method name: `ChannelMessageEdit`
55 |
56 | ### Parameter structures
57 |
58 | When making a complex REST endpoint, sometimes you might need to implement a `Param` structure. This structure contains parameters for certain endpoint/set of endpoints.
59 |
60 | - If an endpoint/set of endpoints have mostly same parameters, it’s a good idea to use a single `Param` structure for them. Here’s an example:
61 |
62 | > Endpoint: `GuildMemberEdit`
63 | >
64 | > `Param` structure: `GuildMemberParams`
65 | - If an endpoint/set of endpoints have differentiating parameters, `Param` structure can be named after the endpoint’s verb. Here’s an example:
66 |
67 | > Endpoint: `ChannelMessageSendComplex`
68 | >
69 | > `Param` structure: `MessageSend`
70 |
71 | > Endpoint: `ChannelMessageEditComplex`
72 | >
73 | > `Param` structure: `MessageEdit`
74 |
75 | ### Events
76 |
77 | When naming an event, we follow gateway’s internal naming (which often matches with the official event name in the docs). Here’s an example:
78 |
79 | > Event name: Interaction Create (`INTERACTION_CREATE`)
80 | >
81 | > Structure name: `InteractionCreate`
82 |
83 | ## Returns
84 |
85 | In our REST functions we usually favor named returns instead of regular anonymous returns. This helps readability.
86 |
87 | Additionally we try to avoid naked return statements for functions with a long body. Since it’s easier to loose track of the return result.
88 |
--------------------------------------------------------------------------------
/user.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | // UserFlags is the flags of "user" (see UserFlags* consts)
4 | // https://discord.com/developers/docs/resources/user#user-object-user-flags
5 | type UserFlags int
6 |
7 | // Valid UserFlags values
8 | const (
9 | UserFlagDiscordEmployee UserFlags = 1 << 0
10 | UserFlagDiscordPartner UserFlags = 1 << 1
11 | UserFlagHypeSquadEvents UserFlags = 1 << 2
12 | UserFlagBugHunterLevel1 UserFlags = 1 << 3
13 | UserFlagHouseBravery UserFlags = 1 << 6
14 | UserFlagHouseBrilliance UserFlags = 1 << 7
15 | UserFlagHouseBalance UserFlags = 1 << 8
16 | UserFlagEarlySupporter UserFlags = 1 << 9
17 | UserFlagTeamUser UserFlags = 1 << 10
18 | UserFlagSystem UserFlags = 1 << 12
19 | UserFlagBugHunterLevel2 UserFlags = 1 << 14
20 | UserFlagVerifiedBot UserFlags = 1 << 16
21 | UserFlagVerifiedBotDeveloper UserFlags = 1 << 17
22 | UserFlagDiscordCertifiedModerator UserFlags = 1 << 18
23 | )
24 |
25 | // A User stores all data for an individual Discord user.
26 | type User struct {
27 | // The ID of the user.
28 | ID string `json:"id"`
29 |
30 | // The email of the user. This is only present when
31 | // the application possesses the email scope for the user.
32 | Email string `json:"email"`
33 |
34 | // The user's username.
35 | Username string `json:"username"`
36 |
37 | // The hash of the user's avatar. Use Session.UserAvatar
38 | // to retrieve the avatar itself.
39 | Avatar string `json:"avatar"`
40 |
41 | // The user's chosen language option.
42 | Locale string `json:"locale"`
43 |
44 | // The discriminator of the user (4 numbers after name).
45 | Discriminator string `json:"discriminator"`
46 |
47 | // The token of the user. This is only present for
48 | // the user represented by the current session.
49 | Token string `json:"token"`
50 |
51 | // Whether the user's email is verified.
52 | Verified bool `json:"verified"`
53 |
54 | // Whether the user has multi-factor authentication enabled.
55 | MFAEnabled bool `json:"mfa_enabled"`
56 |
57 | // The hash of the user's banner image.
58 | Banner string `json:"banner"`
59 |
60 | // User's banner color, encoded as an integer representation of hexadecimal color code
61 | AccentColor int `json:"accent_color"`
62 |
63 | // Whether the user is a bot.
64 | Bot bool `json:"bot"`
65 |
66 | // The public flags on a user's account.
67 | // This is a combination of bit masks; the presence of a certain flag can
68 | // be checked by performing a bitwise AND between this int and the flag.
69 | PublicFlags UserFlags `json:"public_flags"`
70 |
71 | // The type of Nitro subscription on a user's account.
72 | // Only available when the request is authorized via a Bearer token.
73 | PremiumType int `json:"premium_type"`
74 |
75 | // Whether the user is an Official Discord System user (part of the urgent message system).
76 | System bool `json:"system"`
77 |
78 | // The flags on a user's account.
79 | // Only available when the request is authorized via a Bearer token.
80 | Flags int `json:"flags"`
81 | }
82 |
83 | // String returns a unique identifier of the form username#discriminator
84 | func (u *User) String() string {
85 | return u.Username + "#" + u.Discriminator
86 | }
87 |
88 | // Mention return a string which mentions the user
89 | func (u *User) Mention() string {
90 | return "<@" + u.ID + ">"
91 | }
92 |
93 | // AvatarURL returns a URL to the user's avatar.
94 | // size: The size of the user's avatar as a power of two
95 | // if size is an empty string, no size parameter will
96 | // be added to the URL.
97 | func (u *User) AvatarURL(size string) string {
98 | return avatarURL(u.Avatar, EndpointDefaultUserAvatar(u.Discriminator),
99 | EndpointUserAvatar(u.ID, u.Avatar), EndpointUserAvatarAnimated(u.ID, u.Avatar), size)
100 | }
101 |
102 | // BannerURL returns the URL of the users's banner image.
103 | // size: The size of the desired banner image as a power of two
104 | // Image size can be any power of two between 16 and 4096.
105 | func (u *User) BannerURL(size string) string {
106 | return bannerURL(u.Banner, EndpointUserBanner(u.ID, u.Banner), EndpointUserBannerAnimated(u.ID, u.Banner), size)
107 | }
108 |
--------------------------------------------------------------------------------
/examples/auto_moderation/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 | "os/signal"
9 | "sync"
10 |
11 | "github.com/bwmarrin/discordgo"
12 | )
13 |
14 | // Command line flags
15 | var (
16 | BotToken = flag.String("token", "", "Bot authorization token")
17 | GuildID = flag.String("guild", "", "ID of the testing guild")
18 | ChannelID = flag.String("channel", "", "ID of the testing channel")
19 | )
20 |
21 | func init() { flag.Parse() }
22 |
23 | func main() {
24 | session, _ := discordgo.New("Bot " + *BotToken)
25 | session.Identify.Intents |= discordgo.IntentAutoModerationExecution
26 | session.Identify.Intents |= discordgo.IntentMessageContent
27 |
28 | enabled := true
29 | rule, err := session.AutoModerationRuleCreate(*GuildID, &discordgo.AutoModerationRule{
30 | Name: "Auto Moderation example",
31 | EventType: discordgo.AutoModerationEventMessageSend,
32 | TriggerType: discordgo.AutoModerationEventTriggerKeyword,
33 | TriggerMetadata: &discordgo.AutoModerationTriggerMetadata{
34 | KeywordFilter: []string{"*cat*"},
35 | RegexPatterns: []string{"(c|b)at"},
36 | },
37 |
38 | Enabled: &enabled,
39 | Actions: []discordgo.AutoModerationAction{
40 | {Type: discordgo.AutoModerationRuleActionBlockMessage},
41 | },
42 | })
43 | if err != nil {
44 | panic(err)
45 | }
46 |
47 | fmt.Println("Successfully created the rule")
48 | defer session.AutoModerationRuleDelete(*GuildID, rule.ID)
49 |
50 | session.AddHandlerOnce(func(s *discordgo.Session, e *discordgo.AutoModerationActionExecution) {
51 | _, err = session.AutoModerationRuleEdit(*GuildID, rule.ID, &discordgo.AutoModerationRule{
52 | TriggerMetadata: &discordgo.AutoModerationTriggerMetadata{
53 | KeywordFilter: []string{"cat"},
54 | },
55 | Actions: []discordgo.AutoModerationAction{
56 | {Type: discordgo.AutoModerationRuleActionTimeout, Metadata: &discordgo.AutoModerationActionMetadata{Duration: 60}},
57 | {Type: discordgo.AutoModerationRuleActionSendAlertMessage, Metadata: &discordgo.AutoModerationActionMetadata{
58 | ChannelID: e.ChannelID,
59 | }},
60 | },
61 | })
62 | if err != nil {
63 | session.AutoModerationRuleDelete(*GuildID, rule.ID)
64 | panic(err)
65 | }
66 |
67 | s.ChannelMessageSend(e.ChannelID, "Congratulations! You have just triggered an auto moderation rule.\n"+
68 | "The current trigger can match anywhere in the word, so even if you write the trigger word as a part of another word, it will still match.\n"+
69 | "The rule has now been changed, now the trigger matches only in the full words.\n"+
70 | "Additionally, when you send a message, an alert will be sent to this channel and you will be **timed out** for a minute.\n")
71 |
72 | var counter int
73 | var counterMutex sync.Mutex
74 | session.AddHandler(func(s *discordgo.Session, e *discordgo.AutoModerationActionExecution) {
75 | action := "unknown"
76 | switch e.Action.Type {
77 | case discordgo.AutoModerationRuleActionBlockMessage:
78 | action = "block message"
79 | case discordgo.AutoModerationRuleActionSendAlertMessage:
80 | action = "send alert message into <#" + e.Action.Metadata.ChannelID + ">"
81 | case discordgo.AutoModerationRuleActionTimeout:
82 | action = "timeout"
83 | }
84 |
85 | counterMutex.Lock()
86 | counter++
87 | if counter == 1 {
88 | counterMutex.Unlock()
89 | s.ChannelMessageSend(e.ChannelID, "Nothing has changed, right? "+
90 | "Well, since separate gateway events are fired per each action (current is "+action+"), "+
91 | "you'll see a second message about an action pop up soon")
92 | } else if counter == 2 {
93 | counterMutex.Unlock()
94 | s.ChannelMessageSend(e.ChannelID, "Now the second ("+action+") action got executed.")
95 | s.ChannelMessageSend(e.ChannelID, "And... you've made it! That's the end of the example.\n"+
96 | "For more information about the automod and how to use it, "+
97 | "you can visit the official Discord docs: https://discord.dev/resources/auto-moderation or ask in our server: https://discord.gg/6dzbuDpSWY",
98 | )
99 |
100 | session.Close()
101 | session.AutoModerationRuleDelete(*GuildID, rule.ID)
102 | os.Exit(0)
103 | }
104 | })
105 | })
106 |
107 | err = session.Open()
108 | if err != nil {
109 | log.Fatalf("Cannot open the session: %v", err)
110 | }
111 | defer session.Close()
112 |
113 | stop := make(chan os.Signal, 1)
114 | signal.Notify(stop, os.Interrupt)
115 | <-stop
116 | log.Println("Graceful shutdown")
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DiscordGo
2 |
3 | [](https://pkg.go.dev/github.com/bwmarrin/discordgo) [](https://goreportcard.com/report/github.com/bwmarrin/discordgo) [](https://github.com/bwmarrin/discordgo/actions/workflows/ci.yml) [](https://discord.gg/golang) [](https://discord.com/invite/discord-api)
4 |
5 |
6 |
7 | DiscordGo is a [Go](https://golang.org/) package that provides low level
8 | bindings to the [Discord](https://discord.com/) chat client API. DiscordGo
9 | has nearly complete support for all of the Discord API endpoints, websocket
10 | interface, and voice interface.
11 |
12 | If you would like to help the DiscordGo package please use
13 | [this link](https://discord.com/oauth2/authorize?client_id=173113690092994561&scope=bot)
14 | to add the official DiscordGo test bot **dgo** to your server. This provides
15 | indispensable help to this project.
16 |
17 | * See [dgVoice](https://github.com/bwmarrin/dgvoice) package for an example of
18 | additional voice helper functions and features for DiscordGo.
19 |
20 | * See [dca](https://github.com/bwmarrin/dca) for an **experimental** stand alone
21 | tool that wraps `ffmpeg` to create opus encoded audio appropriate for use with
22 | Discord (and DiscordGo).
23 |
24 | **For help with this package or general Go discussion, please join the [Discord
25 | Gophers](https://discord.gg/golang) chat server.**
26 |
27 | ## Getting Started
28 |
29 | ### Installing
30 |
31 | This assumes you already have a working Go environment, if not please see
32 | [this page](https://golang.org/doc/install) first.
33 |
34 | `go get` *will always pull the latest tagged release from the master branch.*
35 |
36 | ```sh
37 | go get github.com/bwmarrin/discordgo
38 | ```
39 |
40 | ### Usage
41 |
42 | Import the package into your project.
43 |
44 | ```go
45 | import "github.com/bwmarrin/discordgo"
46 | ```
47 |
48 | Construct a new Discord client which can be used to access the variety of
49 | Discord API functions and to set callback functions for Discord events.
50 |
51 | ```go
52 | discord, err := discordgo.New("Bot " + "authentication token")
53 | ```
54 |
55 | See Documentation and Examples below for more detailed information.
56 |
57 |
58 | ## Documentation
59 |
60 | **NOTICE**: This library and the Discord API are unfinished.
61 | Because of that there may be major changes to library in the future.
62 |
63 | The DiscordGo code is fairly well documented at this point and is currently
64 | the only documentation available. Go reference (below) presents that information in a nice format.
65 |
66 | - [](https://pkg.go.dev/github.com/bwmarrin/discordgo)
67 | - Hand crafted documentation coming eventually.
68 |
69 |
70 | ## Examples
71 |
72 | Below is a list of examples and other projects using DiscordGo. Please submit
73 | an issue if you would like your project added or removed from this list.
74 |
75 | - [DiscordGo Examples](https://github.com/bwmarrin/discordgo/tree/master/examples) - A collection of example programs written with DiscordGo
76 | - [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) - A curated list of high quality projects using DiscordGo
77 |
78 | ## Troubleshooting
79 | For help with common problems please reference the
80 | [Troubleshooting](https://github.com/bwmarrin/discordgo/wiki/Troubleshooting)
81 | section of the project wiki.
82 |
83 |
84 | ## Contributing
85 | Contributions are very welcomed, however please follow the below guidelines.
86 |
87 | - First open an issue describing the bug or enhancement so it can be
88 | discussed.
89 | - Try to match current naming conventions as closely as possible.
90 | - This package is intended to be a low level direct mapping of the Discord API,
91 | so please avoid adding enhancements outside of that scope without first
92 | discussing it.
93 | - Create a Pull Request with your changes against the master branch.
94 |
95 |
96 | ## List of Discord APIs
97 |
98 | See [this chart](https://abal.moe/Discord/Libraries.html) for a feature
99 | comparison and list of other Discord API libraries.
100 |
101 | ## Special Thanks
102 |
103 | [Chris Rhodes](https://github.com/iopred) - For the DiscordGo logo and tons of PRs.
104 |
--------------------------------------------------------------------------------
/examples/linked_roles/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "flag"
6 | "fmt"
7 | "net/http"
8 | "net/url"
9 |
10 | "github.com/bwmarrin/discordgo"
11 | "github.com/joho/godotenv"
12 | "golang.org/x/oauth2"
13 | )
14 |
15 | var oauthConfig = oauth2.Config{
16 | Endpoint: oauth2.Endpoint{
17 | AuthURL: "https://discord.com/oauth2/authorize",
18 | TokenURL: "https://discord.com/api/oauth2/token",
19 | },
20 | Scopes: []string{"identify", "role_connections.write"},
21 | }
22 |
23 | var (
24 | appID = flag.String("app", "", "Application ID")
25 | token = flag.String("token", "", "Application token")
26 | clientSecret = flag.String("secret", "", "OAuth2 secret")
27 | redirectURL = flag.String("redirect", "", "OAuth2 Redirect URL")
28 | )
29 |
30 | func init() {
31 | flag.Parse()
32 | godotenv.Load()
33 | oauthConfig.ClientID = *appID
34 | oauthConfig.ClientSecret = *clientSecret
35 | oauthConfig.RedirectURL, _ = url.JoinPath(*redirectURL, "/linked-roles-callback")
36 | }
37 |
38 | func main() {
39 | s, _ := discordgo.New("Bot " + *token)
40 |
41 | _, err := s.ApplicationRoleConnectionMetadataUpdate(*appID, []*discordgo.ApplicationRoleConnectionMetadata{
42 | {
43 | Type: discordgo.ApplicationRoleConnectionMetadataIntegerGreaterThanOrEqual,
44 | Key: "loc",
45 | Name: "Lines of Code",
46 | NameLocalizations: map[discordgo.Locale]string{},
47 | Description: "Total lines of code written",
48 | DescriptionLocalizations: map[discordgo.Locale]string{},
49 | },
50 | {
51 | Type: discordgo.ApplicationRoleConnectionMetadataBooleanEqual,
52 | Key: "gopher",
53 | Name: "Gopher",
54 | NameLocalizations: map[discordgo.Locale]string{},
55 | Description: "Writes in Go",
56 | DescriptionLocalizations: map[discordgo.Locale]string{},
57 | },
58 | {
59 | Type: discordgo.ApplicationRoleConnectionMetadataDatetimeGreaterThanOrEqual,
60 | Key: "first_line",
61 | Name: "First line written",
62 | NameLocalizations: map[discordgo.Locale]string{},
63 | Description: "Days since the first line of code",
64 | DescriptionLocalizations: map[discordgo.Locale]string{},
65 | },
66 | })
67 | if err != nil {
68 | panic(err)
69 | }
70 |
71 | fmt.Println("Updated application metadata")
72 | http.HandleFunc("/linked-roles", func(w http.ResponseWriter, r *http.Request) {
73 | w.Header().Set("Cache-Control", "no-cache")
74 | // Redirect the user to Discord OAuth2 page.
75 | http.Redirect(w, r, oauthConfig.AuthCodeURL("random-state"), http.StatusMovedPermanently)
76 | })
77 | http.HandleFunc("/linked-roles-callback", func(w http.ResponseWriter, r *http.Request) {
78 | q := r.URL.Query()
79 | // A safeguard against CSRF attacks.
80 | // Usually tied to requesting user or random.
81 | // NOTE: Hardcoded for the sake of the example.
82 | if q["state"][0] != "random-state" {
83 | return
84 | }
85 |
86 | // Fetch the tokens with code we've received.
87 | tokens, err := oauthConfig.Exchange(r.Context(), q["code"][0])
88 | if err != nil {
89 | w.Write([]byte(err.Error()))
90 | return
91 | }
92 |
93 | // Construct a temporary session with user's OAuth2 access_token.
94 | ts, _ := discordgo.New("Bearer " + tokens.AccessToken)
95 |
96 | // Retrive the user data.
97 | u, err := ts.User("@me")
98 | if err != nil {
99 | w.Write([]byte(err.Error()))
100 | return
101 | }
102 |
103 | // Fetch external metadata...
104 | // NOTE: Hardcoded for the sake of the example.
105 | metadata := map[string]string{
106 | "gopher": "1", // 1 for true, 0 for false
107 | "loc": "10000",
108 | "first_line": "1970-01-01", // YYYY-MM-DD
109 | }
110 |
111 | // And submit it back to discord.
112 | _, err = ts.UserApplicationRoleConnectionUpdate(*appID, &discordgo.ApplicationRoleConnection{
113 | PlatformName: "Discord Gophers",
114 | PlatformUsername: u.Username,
115 | Metadata: metadata,
116 | })
117 | if err != nil {
118 | w.Write([]byte(err.Error()))
119 | return
120 | }
121 |
122 | // Retrieve it to check if everything is ok.
123 | info, err := ts.UserApplicationRoleConnection(*appID)
124 | if err != nil {
125 | w.Write([]byte(err.Error()))
126 | return
127 | }
128 | jsonMetadata, _ := json.Marshal(info.Metadata)
129 | // And show it to the user.
130 | w.Write([]byte(fmt.Sprintf("Your updated metadata is: %s", jsonMetadata)))
131 | })
132 | http.ListenAndServe(":8010", nil)
133 | }
134 |
--------------------------------------------------------------------------------
/examples/modals/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 | "os/signal"
9 | "strings"
10 |
11 | "github.com/bwmarrin/discordgo"
12 | )
13 |
14 | // Bot parameters
15 | var (
16 | GuildID = flag.String("guild", "", "Test guild ID")
17 | BotToken = flag.String("token", "", "Bot access token")
18 | AppID = flag.String("app", "", "Application ID")
19 | Cleanup = flag.Bool("cleanup", true, "Cleanup of commands")
20 | ResultsChannel = flag.String("results", "", "Channel where send survey results to")
21 | )
22 |
23 | var s *discordgo.Session
24 |
25 | func init() {
26 | flag.Parse()
27 | }
28 |
29 | func init() {
30 | var err error
31 | s, err = discordgo.New("Bot " + *BotToken)
32 | if err != nil {
33 | log.Fatalf("Invalid bot parameters: %v", err)
34 | }
35 | }
36 |
37 | var (
38 | commands = []discordgo.ApplicationCommand{
39 | {
40 | Name: "modals-survey",
41 | Description: "Take a survey about modals",
42 | },
43 | }
44 | commandsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
45 | "modals-survey": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
46 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
47 | Type: discordgo.InteractionResponseModal,
48 | Data: &discordgo.InteractionResponseData{
49 | CustomID: "modals_survey_" + i.Interaction.Member.User.ID,
50 | Title: "Modals survey",
51 | Components: []discordgo.MessageComponent{
52 | discordgo.ActionsRow{
53 | Components: []discordgo.MessageComponent{
54 | discordgo.TextInput{
55 | CustomID: "opinion",
56 | Label: "What is your opinion on them?",
57 | Style: discordgo.TextInputShort,
58 | Placeholder: "Don't be shy, share your opinion with us",
59 | Required: true,
60 | MaxLength: 300,
61 | MinLength: 10,
62 | },
63 | },
64 | },
65 | discordgo.ActionsRow{
66 | Components: []discordgo.MessageComponent{
67 | discordgo.TextInput{
68 | CustomID: "suggestions",
69 | Label: "What would you suggest to improve them?",
70 | Style: discordgo.TextInputParagraph,
71 | Required: false,
72 | MaxLength: 2000,
73 | },
74 | },
75 | },
76 | },
77 | },
78 | })
79 | if err != nil {
80 | panic(err)
81 | }
82 | },
83 | }
84 | )
85 |
86 | func main() {
87 | s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
88 | log.Println("Bot is up!")
89 | })
90 |
91 | s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
92 | switch i.Type {
93 | case discordgo.InteractionApplicationCommand:
94 | if h, ok := commandsHandlers[i.ApplicationCommandData().Name]; ok {
95 | h(s, i)
96 | }
97 | case discordgo.InteractionModalSubmit:
98 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
99 | Type: discordgo.InteractionResponseChannelMessageWithSource,
100 | Data: &discordgo.InteractionResponseData{
101 | Content: "Thank you for taking your time to fill this survey",
102 | Flags: discordgo.MessageFlagsEphemeral,
103 | },
104 | })
105 | if err != nil {
106 | panic(err)
107 | }
108 | data := i.ModalSubmitData()
109 |
110 | if !strings.HasPrefix(data.CustomID, "modals_survey") {
111 | return
112 | }
113 |
114 | userid := strings.Split(data.CustomID, "_")[2]
115 | _, err = s.ChannelMessageSend(*ResultsChannel, fmt.Sprintf(
116 | "Feedback received. From <@%s>\n\n**Opinion**:\n%s\n\n**Suggestions**:\n%s",
117 | userid,
118 | data.Components[0].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value,
119 | data.Components[1].(*discordgo.ActionsRow).Components[0].(*discordgo.TextInput).Value,
120 | ))
121 | if err != nil {
122 | panic(err)
123 | }
124 | }
125 | })
126 |
127 | cmdIDs := make(map[string]string, len(commands))
128 |
129 | for _, cmd := range commands {
130 | rcmd, err := s.ApplicationCommandCreate(*AppID, *GuildID, &cmd)
131 | if err != nil {
132 | log.Fatalf("Cannot create slash command %q: %v", cmd.Name, err)
133 | }
134 |
135 | cmdIDs[rcmd.ID] = rcmd.Name
136 | }
137 |
138 | err := s.Open()
139 | if err != nil {
140 | log.Fatalf("Cannot open the session: %v", err)
141 | }
142 | defer s.Close()
143 |
144 | stop := make(chan os.Signal, 1)
145 | signal.Notify(stop, os.Interrupt)
146 | <-stop
147 | log.Println("Graceful shutdown")
148 |
149 | if !*Cleanup {
150 | return
151 | }
152 |
153 | for id, name := range cmdIDs {
154 | err := s.ApplicationCommandDelete(*AppID, *GuildID, id)
155 | if err != nil {
156 | log.Fatalf("Cannot delete slash command %q: %v", name, err)
157 | }
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------
/examples/linked_roles/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
2 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
3 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
4 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
5 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
6 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
7 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
8 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
9 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
10 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
11 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
12 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
13 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
14 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
15 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
16 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
17 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
18 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
19 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
20 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
21 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
22 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
23 | golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
24 | golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
25 | golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
26 | golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
27 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
28 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
29 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
30 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
31 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
32 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
33 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
34 | golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
35 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
36 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
37 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
38 | golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
39 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
40 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
41 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
42 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
43 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
44 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
45 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
46 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
47 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
48 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
49 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
50 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
51 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
52 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
53 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
54 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
55 |
--------------------------------------------------------------------------------
/oauth2.go:
--------------------------------------------------------------------------------
1 | // Discordgo - Discord bindings for Go
2 | // Available at https://github.com/bwmarrin/discordgo
3 |
4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved.
5 | // Use of this source code is governed by a BSD-style
6 | // license that can be found in the LICENSE file.
7 |
8 | // This file contains functions related to Discord OAuth2 endpoints
9 |
10 | package discordgo
11 |
12 | // ------------------------------------------------------------------------------------------------
13 | // Code specific to Discord OAuth2 Applications
14 | // ------------------------------------------------------------------------------------------------
15 |
16 | // The MembershipState represents whether the user is in the team or has been invited into it
17 | type MembershipState int
18 |
19 | // Constants for the different stages of the MembershipState
20 | const (
21 | MembershipStateInvited MembershipState = 1
22 | MembershipStateAccepted MembershipState = 2
23 | )
24 |
25 | // A TeamMember struct stores values for a single Team Member, extending the normal User data - note that the user field is partial
26 | type TeamMember struct {
27 | User *User `json:"user"`
28 | TeamID string `json:"team_id"`
29 | MembershipState MembershipState `json:"membership_state"`
30 | Permissions []string `json:"permissions"`
31 | }
32 |
33 | // A Team struct stores the members of a Discord Developer Team as well as some metadata about it
34 | type Team struct {
35 | ID string `json:"id"`
36 | Name string `json:"name"`
37 | Description string `json:"description"`
38 | Icon string `json:"icon"`
39 | OwnerID string `json:"owner_user_id"`
40 | Members []*TeamMember `json:"members"`
41 | }
42 |
43 | // Application returns an Application structure of a specific Application
44 | // appID : The ID of an Application
45 | func (s *Session) Application(appID string) (st *Application, err error) {
46 |
47 | body, err := s.RequestWithBucketID("GET", EndpointOAuth2Application(appID), nil, EndpointOAuth2Application(""))
48 | if err != nil {
49 | return
50 | }
51 |
52 | err = unmarshal(body, &st)
53 | return
54 | }
55 |
56 | // Applications returns all applications for the authenticated user
57 | func (s *Session) Applications() (st []*Application, err error) {
58 |
59 | body, err := s.RequestWithBucketID("GET", EndpointOAuth2Applications, nil, EndpointOAuth2Applications)
60 | if err != nil {
61 | return
62 | }
63 |
64 | err = unmarshal(body, &st)
65 | return
66 | }
67 |
68 | // ApplicationCreate creates a new Application
69 | // name : Name of Application / Bot
70 | // uris : Redirect URIs (Not required)
71 | func (s *Session) ApplicationCreate(ap *Application) (st *Application, err error) {
72 |
73 | data := struct {
74 | Name string `json:"name"`
75 | Description string `json:"description"`
76 | }{ap.Name, ap.Description}
77 |
78 | body, err := s.RequestWithBucketID("POST", EndpointOAuth2Applications, data, EndpointOAuth2Applications)
79 | if err != nil {
80 | return
81 | }
82 |
83 | err = unmarshal(body, &st)
84 | return
85 | }
86 |
87 | // ApplicationUpdate updates an existing Application
88 | // var : desc
89 | func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Application, err error) {
90 |
91 | data := struct {
92 | Name string `json:"name"`
93 | Description string `json:"description"`
94 | }{ap.Name, ap.Description}
95 |
96 | body, err := s.RequestWithBucketID("PUT", EndpointOAuth2Application(appID), data, EndpointOAuth2Application(""))
97 | if err != nil {
98 | return
99 | }
100 |
101 | err = unmarshal(body, &st)
102 | return
103 | }
104 |
105 | // ApplicationDelete deletes an existing Application
106 | // appID : The ID of an Application
107 | func (s *Session) ApplicationDelete(appID string) (err error) {
108 |
109 | _, err = s.RequestWithBucketID("DELETE", EndpointOAuth2Application(appID), nil, EndpointOAuth2Application(""))
110 | if err != nil {
111 | return
112 | }
113 |
114 | return
115 | }
116 |
117 | // Asset struct stores values for an asset of an application
118 | type Asset struct {
119 | Type int `json:"type"`
120 | ID string `json:"id"`
121 | Name string `json:"name"`
122 | }
123 |
124 | // ApplicationAssets returns an application's assets
125 | func (s *Session) ApplicationAssets(appID string) (ass []*Asset, err error) {
126 |
127 | body, err := s.RequestWithBucketID("GET", EndpointOAuth2ApplicationAssets(appID), nil, EndpointOAuth2ApplicationAssets(""))
128 | if err != nil {
129 | return
130 | }
131 |
132 | err = unmarshal(body, &ass)
133 | return
134 | }
135 |
136 | // ------------------------------------------------------------------------------------------------
137 | // Code specific to Discord OAuth2 Application Bots
138 | // ------------------------------------------------------------------------------------------------
139 |
140 | // ApplicationBotCreate creates an Application Bot Account
141 | //
142 | // appID : The ID of an Application
143 | //
144 | // NOTE: func name may change, if I can think up something better.
145 | func (s *Session) ApplicationBotCreate(appID string) (st *User, err error) {
146 |
147 | body, err := s.RequestWithBucketID("POST", EndpointOAuth2ApplicationsBot(appID), nil, EndpointOAuth2ApplicationsBot(""))
148 | if err != nil {
149 | return
150 | }
151 |
152 | err = unmarshal(body, &st)
153 | return
154 | }
155 |
--------------------------------------------------------------------------------
/docs/img/discordgo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
46 |
--------------------------------------------------------------------------------
/ratelimit.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "math"
5 | "net/http"
6 | "strconv"
7 | "strings"
8 | "sync"
9 | "sync/atomic"
10 | "time"
11 | )
12 |
13 | // customRateLimit holds information for defining a custom rate limit
14 | type customRateLimit struct {
15 | suffix string
16 | requests int
17 | reset time.Duration
18 | }
19 |
20 | // RateLimiter holds all ratelimit buckets
21 | type RateLimiter struct {
22 | sync.Mutex
23 | global *int64
24 | buckets map[string]*Bucket
25 | globalRateLimit time.Duration
26 | customRateLimits []*customRateLimit
27 | }
28 |
29 | // NewRatelimiter returns a new RateLimiter
30 | func NewRatelimiter() *RateLimiter {
31 |
32 | return &RateLimiter{
33 | buckets: make(map[string]*Bucket),
34 | global: new(int64),
35 | customRateLimits: []*customRateLimit{
36 | {
37 | suffix: "//reactions//",
38 | requests: 1,
39 | reset: 200 * time.Millisecond,
40 | },
41 | },
42 | }
43 | }
44 |
45 | // GetBucket retrieves or creates a bucket
46 | func (r *RateLimiter) GetBucket(key string) *Bucket {
47 | r.Lock()
48 | defer r.Unlock()
49 |
50 | if bucket, ok := r.buckets[key]; ok {
51 | return bucket
52 | }
53 |
54 | b := &Bucket{
55 | Remaining: 1,
56 | Key: key,
57 | global: r.global,
58 | }
59 |
60 | // Check if there is a custom ratelimit set for this bucket ID.
61 | for _, rl := range r.customRateLimits {
62 | if strings.HasSuffix(b.Key, rl.suffix) {
63 | b.customRateLimit = rl
64 | break
65 | }
66 | }
67 |
68 | r.buckets[key] = b
69 | return b
70 | }
71 |
72 | // GetWaitTime returns the duration you should wait for a Bucket
73 | func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration {
74 | // If we ran out of calls and the reset time is still ahead of us
75 | // then we need to take it easy and relax a little
76 | if b.Remaining < minRemaining && b.reset.After(time.Now()) {
77 | return b.reset.Sub(time.Now())
78 | }
79 |
80 | // Check for global ratelimits
81 | sleepTo := time.Unix(0, atomic.LoadInt64(r.global))
82 | if now := time.Now(); now.Before(sleepTo) {
83 | return sleepTo.Sub(now)
84 | }
85 |
86 | return 0
87 | }
88 |
89 | // LockBucket Locks until a request can be made
90 | func (r *RateLimiter) LockBucket(bucketID string) *Bucket {
91 | return r.LockBucketObject(r.GetBucket(bucketID))
92 | }
93 |
94 | // LockBucketObject Locks an already resolved bucket until a request can be made
95 | func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket {
96 | b.Lock()
97 |
98 | if wait := r.GetWaitTime(b, 1); wait > 0 {
99 | time.Sleep(wait)
100 | }
101 |
102 | b.Remaining--
103 | return b
104 | }
105 |
106 | // Bucket represents a ratelimit bucket, each bucket gets ratelimited individually (-global ratelimits)
107 | type Bucket struct {
108 | sync.Mutex
109 | Key string
110 | Remaining int
111 | limit int
112 | reset time.Time
113 | global *int64
114 |
115 | lastReset time.Time
116 | customRateLimit *customRateLimit
117 | Userdata interface{}
118 | }
119 |
120 | // Release unlocks the bucket and reads the headers to update the buckets ratelimit info
121 | // and locks up the whole thing in case if there's a global ratelimit.
122 | func (b *Bucket) Release(headers http.Header) error {
123 | defer b.Unlock()
124 |
125 | // Check if the bucket uses a custom ratelimiter
126 | if rl := b.customRateLimit; rl != nil {
127 | if time.Now().Sub(b.lastReset) >= rl.reset {
128 | b.Remaining = rl.requests - 1
129 | b.lastReset = time.Now()
130 | }
131 | if b.Remaining < 1 {
132 | b.reset = time.Now().Add(rl.reset)
133 | }
134 | return nil
135 | }
136 |
137 | if headers == nil {
138 | return nil
139 | }
140 |
141 | remaining := headers.Get("X-RateLimit-Remaining")
142 | reset := headers.Get("X-RateLimit-Reset")
143 | global := headers.Get("X-RateLimit-Global")
144 | resetAfter := headers.Get("X-RateLimit-Reset-After")
145 |
146 | // Update global and per bucket reset time if the proper headers are available
147 | // If global is set, then it will block all buckets until after Retry-After
148 | // If Retry-After without global is provided it will use that for the new reset
149 | // time since it's more accurate than X-RateLimit-Reset.
150 | // If Retry-After after is not proided, it will update the reset time from X-RateLimit-Reset
151 | if resetAfter != "" {
152 | parsedAfter, err := strconv.ParseFloat(resetAfter, 64)
153 | if err != nil {
154 | return err
155 | }
156 |
157 | whole, frac := math.Modf(parsedAfter)
158 | resetAt := time.Now().Add(time.Duration(whole) * time.Second).Add(time.Duration(frac*1000) * time.Millisecond)
159 |
160 | // Lock either this single bucket or all buckets
161 | if global != "" {
162 | atomic.StoreInt64(b.global, resetAt.UnixNano())
163 | } else {
164 | b.reset = resetAt
165 | }
166 | } else if reset != "" {
167 | // Calculate the reset time by using the date header returned from discord
168 | discordTime, err := http.ParseTime(headers.Get("Date"))
169 | if err != nil {
170 | return err
171 | }
172 |
173 | unix, err := strconv.ParseFloat(reset, 64)
174 | if err != nil {
175 | return err
176 | }
177 |
178 | // Calculate the time until reset and add it to the current local time
179 | // some extra time is added because without it i still encountered 429's.
180 | // The added amount is the lowest amount that gave no 429's
181 | // in 1k requests
182 | whole, frac := math.Modf(unix)
183 | delta := time.Unix(int64(whole), 0).Add(time.Duration(frac*1000)*time.Millisecond).Sub(discordTime) + time.Millisecond*250
184 | b.reset = time.Now().Add(delta)
185 | }
186 |
187 | // Udpate remaining if header is present
188 | if remaining != "" {
189 | parsedRemaining, err := strconv.ParseInt(remaining, 10, 32)
190 | if err != nil {
191 | return err
192 | }
193 | b.Remaining = int(parsedRemaining)
194 | }
195 |
196 | return nil
197 | }
198 |
--------------------------------------------------------------------------------
/restapi_test.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | //////////////////////////////////////////////////////////////////////////////
9 | /////////////////////////////////////////////////////////////// START OF TESTS
10 |
11 | // TestChannelMessageSend tests the ChannelMessageSend() function. This should not return an error.
12 | func TestChannelMessageSend(t *testing.T) {
13 |
14 | if envChannel == "" {
15 | t.Skip("Skipping, DG_CHANNEL not set.")
16 | }
17 |
18 | if dg == nil {
19 | t.Skip("Skipping, dg not set.")
20 | }
21 |
22 | _, err := dg.ChannelMessageSend(envChannel, "Running REST API Tests!")
23 | if err != nil {
24 | t.Errorf("ChannelMessageSend returned error: %+v", err)
25 | }
26 | }
27 |
28 | /*
29 | // removed for now, only works on BOT accounts now
30 | func TestUserAvatar(t *testing.T) {
31 |
32 | if dg == nil {
33 | t.Skip("Cannot TestUserAvatar, dg not set.")
34 | }
35 |
36 | u, err := dg.User("@me")
37 | if err != nil {
38 | t.Error("error fetching @me user,", err)
39 | }
40 |
41 | a, err := dg.UserAvatar(u.ID)
42 | if err != nil {
43 | if err.Error() == `HTTP 404 NOT FOUND, {"code": 0, "message": "404: Not Found"}` {
44 | t.Skip("Skipped, @me doesn't have an Avatar")
45 | }
46 | t.Errorf(err.Error())
47 | }
48 |
49 | if a == nil {
50 | t.Errorf("a == nil, should be image.Image")
51 | }
52 | }
53 | */
54 |
55 | /* Running this causes an error due to 2/hour rate limit on username changes
56 | func TestUserUpdate(t *testing.T) {
57 | if dg == nil {
58 | t.Skip("Cannot test logout, dg not set.")
59 | }
60 |
61 | u, err := dg.User("@me")
62 | if err != nil {
63 | t.Errorf(err.Error())
64 | }
65 |
66 | s, err := dg.UserUpdate(envEmail, envPassword, "testname", u.Avatar, "")
67 | if err != nil {
68 | t.Error(err.Error())
69 | }
70 | if s.Username != "testname" {
71 | t.Error("Username != testname")
72 | }
73 | s, err = dg.UserUpdate(envEmail, envPassword, u.Username, u.Avatar, "")
74 | if err != nil {
75 | t.Error(err.Error())
76 | }
77 | if s.Username != u.Username {
78 | t.Error("Username != " + u.Username)
79 | }
80 | }
81 | */
82 |
83 | //func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) {
84 |
85 | func TestUserChannelCreate(t *testing.T) {
86 | if dg == nil {
87 | t.Skip("Cannot TestUserChannelCreate, dg not set.")
88 | }
89 |
90 | if envAdmin == "" {
91 | t.Skip("Skipped, DG_ADMIN not set.")
92 | }
93 |
94 | _, err := dg.UserChannelCreate(envAdmin)
95 | if err != nil {
96 | t.Errorf(err.Error())
97 | }
98 |
99 | // TODO make sure the channel was added
100 | }
101 |
102 | func TestUserGuilds(t *testing.T) {
103 | if dg == nil {
104 | t.Skip("Cannot TestUserGuilds, dg not set.")
105 | }
106 |
107 | _, err := dg.UserGuilds(10, "", "")
108 | if err != nil {
109 | t.Errorf(err.Error())
110 | }
111 | }
112 |
113 | func TestGateway(t *testing.T) {
114 |
115 | if dg == nil {
116 | t.Skip("Skipping, dg not set.")
117 | }
118 | _, err := dg.Gateway()
119 | if err != nil {
120 | t.Errorf("Gateway() returned error: %+v", err)
121 | }
122 | }
123 |
124 | func TestGatewayBot(t *testing.T) {
125 |
126 | if dgBot == nil {
127 | t.Skip("Skipping, dgBot not set.")
128 | }
129 | _, err := dgBot.GatewayBot()
130 | if err != nil {
131 | t.Errorf("GatewayBot() returned error: %+v", err)
132 | }
133 | }
134 |
135 | func TestVoiceRegions(t *testing.T) {
136 |
137 | if dg == nil {
138 | t.Skip("Skipping, dg not set.")
139 | }
140 |
141 | _, err := dg.VoiceRegions()
142 | if err != nil {
143 | t.Errorf("VoiceRegions() returned error: %+v", err)
144 | }
145 | }
146 | func TestGuildRoles(t *testing.T) {
147 |
148 | if envGuild == "" {
149 | t.Skip("Skipping, DG_GUILD not set.")
150 | }
151 |
152 | if dg == nil {
153 | t.Skip("Skipping, dg not set.")
154 | }
155 |
156 | _, err := dg.GuildRoles(envGuild)
157 | if err != nil {
158 | t.Errorf("GuildRoles(envGuild) returned error: %+v", err)
159 | }
160 |
161 | }
162 |
163 | func TestGuildMemberNickname(t *testing.T) {
164 |
165 | if envGuild == "" {
166 | t.Skip("Skipping, DG_GUILD not set.")
167 | }
168 |
169 | if dg == nil {
170 | t.Skip("Skipping, dg not set.")
171 | }
172 |
173 | err := dg.GuildMemberNickname(envGuild, "@me/nick", "B1nzyRocks")
174 | if err != nil {
175 | t.Errorf("GuildNickname returned error: %+v", err)
176 | }
177 | }
178 |
179 | // TestChannelMessageSend2 tests the ChannelMessageSend() function. This should not return an error.
180 | func TestChannelMessageSend2(t *testing.T) {
181 |
182 | if envChannel == "" {
183 | t.Skip("Skipping, DG_CHANNEL not set.")
184 | }
185 |
186 | if dg == nil {
187 | t.Skip("Skipping, dg not set.")
188 | }
189 |
190 | _, err := dg.ChannelMessageSend(envChannel, "All done running REST API Tests!")
191 | if err != nil {
192 | t.Errorf("ChannelMessageSend returned error: %+v", err)
193 | }
194 | }
195 |
196 | // TestGuildPruneCount tests GuildPruneCount() function. This should not return an error.
197 | func TestGuildPruneCount(t *testing.T) {
198 |
199 | if envGuild == "" {
200 | t.Skip("Skipping, DG_GUILD not set.")
201 | }
202 |
203 | if dg == nil {
204 | t.Skip("Skipping, dg not set.")
205 | }
206 |
207 | _, err := dg.GuildPruneCount(envGuild, 1)
208 | if err != nil {
209 | t.Errorf("GuildPruneCount returned error: %+v", err)
210 | }
211 | }
212 |
213 | /*
214 | // TestGuildPrune tests GuildPrune() function. This should not return an error.
215 | func TestGuildPrune(t *testing.T) {
216 |
217 | if envGuild == "" {
218 | t.Skip("Skipping, DG_GUILD not set.")
219 | }
220 |
221 | if dg == nil {
222 | t.Skip("Skipping, dg not set.")
223 | }
224 |
225 | _, err := dg.GuildPrune(envGuild, 1)
226 | if err != nil {
227 | t.Errorf("GuildPrune returned error: %+v", err)
228 | }
229 | }
230 | */
231 |
232 | func Test_unmarshal(t *testing.T) {
233 | err := unmarshal([]byte{}, &struct{}{})
234 | if !errors.Is(err, ErrJSONUnmarshal) {
235 | t.Errorf("Unexpected error type: %T", err)
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/examples/airhorn/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/binary"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "os"
9 | "os/signal"
10 | "strings"
11 | "syscall"
12 | "time"
13 |
14 | "github.com/bwmarrin/discordgo"
15 | )
16 |
17 | func init() {
18 | flag.StringVar(&token, "t", "", "Bot Token")
19 | flag.Parse()
20 | }
21 |
22 | var token string
23 | var buffer = make([][]byte, 0)
24 |
25 | func main() {
26 |
27 | if token == "" {
28 | fmt.Println("No token provided. Please run: airhorn -t ")
29 | return
30 | }
31 |
32 | // Load the sound file.
33 | err := loadSound()
34 | if err != nil {
35 | fmt.Println("Error loading sound: ", err)
36 | fmt.Println("Please copy $GOPATH/src/github.com/bwmarrin/examples/airhorn/airhorn.dca to this directory.")
37 | return
38 | }
39 |
40 | // Create a new Discord session using the provided bot token.
41 | dg, err := discordgo.New("Bot " + token)
42 | if err != nil {
43 | fmt.Println("Error creating Discord session: ", err)
44 | return
45 | }
46 |
47 | // Register ready as a callback for the ready events.
48 | dg.AddHandler(ready)
49 |
50 | // Register messageCreate as a callback for the messageCreate events.
51 | dg.AddHandler(messageCreate)
52 |
53 | // Register guildCreate as a callback for the guildCreate events.
54 | dg.AddHandler(guildCreate)
55 |
56 | // We need information about guilds (which includes their channels),
57 | // messages and voice states.
58 | dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages | discordgo.IntentsGuildVoiceStates
59 |
60 | // Open the websocket and begin listening.
61 | err = dg.Open()
62 | if err != nil {
63 | fmt.Println("Error opening Discord session: ", err)
64 | }
65 |
66 | // Wait here until CTRL-C or other term signal is received.
67 | fmt.Println("Airhorn is now running. Press CTRL-C to exit.")
68 | sc := make(chan os.Signal, 1)
69 | signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
70 | <-sc
71 |
72 | // Cleanly close down the Discord session.
73 | dg.Close()
74 | }
75 |
76 | // This function will be called (due to AddHandler above) when the bot receives
77 | // the "ready" event from Discord.
78 | func ready(s *discordgo.Session, event *discordgo.Ready) {
79 |
80 | // Set the playing status.
81 | s.UpdateGameStatus(0, "!airhorn")
82 | }
83 |
84 | // This function will be called (due to AddHandler above) every time a new
85 | // message is created on any channel that the autenticated bot has access to.
86 | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
87 |
88 | // Ignore all messages created by the bot itself
89 | // This isn't required in this specific example but it's a good practice.
90 | if m.Author.ID == s.State.User.ID {
91 | return
92 | }
93 |
94 | // check if the message is "!airhorn"
95 | if strings.HasPrefix(m.Content, "!airhorn") {
96 |
97 | // Find the channel that the message came from.
98 | c, err := s.State.Channel(m.ChannelID)
99 | if err != nil {
100 | // Could not find channel.
101 | return
102 | }
103 |
104 | // Find the guild for that channel.
105 | g, err := s.State.Guild(c.GuildID)
106 | if err != nil {
107 | // Could not find guild.
108 | return
109 | }
110 |
111 | // Look for the message sender in that guild's current voice states.
112 | for _, vs := range g.VoiceStates {
113 | if vs.UserID == m.Author.ID {
114 | err = playSound(s, g.ID, vs.ChannelID)
115 | if err != nil {
116 | fmt.Println("Error playing sound:", err)
117 | }
118 |
119 | return
120 | }
121 | }
122 | }
123 | }
124 |
125 | // This function will be called (due to AddHandler above) every time a new
126 | // guild is joined.
127 | func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) {
128 |
129 | if event.Guild.Unavailable {
130 | return
131 | }
132 |
133 | for _, channel := range event.Guild.Channels {
134 | if channel.ID == event.Guild.ID {
135 | _, _ = s.ChannelMessageSend(channel.ID, "Airhorn is ready! Type !airhorn while in a voice channel to play a sound.")
136 | return
137 | }
138 | }
139 | }
140 |
141 | // loadSound attempts to load an encoded sound file from disk.
142 | func loadSound() error {
143 |
144 | file, err := os.Open("airhorn.dca")
145 | if err != nil {
146 | fmt.Println("Error opening dca file :", err)
147 | return err
148 | }
149 |
150 | var opuslen int16
151 |
152 | for {
153 | // Read opus frame length from dca file.
154 | err = binary.Read(file, binary.LittleEndian, &opuslen)
155 |
156 | // If this is the end of the file, just return.
157 | if err == io.EOF || err == io.ErrUnexpectedEOF {
158 | err := file.Close()
159 | if err != nil {
160 | return err
161 | }
162 | return nil
163 | }
164 |
165 | if err != nil {
166 | fmt.Println("Error reading from dca file :", err)
167 | return err
168 | }
169 |
170 | // Read encoded pcm from dca file.
171 | InBuf := make([]byte, opuslen)
172 | err = binary.Read(file, binary.LittleEndian, &InBuf)
173 |
174 | // Should not be any end of file errors
175 | if err != nil {
176 | fmt.Println("Error reading from dca file :", err)
177 | return err
178 | }
179 |
180 | // Append encoded pcm data to the buffer.
181 | buffer = append(buffer, InBuf)
182 | }
183 | }
184 |
185 | // playSound plays the current buffer to the provided channel.
186 | func playSound(s *discordgo.Session, guildID, channelID string) (err error) {
187 |
188 | // Join the provided voice channel.
189 | vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true)
190 | if err != nil {
191 | return err
192 | }
193 |
194 | // Sleep for a specified amount of time before playing the sound
195 | time.Sleep(250 * time.Millisecond)
196 |
197 | // Start speaking.
198 | vc.Speaking(true)
199 |
200 | // Send the buffer data.
201 | for _, buff := range buffer {
202 | vc.OpusSend <- buff
203 | }
204 |
205 | // Stop speaking
206 | vc.Speaking(false)
207 |
208 | // Sleep for a specificed amount of time before ending.
209 | time.Sleep(250 * time.Millisecond)
210 |
211 | // Disconnect from the provided voice channel.
212 | vc.Disconnect()
213 |
214 | return nil
215 | }
216 |
--------------------------------------------------------------------------------
/examples/context_menus/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 | "os/signal"
9 | "strings"
10 |
11 | "github.com/bwmarrin/discordgo"
12 | )
13 |
14 | // Bot parameters
15 | var (
16 | GuildID = flag.String("guild", "", "Test guild ID")
17 | BotToken = flag.String("token", "", "Bot access token")
18 | AppID = flag.String("app", "", "Application ID")
19 | Cleanup = flag.Bool("cleanup", true, "Cleanup of commands")
20 | )
21 |
22 | var s *discordgo.Session
23 |
24 | func init() { flag.Parse() }
25 |
26 | func init() {
27 | var err error
28 | s, err = discordgo.New("Bot " + *BotToken)
29 | if err != nil {
30 | log.Fatalf("Invalid bot parameters: %v", err)
31 | }
32 | }
33 |
34 | func searchLink(message, format, sep string) string {
35 | return fmt.Sprintf(format, strings.Join(
36 | strings.Split(
37 | message,
38 | " ",
39 | ),
40 | sep,
41 | ))
42 | }
43 |
44 | var (
45 | commands = []discordgo.ApplicationCommand{
46 | {
47 | Name: "rickroll-em",
48 | Type: discordgo.UserApplicationCommand,
49 | },
50 | {
51 | Name: "google-it",
52 | Type: discordgo.MessageApplicationCommand,
53 | },
54 | {
55 | Name: "stackoverflow-it",
56 | Type: discordgo.MessageApplicationCommand,
57 | },
58 | {
59 | Name: "godoc-it",
60 | Type: discordgo.MessageApplicationCommand,
61 | },
62 | {
63 | Name: "discordjs-it",
64 | Type: discordgo.MessageApplicationCommand,
65 | },
66 | {
67 | Name: "discordpy-it",
68 | Type: discordgo.MessageApplicationCommand,
69 | },
70 | }
71 | commandsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
72 | "rickroll-em": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
73 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
74 | Type: discordgo.InteractionResponseChannelMessageWithSource,
75 | Data: &discordgo.InteractionResponseData{
76 | Content: "Operation rickroll has begun",
77 | Flags: discordgo.MessageFlagsEphemeral,
78 | },
79 | })
80 | if err != nil {
81 | panic(err)
82 | }
83 |
84 | ch, err := s.UserChannelCreate(
85 | i.ApplicationCommandData().TargetID,
86 | )
87 | if err != nil {
88 | _, err = s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
89 | Content: fmt.Sprintf("Mission failed. Cannot send a message to this user: %q", err.Error()),
90 | Flags: discordgo.MessageFlagsEphemeral,
91 | })
92 | if err != nil {
93 | panic(err)
94 | }
95 | }
96 | _, err = s.ChannelMessageSend(
97 | ch.ID,
98 | fmt.Sprintf("%s sent you this: https://youtu.be/dQw4w9WgXcQ", i.Member.Mention()),
99 | )
100 | if err != nil {
101 | panic(err)
102 | }
103 | },
104 | "google-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
105 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
106 | Type: discordgo.InteractionResponseChannelMessageWithSource,
107 | Data: &discordgo.InteractionResponseData{
108 | Content: searchLink(
109 | i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
110 | "https://google.com/search?q=%s", "+"),
111 | Flags: discordgo.MessageFlagsEphemeral,
112 | },
113 | })
114 | if err != nil {
115 | panic(err)
116 | }
117 | },
118 | "stackoverflow-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
119 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
120 | Type: discordgo.InteractionResponseChannelMessageWithSource,
121 | Data: &discordgo.InteractionResponseData{
122 | Content: searchLink(
123 | i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
124 | "https://stackoverflow.com/search?q=%s", "+"),
125 | Flags: discordgo.MessageFlagsEphemeral,
126 | },
127 | })
128 | if err != nil {
129 | panic(err)
130 | }
131 | },
132 | "godoc-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
133 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
134 | Type: discordgo.InteractionResponseChannelMessageWithSource,
135 | Data: &discordgo.InteractionResponseData{
136 | Content: searchLink(
137 | i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
138 | "https://pkg.go.dev/search?q=%s", "+"),
139 | Flags: discordgo.MessageFlagsEphemeral,
140 | },
141 | })
142 | if err != nil {
143 | panic(err)
144 | }
145 | },
146 | "discordjs-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
147 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
148 | Type: discordgo.InteractionResponseChannelMessageWithSource,
149 | Data: &discordgo.InteractionResponseData{
150 | Content: searchLink(
151 | i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
152 | "https://discord.js.org/#/docs/main/stable/search?query=%s", "+"),
153 | Flags: discordgo.MessageFlagsEphemeral,
154 | },
155 | })
156 | if err != nil {
157 | panic(err)
158 | }
159 | },
160 | "discordpy-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
161 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
162 | Type: discordgo.InteractionResponseChannelMessageWithSource,
163 | Data: &discordgo.InteractionResponseData{
164 | Content: searchLink(
165 | i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
166 | "https://discordpy.readthedocs.io/en/stable/search.html?q=%s", "+"),
167 | Flags: discordgo.MessageFlagsEphemeral,
168 | },
169 | })
170 | if err != nil {
171 | panic(err)
172 | }
173 | },
174 | }
175 | )
176 |
177 | func main() {
178 | s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
179 | log.Println("Bot is up!")
180 | })
181 |
182 | s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
183 | if h, ok := commandsHandlers[i.ApplicationCommandData().Name]; ok {
184 | h(s, i)
185 | }
186 | })
187 |
188 | cmdIDs := make(map[string]string, len(commands))
189 |
190 | for _, cmd := range commands {
191 | rcmd, err := s.ApplicationCommandCreate(*AppID, *GuildID, &cmd)
192 | if err != nil {
193 | log.Fatalf("Cannot create slash command %q: %v", cmd.Name, err)
194 | }
195 |
196 | cmdIDs[rcmd.ID] = rcmd.Name
197 |
198 | }
199 |
200 | err := s.Open()
201 | if err != nil {
202 | log.Fatalf("Cannot open the session: %v", err)
203 | }
204 | defer s.Close()
205 |
206 | stop := make(chan os.Signal, 1)
207 | signal.Notify(stop, os.Interrupt)
208 | <-stop
209 | log.Println("Graceful shutdown")
210 |
211 | if !*Cleanup {
212 | return
213 | }
214 |
215 | for id, name := range cmdIDs {
216 | err := s.ApplicationCommandDelete(*AppID, *GuildID, id)
217 | if err != nil {
218 | log.Fatalf("Cannot delete slash command %q: %v", name, err)
219 | }
220 | }
221 |
222 | }
223 |
--------------------------------------------------------------------------------
/examples/voice_receive/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bwmarrin/discordgo v0.21.1 h1:UI2PWwzvn5IFuscYcDc6QB/duhs9MUIjQ4HclcIZisc=
2 | github.com/bwmarrin/discordgo v0.21.1/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
3 | github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
7 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
8 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
9 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
10 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
11 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
12 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
13 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
14 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
16 | github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
17 | github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
18 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
19 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
20 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
21 | github.com/pion/datachannel v1.4.19/go.mod h1:JzKF/zzeWgkOYwQ+KFb8JzbrUt8s63um+Qunu8VqTyw=
22 | github.com/pion/dtls/v2 v2.0.2/go.mod h1:27PEO3MDdaCfo21heT59/vsdmZc0zMt9wQPcSlLu/1I=
23 | github.com/pion/ice/v2 v2.0.0-rc.8/go.mod h1:BH71LWfapOO69LRT1b+Rg9/be1qQPjSZT9Pm3qQRBdY=
24 | github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
25 | github.com/pion/mdns v0.0.4/go.mod h1:R1sL0p50l42S5lJs91oNdUL58nm0QHrhxnSegr++qC0=
26 | github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
27 | github.com/pion/randutil v0.0.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
28 | github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
29 | github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
30 | github.com/pion/rtcp v1.2.3/go.mod h1:zGhIv0RPRF0Z1Wiij22pUt5W/c9fevqSzT4jje/oK7I=
31 | github.com/pion/rtp v1.6.0 h1:4Ssnl/T5W2LzxHj9ssYpGVEQh3YYhQFNVmSWO88MMwk=
32 | github.com/pion/rtp v1.6.0/go.mod h1:QgfogHsMBVE/RFNno467U/KBqfUywEH+HK+0rtnwsdI=
33 | github.com/pion/sctp v1.7.8/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
34 | github.com/pion/sdp/v2 v2.4.0/go.mod h1:L2LxrOpSTJbAns244vfPChbciR/ReU1KWfG04OpkR7E=
35 | github.com/pion/srtp v1.5.0/go.mod h1:B+QgX5xPeQTNc1CJStJPHzOlHK66ViMDWTT0HZTCkcA=
36 | github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
37 | github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
38 | github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
39 | github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
40 | github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
41 | github.com/pion/turn/v2 v2.0.4/go.mod h1:1812p4DcGVbYVBTiraUmP50XoKye++AMkbfp+N27mog=
42 | github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
43 | github.com/pion/webrtc/v3 v3.0.0-20200721060053-ca3cc9d940bc h1:MEXLzhmwidVyYVKJ+Z+b+eumAc3Pz8yrms26RPHF4J4=
44 | github.com/pion/webrtc/v3 v3.0.0-20200721060053-ca3cc9d940bc/go.mod h1:g1cyQ0CajooTxiCtJOcH+P5S2uC5cVwUhV3Rj4v0uUg=
45 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
46 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
47 | github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
48 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
49 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
50 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
51 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
52 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
53 | golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
54 | golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
55 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
56 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
57 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
58 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
59 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
60 | golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
61 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
62 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
63 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
64 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
65 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
66 | golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
67 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
68 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
69 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
70 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
71 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
72 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
73 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
74 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
75 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
76 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
77 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
78 |
--------------------------------------------------------------------------------
/event.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | // EventHandler is an interface for Discord events.
4 | type EventHandler interface {
5 | // Type returns the type of event this handler belongs to.
6 | Type() string
7 |
8 | // Handle is called whenever an event of Type() happens.
9 | // It is the receivers responsibility to type assert that the interface
10 | // is the expected struct.
11 | Handle(*Session, interface{})
12 | }
13 |
14 | // EventInterfaceProvider is an interface for providing empty interfaces for
15 | // Discord events.
16 | type EventInterfaceProvider interface {
17 | // Type is the type of event this handler belongs to.
18 | Type() string
19 |
20 | // New returns a new instance of the struct this event handler handles.
21 | // This is called once per event.
22 | // The struct is provided to all handlers of the same Type().
23 | New() interface{}
24 | }
25 |
26 | // interfaceEventType is the event handler type for interface{} events.
27 | const interfaceEventType = "__INTERFACE__"
28 |
29 | // interfaceEventHandler is an event handler for interface{} events.
30 | type interfaceEventHandler func(*Session, interface{})
31 |
32 | // Type returns the event type for interface{} events.
33 | func (eh interfaceEventHandler) Type() string {
34 | return interfaceEventType
35 | }
36 |
37 | // Handle is the handler for an interface{} event.
38 | func (eh interfaceEventHandler) Handle(s *Session, i interface{}) {
39 | eh(s, i)
40 | }
41 |
42 | var registeredInterfaceProviders = map[string]EventInterfaceProvider{}
43 |
44 | // registerInterfaceProvider registers a provider so that DiscordGo can
45 | // access it's New() method.
46 | func registerInterfaceProvider(eh EventInterfaceProvider) {
47 | if _, ok := registeredInterfaceProviders[eh.Type()]; ok {
48 | return
49 | // XXX:
50 | // if we should error here, we need to do something with it.
51 | // fmt.Errorf("event %s already registered", eh.Type())
52 | }
53 | registeredInterfaceProviders[eh.Type()] = eh
54 | return
55 | }
56 |
57 | // eventHandlerInstance is a wrapper around an event handler, as functions
58 | // cannot be compared directly.
59 | type eventHandlerInstance struct {
60 | eventHandler EventHandler
61 | }
62 |
63 | // addEventHandler adds an event handler that will be fired anytime
64 | // the Discord WSAPI matching eventHandler.Type() fires.
65 | func (s *Session) addEventHandler(eventHandler EventHandler) func() {
66 | s.handlersMu.Lock()
67 | defer s.handlersMu.Unlock()
68 |
69 | if s.handlers == nil {
70 | s.handlers = map[string][]*eventHandlerInstance{}
71 | }
72 |
73 | ehi := &eventHandlerInstance{eventHandler}
74 | s.handlers[eventHandler.Type()] = append(s.handlers[eventHandler.Type()], ehi)
75 |
76 | return func() {
77 | s.removeEventHandlerInstance(eventHandler.Type(), ehi)
78 | }
79 | }
80 |
81 | // addEventHandler adds an event handler that will be fired the next time
82 | // the Discord WSAPI matching eventHandler.Type() fires.
83 | func (s *Session) addEventHandlerOnce(eventHandler EventHandler) func() {
84 | s.handlersMu.Lock()
85 | defer s.handlersMu.Unlock()
86 |
87 | if s.onceHandlers == nil {
88 | s.onceHandlers = map[string][]*eventHandlerInstance{}
89 | }
90 |
91 | ehi := &eventHandlerInstance{eventHandler}
92 | s.onceHandlers[eventHandler.Type()] = append(s.onceHandlers[eventHandler.Type()], ehi)
93 |
94 | return func() {
95 | s.removeEventHandlerInstance(eventHandler.Type(), ehi)
96 | }
97 | }
98 |
99 | // AddHandler allows you to add an event handler that will be fired anytime
100 | // the Discord WSAPI event that matches the function fires.
101 | // The first parameter is a *Session, and the second parameter is a pointer
102 | // to a struct corresponding to the event for which you want to listen.
103 | //
104 | // eg:
105 | // Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
106 | // })
107 | //
108 | // or:
109 | // Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) {
110 | // })
111 | //
112 | // List of events can be found at this page, with corresponding names in the
113 | // library for each event: https://discord.com/developers/docs/topics/gateway#event-names
114 | // There are also synthetic events fired by the library internally which are
115 | // available for handling, like Connect, Disconnect, and RateLimit.
116 | // events.go contains all of the Discord WSAPI and synthetic events that can be handled.
117 | //
118 | // The return value of this method is a function, that when called will remove the
119 | // event handler.
120 | func (s *Session) AddHandler(handler interface{}) func() {
121 | eh := handlerForInterface(handler)
122 |
123 | if eh == nil {
124 | s.log(LogError, "Invalid handler type, handler will never be called")
125 | return func() {}
126 | }
127 |
128 | return s.addEventHandler(eh)
129 | }
130 |
131 | // AddHandlerOnce allows you to add an event handler that will be fired the next time
132 | // the Discord WSAPI event that matches the function fires.
133 | // See AddHandler for more details.
134 | func (s *Session) AddHandlerOnce(handler interface{}) func() {
135 | eh := handlerForInterface(handler)
136 |
137 | if eh == nil {
138 | s.log(LogError, "Invalid handler type, handler will never be called")
139 | return func() {}
140 | }
141 |
142 | return s.addEventHandlerOnce(eh)
143 | }
144 |
145 | // removeEventHandler instance removes an event handler instance.
146 | func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance) {
147 | s.handlersMu.Lock()
148 | defer s.handlersMu.Unlock()
149 |
150 | handlers := s.handlers[t]
151 | for i := range handlers {
152 | if handlers[i] == ehi {
153 | s.handlers[t] = append(handlers[:i], handlers[i+1:]...)
154 | }
155 | }
156 |
157 | onceHandlers := s.onceHandlers[t]
158 | for i := range onceHandlers {
159 | if onceHandlers[i] == ehi {
160 | s.onceHandlers[t] = append(onceHandlers[:i], onceHandlers[i+1:]...)
161 | }
162 | }
163 | }
164 |
165 | // Handles calling permanent and once handlers for an event type.
166 | func (s *Session) handle(t string, i interface{}) {
167 | for _, eh := range s.handlers[t] {
168 | if s.SyncEvents {
169 | eh.eventHandler.Handle(s, i)
170 | } else {
171 | go eh.eventHandler.Handle(s, i)
172 | }
173 | }
174 |
175 | if len(s.onceHandlers[t]) > 0 {
176 | for _, eh := range s.onceHandlers[t] {
177 | if s.SyncEvents {
178 | eh.eventHandler.Handle(s, i)
179 | } else {
180 | go eh.eventHandler.Handle(s, i)
181 | }
182 | }
183 | s.onceHandlers[t] = nil
184 | }
185 | }
186 |
187 | // Handles an event type by calling internal methods, firing handlers and firing the
188 | // interface{} event.
189 | func (s *Session) handleEvent(t string, i interface{}) {
190 | s.handlersMu.RLock()
191 | defer s.handlersMu.RUnlock()
192 |
193 | // All events are dispatched internally first.
194 | s.onInterface(i)
195 |
196 | // Then they are dispatched to anyone handling interface{} events.
197 | s.handle(interfaceEventType, i)
198 |
199 | // Finally they are dispatched to any typed handlers.
200 | s.handle(t, i)
201 | }
202 |
203 | // setGuildIds will set the GuildID on all the members of a guild.
204 | // This is done as event data does not have it set.
205 | func setGuildIds(g *Guild) {
206 | for _, c := range g.Channels {
207 | c.GuildID = g.ID
208 | }
209 |
210 | for _, m := range g.Members {
211 | m.GuildID = g.ID
212 | }
213 |
214 | for _, vs := range g.VoiceStates {
215 | vs.GuildID = g.ID
216 | }
217 | }
218 |
219 | // onInterface handles all internal events and routes them to the appropriate internal handler.
220 | func (s *Session) onInterface(i interface{}) {
221 | switch t := i.(type) {
222 | case *Ready:
223 | for _, g := range t.Guilds {
224 | setGuildIds(g)
225 | }
226 | s.onReady(t)
227 | case *GuildCreate:
228 | setGuildIds(t.Guild)
229 | case *GuildUpdate:
230 | setGuildIds(t.Guild)
231 | case *VoiceServerUpdate:
232 | go s.onVoiceServerUpdate(t)
233 | case *VoiceStateUpdate:
234 | go s.onVoiceStateUpdate(t)
235 | }
236 | err := s.State.OnInterface(s, i)
237 | if err != nil {
238 | s.log(LogDebug, "error dispatching internal event, %s", err)
239 | }
240 | }
241 |
242 | // onReady handles the ready event.
243 | func (s *Session) onReady(r *Ready) {
244 |
245 | // Store the SessionID within the Session struct.
246 | s.sessionID = r.SessionID
247 | }
248 |
--------------------------------------------------------------------------------
/examples/autocomplete/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "log"
7 | "os"
8 | "os/signal"
9 |
10 | "github.com/bwmarrin/discordgo"
11 | )
12 |
13 | // Bot parameters
14 | var (
15 | GuildID = flag.String("guild", "", "Test guild ID. If not passed - bot registers commands globally")
16 | BotToken = flag.String("token", "", "Bot access token")
17 | RemoveCommands = flag.Bool("rmcmd", true, "Remove all commands after shutdowning or not")
18 | )
19 |
20 | var s *discordgo.Session
21 |
22 | func init() { flag.Parse() }
23 |
24 | func init() {
25 | var err error
26 | s, err = discordgo.New("Bot " + *BotToken)
27 | if err != nil {
28 | log.Fatalf("Invalid bot parameters: %v", err)
29 | }
30 | }
31 |
32 | var (
33 | commands = []*discordgo.ApplicationCommand{
34 | {
35 | Name: "single-autocomplete",
36 | Description: "Showcase of single autocomplete option",
37 | Type: discordgo.ChatApplicationCommand,
38 | Options: []*discordgo.ApplicationCommandOption{
39 | {
40 | Name: "autocomplete-option",
41 | Description: "Autocomplete option",
42 | Type: discordgo.ApplicationCommandOptionString,
43 | Required: true,
44 | Autocomplete: true,
45 | },
46 | },
47 | },
48 | {
49 | Name: "multi-autocomplete",
50 | Description: "Showcase of multiple autocomplete option",
51 | Type: discordgo.ChatApplicationCommand,
52 | Options: []*discordgo.ApplicationCommandOption{
53 | {
54 | Name: "autocomplete-option-1",
55 | Description: "Autocomplete option 1",
56 | Type: discordgo.ApplicationCommandOptionString,
57 | Required: true,
58 | Autocomplete: true,
59 | },
60 | {
61 | Name: "autocomplete-option-2",
62 | Description: "Autocomplete option 2",
63 | Type: discordgo.ApplicationCommandOptionString,
64 | Required: true,
65 | Autocomplete: true,
66 | },
67 | },
68 | },
69 | }
70 |
71 | commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
72 | "single-autocomplete": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
73 | switch i.Type {
74 | case discordgo.InteractionApplicationCommand:
75 | data := i.ApplicationCommandData()
76 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
77 | Type: discordgo.InteractionResponseChannelMessageWithSource,
78 | Data: &discordgo.InteractionResponseData{
79 | Content: fmt.Sprintf(
80 | "You picked %q autocompletion",
81 | // Autocompleted options do not affect usual flow of handling application command. They are ordinary options at this stage
82 | data.Options[0].StringValue(),
83 | ),
84 | },
85 | })
86 | if err != nil {
87 | panic(err)
88 | }
89 | // Autocomplete options introduce a new interaction type (8) for returning custom autocomplete results.
90 | case discordgo.InteractionApplicationCommandAutocomplete:
91 | data := i.ApplicationCommandData()
92 | choices := []*discordgo.ApplicationCommandOptionChoice{
93 | {
94 | Name: "Autocomplete",
95 | Value: "autocomplete",
96 | },
97 | {
98 | Name: "Autocomplete is best!",
99 | Value: "autocomplete_is_best",
100 | },
101 | {
102 | Name: "Choice 3",
103 | Value: "choice3",
104 | },
105 | {
106 | Name: "Choice 4",
107 | Value: "choice4",
108 | },
109 | {
110 | Name: "Choice 5",
111 | Value: "choice5",
112 | },
113 | // And so on, up to 25 choices
114 | }
115 |
116 | if data.Options[0].StringValue() != "" {
117 | choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
118 | Name: data.Options[0].StringValue(), // To get user input you just get value of the autocomplete option.
119 | Value: "choice_custom",
120 | })
121 | }
122 |
123 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
124 | Type: discordgo.InteractionApplicationCommandAutocompleteResult,
125 | Data: &discordgo.InteractionResponseData{
126 | Choices: choices, // This is basically the whole purpose of autocomplete interaction - return custom options to the user.
127 | },
128 | })
129 | if err != nil {
130 | panic(err)
131 | }
132 | }
133 | },
134 | "multi-autocomplete": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
135 | switch i.Type {
136 | case discordgo.InteractionApplicationCommand:
137 | data := i.ApplicationCommandData()
138 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
139 | Type: discordgo.InteractionResponseChannelMessageWithSource,
140 | Data: &discordgo.InteractionResponseData{
141 | Content: fmt.Sprintf(
142 | "Option 1: %s\nOption 2: %s",
143 | data.Options[0].StringValue(),
144 | data.Options[1].StringValue(),
145 | ),
146 | },
147 | })
148 | if err != nil {
149 | panic(err)
150 | }
151 | case discordgo.InteractionApplicationCommandAutocomplete:
152 | data := i.ApplicationCommandData()
153 | var choices []*discordgo.ApplicationCommandOptionChoice
154 | switch {
155 | // In this case there are multiple autocomplete options. The Focused field shows which option user is focused on.
156 | case data.Options[0].Focused:
157 | choices = []*discordgo.ApplicationCommandOptionChoice{
158 | {
159 | Name: "Autocomplete 4 first option",
160 | Value: "autocomplete_default",
161 | },
162 | {
163 | Name: "Choice 3",
164 | Value: "choice3",
165 | },
166 | {
167 | Name: "Choice 4",
168 | Value: "choice4",
169 | },
170 | {
171 | Name: "Choice 5",
172 | Value: "choice5",
173 | },
174 | }
175 | if data.Options[0].StringValue() != "" {
176 | choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
177 | Name: data.Options[0].StringValue(),
178 | Value: "choice_custom",
179 | })
180 | }
181 |
182 | case data.Options[1].Focused:
183 | choices = []*discordgo.ApplicationCommandOptionChoice{
184 | {
185 | Name: "Autocomplete 4 second option",
186 | Value: "autocomplete_1_default",
187 | },
188 | {
189 | Name: "Choice 3.1",
190 | Value: "choice3_1",
191 | },
192 | {
193 | Name: "Choice 4.1",
194 | Value: "choice4_1",
195 | },
196 | {
197 | Name: "Choice 5.1",
198 | Value: "choice5_1",
199 | },
200 | }
201 | if data.Options[1].StringValue() != "" {
202 | choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
203 | Name: data.Options[1].StringValue(),
204 | Value: "choice_custom_2",
205 | })
206 | }
207 | }
208 |
209 | err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
210 | Type: discordgo.InteractionApplicationCommandAutocompleteResult,
211 | Data: &discordgo.InteractionResponseData{
212 | Choices: choices,
213 | },
214 | })
215 | if err != nil {
216 | panic(err)
217 | }
218 | }
219 | },
220 | }
221 | )
222 |
223 | func main() {
224 | s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { log.Println("Bot is up!") })
225 | s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
226 | if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
227 | h(s, i)
228 | }
229 | })
230 | err := s.Open()
231 | if err != nil {
232 | log.Fatalf("Cannot open the session: %v", err)
233 | }
234 | defer s.Close()
235 |
236 | createdCommands, err := s.ApplicationCommandBulkOverwrite(s.State.User.ID, *GuildID, commands)
237 |
238 | if err != nil {
239 | log.Fatalf("Cannot register commands: %v", err)
240 | }
241 |
242 | stop := make(chan os.Signal, 1)
243 | signal.Notify(stop, os.Interrupt)
244 | <-stop
245 | log.Println("Gracefully shutting down")
246 |
247 | if *RemoveCommands {
248 | for _, cmd := range createdCommands {
249 | err := s.ApplicationCommandDelete(s.State.User.ID, *GuildID, cmd.ID)
250 | if err != nil {
251 | log.Fatalf("Cannot delete %q command: %v", cmd.Name, err)
252 | }
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/components.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | )
7 |
8 | // ComponentType is type of component.
9 | type ComponentType uint
10 |
11 | // MessageComponent types.
12 | const (
13 | ActionsRowComponent ComponentType = 1
14 | ButtonComponent ComponentType = 2
15 | SelectMenuComponent ComponentType = 3
16 | TextInputComponent ComponentType = 4
17 | UserSelectMenuComponent ComponentType = 5
18 | RoleSelectMenuComponent ComponentType = 6
19 | MentionableSelectMenuComponent ComponentType = 7
20 | ChannelSelectMenuComponent ComponentType = 8
21 | )
22 |
23 | // MessageComponent is a base interface for all message components.
24 | type MessageComponent interface {
25 | json.Marshaler
26 | Type() ComponentType
27 | }
28 |
29 | type unmarshalableMessageComponent struct {
30 | MessageComponent
31 | }
32 |
33 | // UnmarshalJSON is a helper function to unmarshal MessageComponent object.
34 | func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error {
35 | var v struct {
36 | Type ComponentType `json:"type"`
37 | }
38 | err := json.Unmarshal(src, &v)
39 | if err != nil {
40 | return err
41 | }
42 |
43 | switch v.Type {
44 | case ActionsRowComponent:
45 | umc.MessageComponent = &ActionsRow{}
46 | case ButtonComponent:
47 | umc.MessageComponent = &Button{}
48 | case SelectMenuComponent, ChannelSelectMenuComponent, UserSelectMenuComponent,
49 | RoleSelectMenuComponent, MentionableSelectMenuComponent:
50 | umc.MessageComponent = &SelectMenu{}
51 | case TextInputComponent:
52 | umc.MessageComponent = &TextInput{}
53 | default:
54 | return fmt.Errorf("unknown component type: %d", v.Type)
55 | }
56 | return json.Unmarshal(src, umc.MessageComponent)
57 | }
58 |
59 | // MessageComponentFromJSON is a helper function for unmarshaling message components
60 | func MessageComponentFromJSON(b []byte) (MessageComponent, error) {
61 | var u unmarshalableMessageComponent
62 | err := u.UnmarshalJSON(b)
63 | if err != nil {
64 | return nil, fmt.Errorf("failed to unmarshal into MessageComponent: %w", err)
65 | }
66 | return u.MessageComponent, nil
67 | }
68 |
69 | // ActionsRow is a container for components within one row.
70 | type ActionsRow struct {
71 | Components []MessageComponent `json:"components"`
72 | }
73 |
74 | // MarshalJSON is a method for marshaling ActionsRow to a JSON object.
75 | func (r ActionsRow) MarshalJSON() ([]byte, error) {
76 | type actionsRow ActionsRow
77 |
78 | return Marshal(struct {
79 | actionsRow
80 | Type ComponentType `json:"type"`
81 | }{
82 | actionsRow: actionsRow(r),
83 | Type: r.Type(),
84 | })
85 | }
86 |
87 | // UnmarshalJSON is a helper function to unmarshal Actions Row.
88 | func (r *ActionsRow) UnmarshalJSON(data []byte) error {
89 | var v struct {
90 | RawComponents []unmarshalableMessageComponent `json:"components"`
91 | }
92 | err := json.Unmarshal(data, &v)
93 | if err != nil {
94 | return err
95 | }
96 | r.Components = make([]MessageComponent, len(v.RawComponents))
97 | for i, v := range v.RawComponents {
98 | r.Components[i] = v.MessageComponent
99 | }
100 |
101 | return err
102 | }
103 |
104 | // Type is a method to get the type of a component.
105 | func (r ActionsRow) Type() ComponentType {
106 | return ActionsRowComponent
107 | }
108 |
109 | // ButtonStyle is style of button.
110 | type ButtonStyle uint
111 |
112 | // Button styles.
113 | const (
114 | // PrimaryButton is a button with blurple color.
115 | PrimaryButton ButtonStyle = 1
116 | // SecondaryButton is a button with grey color.
117 | SecondaryButton ButtonStyle = 2
118 | // SuccessButton is a button with green color.
119 | SuccessButton ButtonStyle = 3
120 | // DangerButton is a button with red color.
121 | DangerButton ButtonStyle = 4
122 | // LinkButton is a special type of button which navigates to a URL. Has grey color.
123 | LinkButton ButtonStyle = 5
124 | )
125 |
126 | // ComponentEmoji represents button emoji, if it does have one.
127 | type ComponentEmoji struct {
128 | Name string `json:"name,omitempty"`
129 | ID string `json:"id,omitempty"`
130 | Animated bool `json:"animated,omitempty"`
131 | }
132 |
133 | // Button represents button component.
134 | type Button struct {
135 | Label string `json:"label"`
136 | Style ButtonStyle `json:"style"`
137 | Disabled bool `json:"disabled"`
138 | Emoji ComponentEmoji `json:"emoji"`
139 |
140 | // NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID.
141 | URL string `json:"url,omitempty"`
142 | CustomID string `json:"custom_id,omitempty"`
143 | }
144 |
145 | // MarshalJSON is a method for marshaling Button to a JSON object.
146 | func (b Button) MarshalJSON() ([]byte, error) {
147 | type button Button
148 |
149 | if b.Style == 0 {
150 | b.Style = PrimaryButton
151 | }
152 |
153 | return Marshal(struct {
154 | button
155 | Type ComponentType `json:"type"`
156 | }{
157 | button: button(b),
158 | Type: b.Type(),
159 | })
160 | }
161 |
162 | // Type is a method to get the type of a component.
163 | func (Button) Type() ComponentType {
164 | return ButtonComponent
165 | }
166 |
167 | // SelectMenuOption represents an option for a select menu.
168 | type SelectMenuOption struct {
169 | Label string `json:"label,omitempty"`
170 | Value string `json:"value"`
171 | Description string `json:"description"`
172 | Emoji ComponentEmoji `json:"emoji"`
173 | // Determines whenever option is selected by default or not.
174 | Default bool `json:"default"`
175 | }
176 |
177 | // SelectMenuType represents select menu type.
178 | type SelectMenuType ComponentType
179 |
180 | // SelectMenu types.
181 | const (
182 | StringSelectMenu = SelectMenuType(SelectMenuComponent)
183 | UserSelectMenu = SelectMenuType(UserSelectMenuComponent)
184 | RoleSelectMenu = SelectMenuType(RoleSelectMenuComponent)
185 | MentionableSelectMenu = SelectMenuType(MentionableSelectMenuComponent)
186 | ChannelSelectMenu = SelectMenuType(ChannelSelectMenuComponent)
187 | )
188 |
189 | // SelectMenu represents select menu component.
190 | type SelectMenu struct {
191 | // Type of the select menu.
192 | MenuType SelectMenuType `json:"type,omitempty"`
193 | // CustomID is a developer-defined identifier for the select menu.
194 | CustomID string `json:"custom_id,omitempty"`
195 | // The text which will be shown in the menu if there's no default options or all options was deselected and component was closed.
196 | Placeholder string `json:"placeholder"`
197 | // This value determines the minimal amount of selected items in the menu.
198 | MinValues *int `json:"min_values,omitempty"`
199 | // This value determines the maximal amount of selected items in the menu.
200 | // If MaxValues or MinValues are greater than one then the user can select multiple items in the component.
201 | MaxValues int `json:"max_values,omitempty"`
202 | Options []SelectMenuOption `json:"options,omitempty"`
203 | Disabled bool `json:"disabled"`
204 |
205 | // NOTE: Can only be used in SelectMenu with Channel menu type.
206 | ChannelTypes []ChannelType `json:"channel_types,omitempty"`
207 | }
208 |
209 | // Type is a method to get the type of a component.
210 | func (s SelectMenu) Type() ComponentType {
211 | if s.MenuType != 0 {
212 | return ComponentType(s.MenuType)
213 | }
214 | return SelectMenuComponent
215 | }
216 |
217 | // MarshalJSON is a method for marshaling SelectMenu to a JSON object.
218 | func (s SelectMenu) MarshalJSON() ([]byte, error) {
219 | type selectMenu SelectMenu
220 |
221 | return Marshal(struct {
222 | selectMenu
223 | Type ComponentType `json:"type"`
224 | }{
225 | selectMenu: selectMenu(s),
226 | Type: s.Type(),
227 | })
228 | }
229 |
230 | // TextInput represents text input component.
231 | type TextInput struct {
232 | CustomID string `json:"custom_id"`
233 | Label string `json:"label"`
234 | Style TextInputStyle `json:"style"`
235 | Placeholder string `json:"placeholder,omitempty"`
236 | Value string `json:"value,omitempty"`
237 | Required bool `json:"required"`
238 | MinLength int `json:"min_length,omitempty"`
239 | MaxLength int `json:"max_length,omitempty"`
240 | }
241 |
242 | // Type is a method to get the type of a component.
243 | func (TextInput) Type() ComponentType {
244 | return TextInputComponent
245 | }
246 |
247 | // MarshalJSON is a method for marshaling TextInput to a JSON object.
248 | func (m TextInput) MarshalJSON() ([]byte, error) {
249 | type inputText TextInput
250 |
251 | return Marshal(struct {
252 | inputText
253 | Type ComponentType `json:"type"`
254 | }{
255 | inputText: inputText(m),
256 | Type: m.Type(),
257 | })
258 | }
259 |
260 | // TextInputStyle is style of text in TextInput component.
261 | type TextInputStyle uint
262 |
263 | // Text styles
264 | const (
265 | TextInputShort TextInputStyle = 1
266 | TextInputParagraph TextInputStyle = 2
267 | )
268 |
--------------------------------------------------------------------------------
/discord_test.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "runtime"
7 | "sync/atomic"
8 | "testing"
9 | "time"
10 | )
11 |
12 | //////////////////////////////////////////////////////////////////////////////
13 | ////////////////////////////////////////////////////// VARS NEEDED FOR TESTING
14 | var (
15 | dg *Session // Stores a global discordgo user session
16 | dgBot *Session // Stores a global discordgo bot session
17 |
18 | envOAuth2Token = os.Getenv("DG_OAUTH2_TOKEN") // Token to use when authenticating using OAuth2 token
19 | envBotToken = os.Getenv("DGB_TOKEN") // Token to use when authenticating the bot account
20 | envGuild = os.Getenv("DG_GUILD") // Guild ID to use for tests
21 | envChannel = os.Getenv("DG_CHANNEL") // Channel ID to use for tests
22 | envVoiceChannel = os.Getenv("DG_VOICE_CHANNEL") // Channel ID to use for tests
23 | envAdmin = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests
24 | )
25 |
26 | func TestMain(m *testing.M) {
27 | fmt.Println("Init is being called.")
28 | if envBotToken != "" {
29 | if d, err := New(envBotToken); err == nil {
30 | dgBot = d
31 | }
32 | }
33 |
34 | if envOAuth2Token == "" {
35 | envOAuth2Token = os.Getenv("DGU_TOKEN")
36 | }
37 |
38 | if envOAuth2Token != "" {
39 | if d, err := New(envOAuth2Token); err == nil {
40 | dg = d
41 | }
42 | }
43 |
44 | os.Exit(m.Run())
45 | }
46 |
47 | //////////////////////////////////////////////////////////////////////////////
48 | /////////////////////////////////////////////////////////////// START OF TESTS
49 |
50 | // TestNewToken tests the New() function with a Token.
51 | func TestNewToken(t *testing.T) {
52 |
53 | if envOAuth2Token == "" {
54 | t.Skip("Skipping New(token), DGU_TOKEN not set")
55 | }
56 |
57 | d, err := New(envOAuth2Token)
58 | if err != nil {
59 | t.Fatalf("New(envToken) returned error: %+v", err)
60 | }
61 |
62 | if d == nil {
63 | t.Fatal("New(envToken), d is nil, should be Session{}")
64 | }
65 |
66 | if d.Token == "" {
67 | t.Fatal("New(envToken), d.Token is empty, should be a valid Token.")
68 | }
69 | }
70 |
71 | func TestOpenClose(t *testing.T) {
72 | if envOAuth2Token == "" {
73 | t.Skip("Skipping TestClose, DGU_TOKEN not set")
74 | }
75 |
76 | d, err := New(envOAuth2Token)
77 | if err != nil {
78 | t.Fatalf("TestClose, New(envToken) returned error: %+v", err)
79 | }
80 |
81 | if err = d.Open(); err != nil {
82 | t.Fatalf("TestClose, d.Open failed: %+v", err)
83 | }
84 |
85 | // We need a better way to know the session is ready for use,
86 | // this is totally gross.
87 | start := time.Now()
88 | for {
89 | d.RLock()
90 | if d.DataReady {
91 | d.RUnlock()
92 | break
93 | }
94 | d.RUnlock()
95 |
96 | if time.Since(start) > 10*time.Second {
97 | t.Fatal("DataReady never became true.yy")
98 | }
99 | runtime.Gosched()
100 | }
101 |
102 | // TODO find a better way
103 | // Add a small sleep here to make sure heartbeat and other events
104 | // have enough time to get fired. Need a way to actually check
105 | // those events.
106 | time.Sleep(2 * time.Second)
107 |
108 | // UpdateStatus - maybe we move this into wsapi_test.go but the websocket
109 | // created here is needed. This helps tests that the websocket was setup
110 | // and it is working.
111 | if err = d.UpdateGameStatus(0, time.Now().String()); err != nil {
112 | t.Errorf("UpdateStatus error: %+v", err)
113 | }
114 |
115 | if err = d.Close(); err != nil {
116 | t.Fatalf("TestClose, d.Close failed: %+v", err)
117 | }
118 | }
119 |
120 | func TestAddHandler(t *testing.T) {
121 |
122 | testHandlerCalled := int32(0)
123 | testHandler := func(s *Session, m *MessageCreate) {
124 | atomic.AddInt32(&testHandlerCalled, 1)
125 | }
126 |
127 | interfaceHandlerCalled := int32(0)
128 | interfaceHandler := func(s *Session, i interface{}) {
129 | atomic.AddInt32(&interfaceHandlerCalled, 1)
130 | }
131 |
132 | bogusHandlerCalled := int32(0)
133 | bogusHandler := func(s *Session, se *Session) {
134 | atomic.AddInt32(&bogusHandlerCalled, 1)
135 | }
136 |
137 | d := Session{}
138 | d.AddHandler(testHandler)
139 | d.AddHandler(testHandler)
140 |
141 | d.AddHandler(interfaceHandler)
142 | d.AddHandler(bogusHandler)
143 |
144 | d.handleEvent(messageCreateEventType, &MessageCreate{})
145 | d.handleEvent(messageDeleteEventType, &MessageDelete{})
146 |
147 | <-time.After(500 * time.Millisecond)
148 |
149 | // testHandler will be called twice because it was added twice.
150 | if atomic.LoadInt32(&testHandlerCalled) != 2 {
151 | t.Fatalf("testHandler was not called twice.")
152 | }
153 |
154 | // interfaceHandler will be called twice, once for each event.
155 | if atomic.LoadInt32(&interfaceHandlerCalled) != 2 {
156 | t.Fatalf("interfaceHandler was not called twice.")
157 | }
158 |
159 | if atomic.LoadInt32(&bogusHandlerCalled) != 0 {
160 | t.Fatalf("bogusHandler was called.")
161 | }
162 | }
163 |
164 | func TestRemoveHandler(t *testing.T) {
165 |
166 | testHandlerCalled := int32(0)
167 | testHandler := func(s *Session, m *MessageCreate) {
168 | atomic.AddInt32(&testHandlerCalled, 1)
169 | }
170 |
171 | d := Session{}
172 | r := d.AddHandler(testHandler)
173 |
174 | d.handleEvent(messageCreateEventType, &MessageCreate{})
175 |
176 | r()
177 |
178 | d.handleEvent(messageCreateEventType, &MessageCreate{})
179 |
180 | <-time.After(500 * time.Millisecond)
181 |
182 | // testHandler will be called once, as it was removed in between calls.
183 | if atomic.LoadInt32(&testHandlerCalled) != 1 {
184 | t.Fatalf("testHandler was not called once.")
185 | }
186 | }
187 |
188 | func TestScheduledEvents(t *testing.T) {
189 | if dgBot == nil {
190 | t.Skip("Skipping, dgBot not set.")
191 | }
192 |
193 | beginAt := time.Now().Add(1 * time.Hour)
194 | endAt := time.Now().Add(2 * time.Hour)
195 | event, err := dgBot.GuildScheduledEventCreate(envGuild, &GuildScheduledEventParams{
196 | Name: "Test Event",
197 | PrivacyLevel: GuildScheduledEventPrivacyLevelGuildOnly,
198 | ScheduledStartTime: &beginAt,
199 | ScheduledEndTime: &endAt,
200 | Description: "Awesome Test Event created on livestream",
201 | EntityType: GuildScheduledEventEntityTypeExternal,
202 | EntityMetadata: &GuildScheduledEventEntityMetadata{
203 | Location: "https://discord.com",
204 | },
205 | })
206 | defer dgBot.GuildScheduledEventDelete(envGuild, event.ID)
207 |
208 | if err != nil || event.Name != "Test Event" {
209 | t.Fatal(err)
210 | }
211 |
212 | events, err := dgBot.GuildScheduledEvents(envGuild, true)
213 | if err != nil {
214 | t.Fatal(err)
215 | }
216 |
217 | var foundEvent *GuildScheduledEvent
218 | for _, e := range events {
219 | if e.ID == event.ID {
220 | foundEvent = e
221 | break
222 | }
223 | }
224 | if foundEvent.Name != event.Name {
225 | t.Fatal("err on GuildScheduledEvents endpoint. Missing Scheduled Event")
226 | }
227 |
228 | getEvent, err := dgBot.GuildScheduledEvent(envGuild, event.ID, true)
229 | if err != nil {
230 | t.Fatal(err)
231 | }
232 | if getEvent.Name != event.Name {
233 | t.Fatal("err on GuildScheduledEvent endpoint. Mismatched Scheduled Event")
234 | }
235 |
236 | eventUpdated, err := dgBot.GuildScheduledEventEdit(envGuild, event.ID, &GuildScheduledEventParams{Name: "Test Event Updated"})
237 | if err != nil {
238 | t.Fatal(err)
239 | }
240 |
241 | if eventUpdated.Name != "Test Event Updated" {
242 | t.Fatal("err on GuildScheduledEventUpdate endpoint. Scheduled Event Name mismatch")
243 | }
244 |
245 | // Usage of 1 and 1 is just the pseudo data with the purpose to run all branches in the function without crashes.
246 | // see https://github.com/bwmarrin/discordgo/pull/1032#discussion_r815438303 for more details.
247 | users, err := dgBot.GuildScheduledEventUsers(envGuild, event.ID, 1, true, "1", "1")
248 | if err != nil {
249 | t.Fatal(err)
250 | }
251 | if len(users) != 0 {
252 | t.Fatal("err on GuildScheduledEventUsers. Mismatch of event maybe occured")
253 | }
254 |
255 | err = dgBot.GuildScheduledEventDelete(envGuild, event.ID)
256 | if err != nil {
257 | t.Fatal(err)
258 | }
259 | }
260 |
261 | func TestComplexScheduledEvents(t *testing.T) {
262 | if dgBot == nil {
263 | t.Skip("Skipping, dgBot not set.")
264 | }
265 |
266 | beginAt := time.Now().Add(1 * time.Hour)
267 | endAt := time.Now().Add(2 * time.Hour)
268 | event, err := dgBot.GuildScheduledEventCreate(envGuild, &GuildScheduledEventParams{
269 | Name: "Test Voice Event",
270 | PrivacyLevel: GuildScheduledEventPrivacyLevelGuildOnly,
271 | ScheduledStartTime: &beginAt,
272 | ScheduledEndTime: &endAt,
273 | Description: "Test event on voice channel",
274 | EntityType: GuildScheduledEventEntityTypeVoice,
275 | ChannelID: envVoiceChannel,
276 | })
277 | if err != nil || event.Name != "Test Voice Event" {
278 | t.Fatal(err)
279 | }
280 | defer dgBot.GuildScheduledEventDelete(envGuild, event.ID)
281 |
282 | _, err = dgBot.GuildScheduledEventEdit(envGuild, event.ID, &GuildScheduledEventParams{
283 | EntityType: GuildScheduledEventEntityTypeExternal,
284 | EntityMetadata: &GuildScheduledEventEntityMetadata{
285 | Location: "https://discord.com",
286 | },
287 | })
288 |
289 | if err != nil {
290 | t.Fatal("err on GuildScheduledEventEdit. Change of entity type to external failed")
291 | }
292 |
293 | _, err = dgBot.GuildScheduledEventEdit(envGuild, event.ID, &GuildScheduledEventParams{
294 | ChannelID: envVoiceChannel,
295 | EntityType: GuildScheduledEventEntityTypeVoice,
296 | EntityMetadata: nil,
297 | })
298 |
299 | if err != nil {
300 | t.Fatal("err on GuildScheduledEventEdit. Change of entity type to voice failed")
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/events.go:
--------------------------------------------------------------------------------
1 | package discordgo
2 |
3 | import (
4 | "encoding/json"
5 | )
6 |
7 | // This file contains all the possible structs that can be
8 | // handled by AddHandler/EventHandler.
9 | // DO NOT ADD ANYTHING BUT EVENT HANDLER STRUCTS TO THIS FILE.
10 | //go:generate go run tools/cmd/eventhandlers/main.go
11 |
12 | // Connect is the data for a Connect event.
13 | // This is a synthetic event and is not dispatched by Discord.
14 | type Connect struct{}
15 |
16 | // Disconnect is the data for a Disconnect event.
17 | // This is a synthetic event and is not dispatched by Discord.
18 | type Disconnect struct{}
19 |
20 | // RateLimit is the data for a RateLimit event.
21 | // This is a synthetic event and is not dispatched by Discord.
22 | type RateLimit struct {
23 | *TooManyRequests
24 | URL string
25 | }
26 |
27 | // Event provides a basic initial struct for all websocket events.
28 | type Event struct {
29 | Operation int `json:"op"`
30 | Sequence int64 `json:"s"`
31 | Type string `json:"t"`
32 | RawData json.RawMessage `json:"d"`
33 | // Struct contains one of the other types in this file.
34 | Struct interface{} `json:"-"`
35 | }
36 |
37 | // A Ready stores all data for the websocket READY event.
38 | type Ready struct {
39 | Version int `json:"v"`
40 | SessionID string `json:"session_id"`
41 | User *User `json:"user"`
42 | Shard *[2]int `json:"shard"`
43 | Application *Application `json:"application"`
44 | Guilds []*Guild `json:"guilds"`
45 | PrivateChannels []*Channel `json:"private_channels"`
46 | }
47 |
48 | // ChannelCreate is the data for a ChannelCreate event.
49 | type ChannelCreate struct {
50 | *Channel
51 | }
52 |
53 | // ChannelUpdate is the data for a ChannelUpdate event.
54 | type ChannelUpdate struct {
55 | *Channel
56 | }
57 |
58 | // ChannelDelete is the data for a ChannelDelete event.
59 | type ChannelDelete struct {
60 | *Channel
61 | }
62 |
63 | // ChannelPinsUpdate stores data for a ChannelPinsUpdate event.
64 | type ChannelPinsUpdate struct {
65 | LastPinTimestamp string `json:"last_pin_timestamp"`
66 | ChannelID string `json:"channel_id"`
67 | GuildID string `json:"guild_id,omitempty"`
68 | }
69 |
70 | // ThreadCreate is the data for a ThreadCreate event.
71 | type ThreadCreate struct {
72 | *Channel
73 | NewlyCreated bool `json:"newly_created"`
74 | }
75 |
76 | // ThreadUpdate is the data for a ThreadUpdate event.
77 | type ThreadUpdate struct {
78 | *Channel
79 | BeforeUpdate *Channel `json:"-"`
80 | }
81 |
82 | // ThreadDelete is the data for a ThreadDelete event.
83 | type ThreadDelete struct {
84 | *Channel
85 | }
86 |
87 | // ThreadListSync is the data for a ThreadListSync event.
88 | type ThreadListSync struct {
89 | // The id of the guild
90 | GuildID string `json:"guild_id"`
91 | // The parent channel ids whose threads are being synced.
92 | // If omitted, then threads were synced for the entire guild.
93 | // This array may contain channel_ids that have no active threads as well, so you know to clear that data.
94 | ChannelIDs []string `json:"channel_ids"`
95 | // All active threads in the given channels that the current user can access
96 | Threads []*Channel `json:"threads"`
97 | // All thread member objects from the synced threads for the current user,
98 | // indicating which threads the current user has been added to
99 | Members []*ThreadMember `json:"members"`
100 | }
101 |
102 | // ThreadMemberUpdate is the data for a ThreadMemberUpdate event.
103 | type ThreadMemberUpdate struct {
104 | *ThreadMember
105 | GuildID string `json:"guild_id"`
106 | }
107 |
108 | // ThreadMembersUpdate is the data for a ThreadMembersUpdate event.
109 | type ThreadMembersUpdate struct {
110 | ID string `json:"id"`
111 | GuildID string `json:"guild_id"`
112 | MemberCount int `json:"member_count"`
113 | AddedMembers []AddedThreadMember `json:"added_members"`
114 | RemovedMembers []string `json:"removed_member_ids"`
115 | }
116 |
117 | // GuildCreate is the data for a GuildCreate event.
118 | type GuildCreate struct {
119 | *Guild
120 | }
121 |
122 | // GuildUpdate is the data for a GuildUpdate event.
123 | type GuildUpdate struct {
124 | *Guild
125 | }
126 |
127 | // GuildDelete is the data for a GuildDelete event.
128 | type GuildDelete struct {
129 | *Guild
130 | BeforeDelete *Guild `json:"-"`
131 | }
132 |
133 | // GuildBanAdd is the data for a GuildBanAdd event.
134 | type GuildBanAdd struct {
135 | User *User `json:"user"`
136 | GuildID string `json:"guild_id"`
137 | }
138 |
139 | // GuildBanRemove is the data for a GuildBanRemove event.
140 | type GuildBanRemove struct {
141 | User *User `json:"user"`
142 | GuildID string `json:"guild_id"`
143 | }
144 |
145 | // GuildMemberAdd is the data for a GuildMemberAdd event.
146 | type GuildMemberAdd struct {
147 | *Member
148 | }
149 |
150 | // GuildMemberUpdate is the data for a GuildMemberUpdate event.
151 | type GuildMemberUpdate struct {
152 | *Member
153 | BeforeUpdate *Member `json:"-"`
154 | }
155 |
156 | // GuildMemberRemove is the data for a GuildMemberRemove event.
157 | type GuildMemberRemove struct {
158 | *Member
159 | }
160 |
161 | // GuildRoleCreate is the data for a GuildRoleCreate event.
162 | type GuildRoleCreate struct {
163 | *GuildRole
164 | }
165 |
166 | // GuildRoleUpdate is the data for a GuildRoleUpdate event.
167 | type GuildRoleUpdate struct {
168 | *GuildRole
169 | }
170 |
171 | // A GuildRoleDelete is the data for a GuildRoleDelete event.
172 | type GuildRoleDelete struct {
173 | RoleID string `json:"role_id"`
174 | GuildID string `json:"guild_id"`
175 | }
176 |
177 | // A GuildEmojisUpdate is the data for a guild emoji update event.
178 | type GuildEmojisUpdate struct {
179 | GuildID string `json:"guild_id"`
180 | Emojis []*Emoji `json:"emojis"`
181 | }
182 |
183 | // A GuildMembersChunk is the data for a GuildMembersChunk event.
184 | type GuildMembersChunk struct {
185 | GuildID string `json:"guild_id"`
186 | Members []*Member `json:"members"`
187 | ChunkIndex int `json:"chunk_index"`
188 | ChunkCount int `json:"chunk_count"`
189 | NotFound []string `json:"not_found,omitempty"`
190 | Presences []*Presence `json:"presences,omitempty"`
191 | Nonce string `json:"nonce,omitempty"`
192 | }
193 |
194 | // GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event.
195 | type GuildIntegrationsUpdate struct {
196 | GuildID string `json:"guild_id"`
197 | }
198 |
199 | // StageInstanceEventCreate is the data for a StageInstanceEventCreate event.
200 | type StageInstanceEventCreate struct {
201 | *StageInstance
202 | }
203 |
204 | // StageInstanceEventUpdate is the data for a StageInstanceEventUpdate event.
205 | type StageInstanceEventUpdate struct {
206 | *StageInstance
207 | }
208 |
209 | // StageInstanceEventDelete is the data for a StageInstanceEventDelete event.
210 | type StageInstanceEventDelete struct {
211 | *StageInstance
212 | }
213 |
214 | // GuildScheduledEventCreate is the data for a GuildScheduledEventCreate event.
215 | type GuildScheduledEventCreate struct {
216 | *GuildScheduledEvent
217 | }
218 |
219 | // GuildScheduledEventUpdate is the data for a GuildScheduledEventUpdate event.
220 | type GuildScheduledEventUpdate struct {
221 | *GuildScheduledEvent
222 | }
223 |
224 | // GuildScheduledEventDelete is the data for a GuildScheduledEventDelete event.
225 | type GuildScheduledEventDelete struct {
226 | *GuildScheduledEvent
227 | }
228 |
229 | // GuildScheduledEventUserAdd is the data for a GuildScheduledEventUserAdd event.
230 | type GuildScheduledEventUserAdd struct {
231 | GuildScheduledEventID string `json:"guild_scheduled_event_id"`
232 | UserID string `json:"user_id"`
233 | GuildID string `json:"guild_id"`
234 | }
235 |
236 | // GuildScheduledEventUserRemove is the data for a GuildScheduledEventUserRemove event.
237 | type GuildScheduledEventUserRemove struct {
238 | GuildScheduledEventID string `json:"guild_scheduled_event_id"`
239 | UserID string `json:"user_id"`
240 | GuildID string `json:"guild_id"`
241 | }
242 |
243 | // MessageCreate is the data for a MessageCreate event.
244 | type MessageCreate struct {
245 | *Message
246 | }
247 |
248 | // UnmarshalJSON is a helper function to unmarshal MessageCreate object.
249 | func (m *MessageCreate) UnmarshalJSON(b []byte) error {
250 | return json.Unmarshal(b, &m.Message)
251 | }
252 |
253 | // MessageUpdate is the data for a MessageUpdate event.
254 | type MessageUpdate struct {
255 | *Message
256 | // BeforeUpdate will be nil if the Message was not previously cached in the state cache.
257 | BeforeUpdate *Message `json:"-"`
258 | }
259 |
260 | // UnmarshalJSON is a helper function to unmarshal MessageUpdate object.
261 | func (m *MessageUpdate) UnmarshalJSON(b []byte) error {
262 | return json.Unmarshal(b, &m.Message)
263 | }
264 |
265 | // MessageDelete is the data for a MessageDelete event.
266 | type MessageDelete struct {
267 | *Message
268 | BeforeDelete *Message `json:"-"`
269 | }
270 |
271 | // UnmarshalJSON is a helper function to unmarshal MessageDelete object.
272 | func (m *MessageDelete) UnmarshalJSON(b []byte) error {
273 | return json.Unmarshal(b, &m.Message)
274 | }
275 |
276 | // MessageReactionAdd is the data for a MessageReactionAdd event.
277 | type MessageReactionAdd struct {
278 | *MessageReaction
279 | Member *Member `json:"member,omitempty"`
280 | }
281 |
282 | // MessageReactionRemove is the data for a MessageReactionRemove event.
283 | type MessageReactionRemove struct {
284 | *MessageReaction
285 | }
286 |
287 | // MessageReactionRemoveAll is the data for a MessageReactionRemoveAll event.
288 | type MessageReactionRemoveAll struct {
289 | *MessageReaction
290 | }
291 |
292 | // PresencesReplace is the data for a PresencesReplace event.
293 | type PresencesReplace []*Presence
294 |
295 | // PresenceUpdate is the data for a PresenceUpdate event.
296 | type PresenceUpdate struct {
297 | Presence
298 | GuildID string `json:"guild_id"`
299 | }
300 |
301 | // Resumed is the data for a Resumed event.
302 | type Resumed struct {
303 | Trace []string `json:"_trace"`
304 | }
305 |
306 | // TypingStart is the data for a TypingStart event.
307 | type TypingStart struct {
308 | UserID string `json:"user_id"`
309 | ChannelID string `json:"channel_id"`
310 | GuildID string `json:"guild_id,omitempty"`
311 | Timestamp int `json:"timestamp"`
312 | }
313 |
314 | // UserUpdate is the data for a UserUpdate event.
315 | type UserUpdate struct {
316 | *User
317 | }
318 |
319 | // VoiceServerUpdate is the data for a VoiceServerUpdate event.
320 | type VoiceServerUpdate struct {
321 | Token string `json:"token"`
322 | GuildID string `json:"guild_id"`
323 | Endpoint string `json:"endpoint"`
324 | }
325 |
326 | // VoiceStateUpdate is the data for a VoiceStateUpdate event.
327 | type VoiceStateUpdate struct {
328 | *VoiceState
329 | // BeforeUpdate will be nil if the VoiceState was not previously cached in the state cache.
330 | BeforeUpdate *VoiceState `json:"-"`
331 | }
332 |
333 | // MessageDeleteBulk is the data for a MessageDeleteBulk event
334 | type MessageDeleteBulk struct {
335 | Messages []string `json:"ids"`
336 | ChannelID string `json:"channel_id"`
337 | GuildID string `json:"guild_id"`
338 | }
339 |
340 | // WebhooksUpdate is the data for a WebhooksUpdate event
341 | type WebhooksUpdate struct {
342 | GuildID string `json:"guild_id"`
343 | ChannelID string `json:"channel_id"`
344 | }
345 |
346 | // InteractionCreate is the data for a InteractionCreate event
347 | type InteractionCreate struct {
348 | *Interaction
349 | }
350 |
351 | // UnmarshalJSON is a helper function to unmarshal Interaction object.
352 | func (i *InteractionCreate) UnmarshalJSON(b []byte) error {
353 | return json.Unmarshal(b, &i.Interaction)
354 | }
355 |
356 | // InviteCreate is the data for a InviteCreate event
357 | type InviteCreate struct {
358 | *Invite
359 | ChannelID string `json:"channel_id"`
360 | GuildID string `json:"guild_id"`
361 | }
362 |
363 | // InviteDelete is the data for a InviteDelete event
364 | type InviteDelete struct {
365 | ChannelID string `json:"channel_id"`
366 | GuildID string `json:"guild_id"`
367 | Code string `json:"code"`
368 | }
369 |
370 | // ApplicationCommandPermissionsUpdate is the data for an ApplicationCommandPermissionsUpdate event
371 | type ApplicationCommandPermissionsUpdate struct {
372 | *GuildApplicationCommandPermissions
373 | }
374 |
375 | // AutoModerationRuleCreate is the data for an AutoModerationRuleCreate event.
376 | type AutoModerationRuleCreate struct {
377 | *AutoModerationRule
378 | }
379 |
380 | // AutoModerationRuleUpdate is the data for an AutoModerationRuleUpdate event.
381 | type AutoModerationRuleUpdate struct {
382 | *AutoModerationRule
383 | }
384 |
385 | // AutoModerationRuleDelete is the data for an AutoModerationRuleDelete event.
386 | type AutoModerationRuleDelete struct {
387 | *AutoModerationRule
388 | }
389 |
390 | // AutoModerationActionExecution is the data for an AutoModerationActionExecution event.
391 | type AutoModerationActionExecution struct {
392 | GuildID string `json:"guild_id"`
393 | Action AutoModerationAction `json:"action"`
394 | RuleID string `json:"rule_id"`
395 | RuleTriggerType AutoModerationRuleTriggerType `json:"rule_trigger_type"`
396 | UserID string `json:"user_id"`
397 | ChannelID string `json:"channel_id"`
398 | MessageID string `json:"message_id"`
399 | AlertSystemMessageID string `json:"alert_system_message_id"`
400 | Content string `json:"content"`
401 | MatchedKeyword string `json:"matched_keyword"`
402 | MatchedContent string `json:"matched_content"`
403 | }
404 |
--------------------------------------------------------------------------------
/endpoints.go:
--------------------------------------------------------------------------------
1 | // Discordgo - Discord bindings for Go
2 | // Available at https://github.com/bwmarrin/discordgo
3 |
4 | // Copyright 2015-2016 Bruce Marriner . All rights reserved.
5 | // Use of this source code is governed by a BSD-style
6 | // license that can be found in the LICENSE file.
7 |
8 | // This file contains variables for all known Discord end points. All functions
9 | // throughout the Discordgo package use these variables for all connections
10 | // to Discord. These are all exported and you may modify them if needed.
11 |
12 | package discordgo
13 |
14 | import "strconv"
15 |
16 | // APIVersion is the Discord API version used for the REST and Websocket API.
17 | var APIVersion = "9"
18 |
19 | // Known Discord API Endpoints.
20 | var (
21 | EndpointStatus = "https://status.discord.com/api/v2/"
22 | EndpointSm = EndpointStatus + "scheduled-maintenances/"
23 | EndpointSmActive = EndpointSm + "active.json"
24 | EndpointSmUpcoming = EndpointSm + "upcoming.json"
25 |
26 | EndpointDiscord = "https://discord.com/"
27 | EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/"
28 | EndpointGuilds = EndpointAPI + "guilds/"
29 | EndpointChannels = EndpointAPI + "channels/"
30 | EndpointUsers = EndpointAPI + "users/"
31 | EndpointGateway = EndpointAPI + "gateway"
32 | EndpointGatewayBot = EndpointGateway + "/bot"
33 | EndpointWebhooks = EndpointAPI + "webhooks/"
34 | EndpointStickers = EndpointAPI + "stickers/"
35 | EndpointStageInstances = EndpointAPI + "stage-instances"
36 |
37 | EndpointCDN = "https://cdn.discordapp.com/"
38 | EndpointCDNAttachments = EndpointCDN + "attachments/"
39 | EndpointCDNAvatars = EndpointCDN + "avatars/"
40 | EndpointCDNIcons = EndpointCDN + "icons/"
41 | EndpointCDNSplashes = EndpointCDN + "splashes/"
42 | EndpointCDNChannelIcons = EndpointCDN + "channel-icons/"
43 | EndpointCDNBanners = EndpointCDN + "banners/"
44 | EndpointCDNGuilds = EndpointCDN + "guilds/"
45 |
46 | EndpointVoice = EndpointAPI + "/voice/"
47 | EndpointVoiceRegions = EndpointVoice + "regions"
48 |
49 | EndpointUser = func(uID string) string { return EndpointUsers + uID }
50 | EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" }
51 | EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" }
52 | EndpointDefaultUserAvatar = func(uDiscriminator string) string {
53 | uDiscriminatorInt, _ := strconv.Atoi(uDiscriminator)
54 | return EndpointCDN + "embed/avatars/" + strconv.Itoa(uDiscriminatorInt%5) + ".png"
55 | }
56 | EndpointUserBanner = func(uID, cID string) string {
57 | return EndpointCDNBanners + uID + "/" + cID + ".png"
58 | }
59 | EndpointUserBannerAnimated = func(uID, cID string) string {
60 | return EndpointCDNBanners + uID + "/" + cID + ".gif"
61 | }
62 |
63 | EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
64 | EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
65 | EndpointUserGuildMember = func(uID, gID string) string { return EndpointUserGuild(uID, gID) + "/member" }
66 | EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
67 | EndpointUserApplicationRoleConnection = func(aID string) string { return EndpointUsers + "@me/applications/" + aID + "/role-connection" }
68 | EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
69 |
70 | EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
71 | EndpointGuildAutoModeration = func(gID string) string { return EndpointGuild(gID) + "/auto-moderation" }
72 | EndpointGuildAutoModerationRules = func(gID string) string { return EndpointGuildAutoModeration(gID) + "/rules" }
73 | EndpointGuildAutoModerationRule = func(gID, rID string) string { return EndpointGuildAutoModerationRules(gID) + "/" + rID }
74 | EndpointGuildThreads = func(gID string) string { return EndpointGuild(gID) + "/threads" }
75 | EndpointGuildActiveThreads = func(gID string) string { return EndpointGuildThreads(gID) + "/active" }
76 | EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" }
77 | EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" }
78 | EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" }
79 | EndpointGuildMembersSearch = func(gID string) string { return EndpointGuildMembers(gID) + "/search" }
80 | EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID }
81 | EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID }
82 | EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" }
83 | EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID }
84 | EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" }
85 | EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID }
86 | EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" }
87 | EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID }
88 | EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" }
89 | EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" }
90 | EndpointGuildEmbed = EndpointGuildWidget
91 | EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" }
92 | EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" }
93 | EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" }
94 | EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" }
95 | EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" }
96 | EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" }
97 | EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" }
98 | EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID }
99 | EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" }
100 | EndpointGuildBannerAnimated = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".gif" }
101 | EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" }
102 | EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID }
103 | EndpointStageInstance = func(cID string) string { return EndpointStageInstances + "/" + cID }
104 | EndpointGuildScheduledEvents = func(gID string) string { return EndpointGuilds + gID + "/scheduled-events" }
105 | EndpointGuildScheduledEvent = func(gID, eID string) string { return EndpointGuilds + gID + "/scheduled-events/" + eID }
106 | EndpointGuildScheduledEventUsers = func(gID, eID string) string { return EndpointGuildScheduledEvent(gID, eID) + "/users" }
107 | EndpointGuildTemplate = func(tID string) string { return EndpointGuilds + "/templates/" + tID }
108 | EndpointGuildTemplates = func(gID string) string { return EndpointGuilds + gID + "/templates" }
109 | EndpointGuildTemplateSync = func(gID, tID string) string { return EndpointGuilds + gID + "/templates/" + tID }
110 | EndpointGuildMemberAvatar = func(gId, uID, aID string) string {
111 | return EndpointCDNGuilds + gId + "/users/" + uID + "/avatars/" + aID + ".png"
112 | }
113 | EndpointGuildMemberAvatarAnimated = func(gId, uID, aID string) string {
114 | return EndpointCDNGuilds + gId + "/users/" + uID + "/avatars/" + aID + ".gif"
115 | }
116 |
117 | EndpointChannel = func(cID string) string { return EndpointChannels + cID }
118 | EndpointChannelThreads = func(cID string) string { return EndpointChannel(cID) + "/threads" }
119 | EndpointChannelActiveThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/active" }
120 | EndpointChannelPublicArchivedThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/archived/public" }
121 | EndpointChannelPrivateArchivedThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/archived/private" }
122 | EndpointChannelJoinedPrivateArchivedThreads = func(cID string) string { return EndpointChannel(cID) + "/users/@me/threads/archived/private" }
123 | EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" }
124 | EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID }
125 | EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" }
126 | EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" }
127 | EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" }
128 | EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID }
129 | EndpointChannelMessageThread = func(cID, mID string) string { return EndpointChannelMessage(cID, mID) + "/threads" }
130 | EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" }
131 | EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" }
132 | EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID }
133 | EndpointChannelMessageCrosspost = func(cID, mID string) string { return EndpointChannel(cID) + "/messages/" + mID + "/crosspost" }
134 | EndpointChannelFollow = func(cID string) string { return EndpointChannel(cID) + "/followers" }
135 | EndpointThreadMembers = func(tID string) string { return EndpointChannel(tID) + "/thread-members" }
136 | EndpointThreadMember = func(tID, mID string) string { return EndpointThreadMembers(tID) + "/" + mID }
137 |
138 | EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" }
139 |
140 | EndpointSticker = func(sID string) string { return EndpointStickers + sID }
141 | EndpointNitroStickersPacks = EndpointAPI + "/sticker-packs"
142 |
143 | EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" }
144 | EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID }
145 | EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token }
146 | EndpointWebhookMessage = func(wID, token, messageID string) string {
147 | return EndpointWebhookToken(wID, token) + "/messages/" + messageID
148 | }
149 |
150 | EndpointMessageReactionsAll = func(cID, mID string) string {
151 | return EndpointChannelMessage(cID, mID) + "/reactions"
152 | }
153 | EndpointMessageReactions = func(cID, mID, eID string) string {
154 | return EndpointChannelMessage(cID, mID) + "/reactions/" + eID
155 | }
156 | EndpointMessageReaction = func(cID, mID, eID, uID string) string {
157 | return EndpointMessageReactions(cID, mID, eID) + "/" + uID
158 | }
159 |
160 | EndpointApplicationGlobalCommands = func(aID string) string {
161 | return EndpointApplication(aID) + "/commands"
162 | }
163 | EndpointApplicationGlobalCommand = func(aID, cID string) string {
164 | return EndpointApplicationGlobalCommands(aID) + "/" + cID
165 | }
166 |
167 | EndpointApplicationGuildCommands = func(aID, gID string) string {
168 | return EndpointApplication(aID) + "/guilds/" + gID + "/commands"
169 | }
170 | EndpointApplicationGuildCommand = func(aID, gID, cID string) string {
171 | return EndpointApplicationGuildCommands(aID, gID) + "/" + cID
172 | }
173 | EndpointApplicationCommandPermissions = func(aID, gID, cID string) string {
174 | return EndpointApplicationGuildCommand(aID, gID, cID) + "/permissions"
175 | }
176 | EndpointApplicationCommandsGuildPermissions = func(aID, gID string) string {
177 | return EndpointApplicationGuildCommands(aID, gID) + "/permissions"
178 | }
179 | EndpointInteraction = func(aID, iToken string) string {
180 | return EndpointAPI + "interactions/" + aID + "/" + iToken
181 | }
182 | EndpointInteractionResponse = func(iID, iToken string) string {
183 | return EndpointInteraction(iID, iToken) + "/callback"
184 | }
185 | EndpointInteractionResponseActions = func(aID, iToken string) string {
186 | return EndpointWebhookMessage(aID, iToken, "@original")
187 | }
188 | EndpointFollowupMessage = func(aID, iToken string) string {
189 | return EndpointWebhookToken(aID, iToken)
190 | }
191 | EndpointFollowupMessageActions = func(aID, iToken, mID string) string {
192 | return EndpointWebhookMessage(aID, iToken, mID)
193 | }
194 |
195 | EndpointGuildCreate = EndpointAPI + "guilds"
196 |
197 | EndpointInvite = func(iID string) string { return EndpointAPI + "invites/" + iID }
198 |
199 | EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" }
200 | EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" }
201 |
202 | EndpointApplications = EndpointAPI + "applications"
203 | EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID }
204 | EndpointApplicationRoleConnectionMetadata = func(aID string) string { return EndpointApplication(aID) + "/role-connections/metadata" }
205 |
206 | EndpointOAuth2 = EndpointAPI + "oauth2/"
207 | EndpointOAuth2Applications = EndpointOAuth2 + "applications"
208 | EndpointOAuth2Application = func(aID string) string { return EndpointOAuth2Applications + "/" + aID }
209 | EndpointOAuth2ApplicationsBot = func(aID string) string { return EndpointOAuth2Applications + "/" + aID + "/bot" }
210 | EndpointOAuth2ApplicationAssets = func(aID string) string { return EndpointOAuth2Applications + "/" + aID + "/assets" }
211 |
212 | // TODO: Deprecated, remove in the next release
213 | EndpointOauth2 = EndpointOAuth2
214 | EndpointOauth2Applications = EndpointOAuth2Applications
215 | EndpointOauth2Application = EndpointOAuth2Application
216 | EndpointOauth2ApplicationsBot = EndpointOAuth2ApplicationsBot
217 | EndpointOauth2ApplicationAssets = EndpointOAuth2ApplicationAssets
218 | )
219 |
--------------------------------------------------------------------------------