From e4c0ca0f4807a36a6cc0ea5f793905539afa4648 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 12 Mar 2022 17:06:39 +0100 Subject: Switch to discordgo upstream again (#1759) * Switch to upstream discordgo again * Fix discord api changes --- bridge/discord/discord.go | 2 +- bridge/discord/handlers.go | 4 +- bridge/discord/handlers_test.go | 2 +- bridge/discord/helpers.go | 2 +- bridge/discord/transmitter/transmitter.go | 2 +- bridge/discord/transmitter/utils.go | 2 +- bridge/discord/webhook.go | 4 +- go.mod | 2 +- go.sum | 5 +- vendor/github.com/bwmarrin/discordgo/.gitignore | 5 + vendor/github.com/bwmarrin/discordgo/.golangci.yml | 19 + vendor/github.com/bwmarrin/discordgo/.travis.yml | 17 + vendor/github.com/bwmarrin/discordgo/LICENSE | 28 + vendor/github.com/bwmarrin/discordgo/README.md | 105 + vendor/github.com/bwmarrin/discordgo/components.go | 241 ++ vendor/github.com/bwmarrin/discordgo/discord.go | 60 + vendor/github.com/bwmarrin/discordgo/endpoints.go | 210 ++ vendor/github.com/bwmarrin/discordgo/event.go | 247 ++ .../github.com/bwmarrin/discordgo/eventhandlers.go | 1342 +++++++++ vendor/github.com/bwmarrin/discordgo/events.go | 372 +++ .../github.com/bwmarrin/discordgo/interactions.go | 568 ++++ vendor/github.com/bwmarrin/discordgo/locales.go | 83 + vendor/github.com/bwmarrin/discordgo/logging.go | 103 + vendor/github.com/bwmarrin/discordgo/message.go | 541 ++++ vendor/github.com/bwmarrin/discordgo/mkdocs.yml | 17 + vendor/github.com/bwmarrin/discordgo/oauth2.go | 154 ++ vendor/github.com/bwmarrin/discordgo/ratelimit.go | 197 ++ vendor/github.com/bwmarrin/discordgo/restapi.go | 2871 ++++++++++++++++++++ vendor/github.com/bwmarrin/discordgo/state.go | 1277 +++++++++ vendor/github.com/bwmarrin/discordgo/structs.go | 2038 ++++++++++++++ vendor/github.com/bwmarrin/discordgo/types.go | 47 + vendor/github.com/bwmarrin/discordgo/user.go | 107 + vendor/github.com/bwmarrin/discordgo/util.go | 110 + vendor/github.com/bwmarrin/discordgo/voice.go | 914 +++++++ vendor/github.com/bwmarrin/discordgo/webhook.go | 49 + vendor/github.com/bwmarrin/discordgo/wsapi.go | 912 +++++++ .../github.com/matterbridge/discordgo/.gitignore | 2 - .../github.com/matterbridge/discordgo/.travis.yml | 16 - vendor/github.com/matterbridge/discordgo/LICENSE | 28 - vendor/github.com/matterbridge/discordgo/README.md | 105 - .../github.com/matterbridge/discordgo/discord.go | 161 -- .../github.com/matterbridge/discordgo/endpoints.go | 153 -- vendor/github.com/matterbridge/discordgo/event.go | 247 -- .../matterbridge/discordgo/eventhandlers.go | 1054 ------- vendor/github.com/matterbridge/discordgo/events.go | 269 -- .../matterbridge/discordgo/interactions.go | 54 - .../github.com/matterbridge/discordgo/logging.go | 103 - .../github.com/matterbridge/discordgo/message.go | 449 --- .../github.com/matterbridge/discordgo/mkdocs.yml | 17 - vendor/github.com/matterbridge/discordgo/oauth2.go | 173 -- .../github.com/matterbridge/discordgo/ratelimit.go | 197 -- .../github.com/matterbridge/discordgo/restapi.go | 2387 ---------------- vendor/github.com/matterbridge/discordgo/state.go | 1104 -------- .../github.com/matterbridge/discordgo/structs.go | 1339 --------- vendor/github.com/matterbridge/discordgo/types.go | 57 - vendor/github.com/matterbridge/discordgo/user.go | 106 - vendor/github.com/matterbridge/discordgo/util.go | 17 - vendor/github.com/matterbridge/discordgo/voice.go | 908 ------- vendor/github.com/matterbridge/discordgo/wsapi.go | 901 ------ vendor/modules.txt | 6 +- 60 files changed, 12650 insertions(+), 9862 deletions(-) create mode 100644 vendor/github.com/bwmarrin/discordgo/.gitignore create mode 100644 vendor/github.com/bwmarrin/discordgo/.golangci.yml create mode 100644 vendor/github.com/bwmarrin/discordgo/.travis.yml create mode 100644 vendor/github.com/bwmarrin/discordgo/LICENSE create mode 100644 vendor/github.com/bwmarrin/discordgo/README.md create mode 100644 vendor/github.com/bwmarrin/discordgo/components.go create mode 100644 vendor/github.com/bwmarrin/discordgo/discord.go create mode 100644 vendor/github.com/bwmarrin/discordgo/endpoints.go create mode 100644 vendor/github.com/bwmarrin/discordgo/event.go create mode 100644 vendor/github.com/bwmarrin/discordgo/eventhandlers.go create mode 100644 vendor/github.com/bwmarrin/discordgo/events.go create mode 100644 vendor/github.com/bwmarrin/discordgo/interactions.go create mode 100644 vendor/github.com/bwmarrin/discordgo/locales.go create mode 100644 vendor/github.com/bwmarrin/discordgo/logging.go create mode 100644 vendor/github.com/bwmarrin/discordgo/message.go create mode 100644 vendor/github.com/bwmarrin/discordgo/mkdocs.yml create mode 100644 vendor/github.com/bwmarrin/discordgo/oauth2.go create mode 100644 vendor/github.com/bwmarrin/discordgo/ratelimit.go create mode 100644 vendor/github.com/bwmarrin/discordgo/restapi.go create mode 100644 vendor/github.com/bwmarrin/discordgo/state.go create mode 100644 vendor/github.com/bwmarrin/discordgo/structs.go create mode 100644 vendor/github.com/bwmarrin/discordgo/types.go create mode 100644 vendor/github.com/bwmarrin/discordgo/user.go create mode 100644 vendor/github.com/bwmarrin/discordgo/util.go create mode 100644 vendor/github.com/bwmarrin/discordgo/voice.go create mode 100644 vendor/github.com/bwmarrin/discordgo/webhook.go create mode 100644 vendor/github.com/bwmarrin/discordgo/wsapi.go delete mode 100644 vendor/github.com/matterbridge/discordgo/.gitignore delete mode 100644 vendor/github.com/matterbridge/discordgo/.travis.yml delete mode 100644 vendor/github.com/matterbridge/discordgo/LICENSE delete mode 100644 vendor/github.com/matterbridge/discordgo/README.md delete mode 100644 vendor/github.com/matterbridge/discordgo/discord.go delete mode 100644 vendor/github.com/matterbridge/discordgo/endpoints.go delete mode 100644 vendor/github.com/matterbridge/discordgo/event.go delete mode 100644 vendor/github.com/matterbridge/discordgo/eventhandlers.go delete mode 100644 vendor/github.com/matterbridge/discordgo/events.go delete mode 100644 vendor/github.com/matterbridge/discordgo/interactions.go delete mode 100644 vendor/github.com/matterbridge/discordgo/logging.go delete mode 100644 vendor/github.com/matterbridge/discordgo/message.go delete mode 100644 vendor/github.com/matterbridge/discordgo/mkdocs.yml delete mode 100644 vendor/github.com/matterbridge/discordgo/oauth2.go delete mode 100644 vendor/github.com/matterbridge/discordgo/ratelimit.go delete mode 100644 vendor/github.com/matterbridge/discordgo/restapi.go delete mode 100644 vendor/github.com/matterbridge/discordgo/state.go delete mode 100644 vendor/github.com/matterbridge/discordgo/structs.go delete mode 100644 vendor/github.com/matterbridge/discordgo/types.go delete mode 100644 vendor/github.com/matterbridge/discordgo/user.go delete mode 100644 vendor/github.com/matterbridge/discordgo/util.go delete mode 100644 vendor/github.com/matterbridge/discordgo/voice.go delete mode 100644 vendor/github.com/matterbridge/discordgo/wsapi.go diff --git a/bridge/discord/discord.go b/bridge/discord/discord.go index 4e86835b..1c2e8ca4 100644 --- a/bridge/discord/discord.go +++ b/bridge/discord/discord.go @@ -10,8 +10,8 @@ import ( "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/discord/transmitter" "github.com/42wim/matterbridge/bridge/helper" + "github.com/bwmarrin/discordgo" lru "github.com/hashicorp/golang-lru" - "github.com/matterbridge/discordgo" ) const ( diff --git a/bridge/discord/handlers.go b/bridge/discord/handlers.go index 42c5f383..d2613e1a 100644 --- a/bridge/discord/handlers.go +++ b/bridge/discord/handlers.go @@ -2,8 +2,8 @@ package bdiscord import ( "github.com/42wim/matterbridge/bridge/config" + "github.com/bwmarrin/discordgo" "github.com/davecgh/go-spew/spew" - "github.com/matterbridge/discordgo" ) func (b *Bdiscord) messageDelete(s *discordgo.Session, m *discordgo.MessageDelete) { //nolint:unparam @@ -56,7 +56,7 @@ func (b *Bdiscord) messageUpdate(s *discordgo.Session, m *discordgo.MessageUpdat return } // only when message is actually edited - if m.Message.EditedTimestamp != "" { + if m.Message.EditedTimestamp != nil { b.Log.Debugf("Sending edit message") m.Content += b.GetString("EditSuffix") msg := &discordgo.MessageCreate{ diff --git a/bridge/discord/handlers_test.go b/bridge/discord/handlers_test.go index a809dadd..915d9b19 100644 --- a/bridge/discord/handlers_test.go +++ b/bridge/discord/handlers_test.go @@ -3,7 +3,7 @@ package bdiscord import ( "testing" - "github.com/matterbridge/discordgo" + "github.com/bwmarrin/discordgo" "github.com/stretchr/testify/assert" ) diff --git a/bridge/discord/helpers.go b/bridge/discord/helpers.go index 4e453ad7..2e18f46c 100644 --- a/bridge/discord/helpers.go +++ b/bridge/discord/helpers.go @@ -6,7 +6,7 @@ import ( "strings" "unicode" - "github.com/matterbridge/discordgo" + "github.com/bwmarrin/discordgo" ) func (b *Bdiscord) getAllowedMentions() *discordgo.MessageAllowedMentions { diff --git a/bridge/discord/transmitter/transmitter.go b/bridge/discord/transmitter/transmitter.go index f327dfb7..71407a1d 100644 --- a/bridge/discord/transmitter/transmitter.go +++ b/bridge/discord/transmitter/transmitter.go @@ -20,7 +20,7 @@ import ( "sync" "time" - "github.com/matterbridge/discordgo" + "github.com/bwmarrin/discordgo" log "github.com/sirupsen/logrus" ) diff --git a/bridge/discord/transmitter/utils.go b/bridge/discord/transmitter/utils.go index f42e81eb..042aa505 100644 --- a/bridge/discord/transmitter/utils.go +++ b/bridge/discord/transmitter/utils.go @@ -1,7 +1,7 @@ package transmitter import ( - "github.com/matterbridge/discordgo" + "github.com/bwmarrin/discordgo" ) // isDiscordPermissionError returns false for nil, and true if a Discord RESTError with code discordgo.ErrorCodeMissionPermissions diff --git a/bridge/discord/webhook.go b/bridge/discord/webhook.go index 3afb942d..c34fc945 100644 --- a/bridge/discord/webhook.go +++ b/bridge/discord/webhook.go @@ -5,7 +5,7 @@ import ( "github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/helper" - "github.com/matterbridge/discordgo" + "github.com/bwmarrin/discordgo" ) // shouldMessageUseWebhooks checks if have a channel specific webhook, if we're not using auto webhooks @@ -89,7 +89,7 @@ func (b *Bdiscord) webhookSend(msg *config.Message, channelID string) (*discordg &discordgo.WebhookParams{ Username: msg.Username, AvatarURL: msg.Avatar, - File: &file, + Files: []*discordgo.File{&file}, Content: content, AllowedMentions: b.getAllowedMentions(), }, diff --git a/go.mod b/go.mod index a4d051f2..d8c5aee2 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560 github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c github.com/SevereCloud/vksdk/v2 v2.13.1 + github.com/bwmarrin/discordgo v0.24.0 github.com/d5/tengo/v2 v2.10.0 github.com/davecgh/go-spew v1.1.1 github.com/fsnotify/fsnotify v1.5.1 @@ -24,7 +25,6 @@ require ( github.com/lrstanley/girc v0.0.0-20211023233735-147f0ff77566 github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696 - github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be github.com/matterbridge/gozulipbot v0.0.0-20211023205727-a19d6c1f3b75 github.com/matterbridge/logrus-prefixed-formatter v0.5.3-0.20200523233437-d971309a77ba diff --git a/go.sum b/go.sum index 84125ab2..787fce3c 100644 --- a/go.sum +++ b/go.sum @@ -290,6 +290,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/bwmarrin/discordgo v0.24.0 h1:Gw4MYxqHdvhO99A3nXnSLy97z5pmIKHZVJ1JY5ZDPqY= +github.com/bwmarrin/discordgo v0.24.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -1079,8 +1081,6 @@ github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxI github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696 h1:pmPKkN3RJM9wVMZidR99epzK0+gatQiqVtvP1FacZcQ= github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696/go.mod h1:c6MxwqHD+0HvtAJjsHMIdPCiAwGiQwPRPTp69ACMg8A= -github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 h1:4J2YZuY8dIYrxbLMsWGqPZb/B59ygCwSBkyZHez5PSY= -github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7/go.mod h1:411nZYv0UMMrtppR5glXop1foboJiFAowy+42U+Ahvw= github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be h1:zlirT+LngOJ60G6FVzI87DljGZLUnfNzmXja61EjtYM= github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q= github.com/matterbridge/gomatrix v0.0.0-20220205235239-607eb9ee6419 h1:dx8x2J3EsVwP3hBGNmVT/otz4b42p7TRQ6Cu4BK2910= @@ -1791,6 +1791,7 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/vendor/github.com/bwmarrin/discordgo/.gitignore b/vendor/github.com/bwmarrin/discordgo/.gitignore new file mode 100644 index 00000000..681a96b1 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/.gitignore @@ -0,0 +1,5 @@ +# IDE-specific metadata +.idea/ + +# Environment variables. Useful for examples. +.env diff --git a/vendor/github.com/bwmarrin/discordgo/.golangci.yml b/vendor/github.com/bwmarrin/discordgo/.golangci.yml new file mode 100644 index 00000000..dd9d2e5b --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/.golangci.yml @@ -0,0 +1,19 @@ +linters: + disable-all: true + enable: + # - staticcheck + # - unused + - golint + +linters-settings: + staticcheck: + go: "1.13" + + checks: ["all"] + + unused: + go: "1.13" + +issues: + include: + - EXC0002 diff --git a/vendor/github.com/bwmarrin/discordgo/.travis.yml b/vendor/github.com/bwmarrin/discordgo/.travis.yml new file mode 100644 index 00000000..5d9cea3e --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/.travis.yml @@ -0,0 +1,17 @@ +language: go +go: + - 1.13.x + - 1.14.x + - 1.15.x + - 1.16.x +env: + - GO111MODULE=on +install: + - go get github.com/bwmarrin/discordgo + - go get -v . + - go get -v golang.org/x/lint/golint +script: + - diff <(gofmt -d .) <(echo -n) + - go vet -x ./... + - golint -set_exit_status ./... + - go test -v -race ./... diff --git a/vendor/github.com/bwmarrin/discordgo/LICENSE b/vendor/github.com/bwmarrin/discordgo/LICENSE new file mode 100644 index 00000000..8d062ea5 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015, Bruce Marriner +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of discordgo nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/bwmarrin/discordgo/README.md b/vendor/github.com/bwmarrin/discordgo/README.md new file mode 100644 index 00000000..2bcd43b8 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/README.md @@ -0,0 +1,105 @@ +# DiscordGo + +[![Go Reference](https://pkg.go.dev/badge/github.com/bwmarrin/discordgo.svg)](https://pkg.go.dev/github.com/bwmarrin/discordgo) [![Go Report Card](https://goreportcard.com/badge/github.com/bwmarrin/discordgo)](https://goreportcard.com/report/github.com/bwmarrin/discordgo) [![Build Status](https://travis-ci.com/bwmarrin/discordgo.svg?branch=master)](https://travis-ci.com/bwmarrin/discordgo) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23discordgo-blue.svg)](https://discord.gg/golang) [![Discord API](https://img.shields.io/badge/Discord%20API-%23go_discordgo-blue.svg)](https://discord.com/invite/discord-api) + +DiscordGo logo + +DiscordGo is a [Go](https://golang.org/) package that provides low level +bindings to the [Discord](https://discord.com/) chat client API. DiscordGo +has nearly complete support for all of the Discord API endpoints, websocket +interface, and voice interface. + +If you would like to help the DiscordGo package please use +[this link](https://discord.com/oauth2/authorize?client_id=173113690092994561&scope=bot) +to add the official DiscordGo test bot **dgo** to your server. This provides +indispensable help to this project. + +* See [dgVoice](https://github.com/bwmarrin/dgvoice) package for an example of +additional voice helper functions and features for DiscordGo. + +* See [dca](https://github.com/bwmarrin/dca) for an **experimental** stand alone +tool that wraps `ffmpeg` to create opus encoded audio appropriate for use with +Discord (and DiscordGo). + +**For help with this package or general Go discussion, please join the [Discord +Gophers](https://discord.gg/golang) chat server.** + +## Getting Started + +### Installing + +This assumes you already have a working Go environment, if not please see +[this page](https://golang.org/doc/install) first. + +`go get` *will always pull the latest tagged release from the master branch.* + +```sh +go get github.com/bwmarrin/discordgo +``` + +### Usage + +Import the package into your project. + +```go +import "github.com/bwmarrin/discordgo" +``` + +Construct a new Discord client which can be used to access the variety of +Discord API functions and to set callback functions for Discord events. + +```go +discord, err := discordgo.New("Bot " + "authentication token") +``` + +See Documentation and Examples below for more detailed information. + + +## Documentation + +**NOTICE**: This library and the Discord API are unfinished. +Because of that there may be major changes to library in the future. + +The DiscordGo code is fairly well documented at this point and is currently +the only documentation available. Both GoDoc and GoWalker (below) present +that information in a nice format. + +- [![Go Reference](https://pkg.go.dev/badge/github.com/bwmarrin/discordgo.svg)](https://pkg.go.dev/github.com/bwmarrin/discordgo) +- [![Go Walker](https://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/bwmarrin/discordgo) +- Hand crafted documentation coming eventually. + + +## Examples + +Below is a list of examples and other projects using DiscordGo. Please submit +an issue if you would like your project added or removed from this list. + +- [DiscordGo Examples](https://github.com/bwmarrin/discordgo/tree/master/examples) - A collection of example programs written with DiscordGo +- [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) - A curated list of high quality projects using DiscordGo + +## Troubleshooting +For help with common problems please reference the +[Troubleshooting](https://github.com/bwmarrin/discordgo/wiki/Troubleshooting) +section of the project wiki. + + +## Contributing +Contributions are very welcomed, however please follow the below guidelines. + +- First open an issue describing the bug or enhancement so it can be +discussed. +- Try to match current naming conventions as closely as possible. +- This package is intended to be a low level direct mapping of the Discord API, +so please avoid adding enhancements outside of that scope without first +discussing it. +- Create a Pull Request with your changes against the master branch. + + +## List of Discord APIs + +See [this chart](https://abal.moe/Discord/Libraries.html) for a feature +comparison and list of other Discord API libraries. + +## Special Thanks + +[Chris Rhodes](https://github.com/iopred) - For the DiscordGo logo and tons of PRs. diff --git a/vendor/github.com/bwmarrin/discordgo/components.go b/vendor/github.com/bwmarrin/discordgo/components.go new file mode 100644 index 00000000..00cbbf19 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/components.go @@ -0,0 +1,241 @@ +package discordgo + +import ( + "encoding/json" + "fmt" +) + +// ComponentType is type of component. +type ComponentType uint + +// MessageComponent types. +const ( + ActionsRowComponent ComponentType = 1 + ButtonComponent ComponentType = 2 + SelectMenuComponent ComponentType = 3 + TextInputComponent ComponentType = 4 +) + +// MessageComponent is a base interface for all message components. +type MessageComponent interface { + json.Marshaler + Type() ComponentType +} + +type unmarshalableMessageComponent struct { + MessageComponent +} + +// UnmarshalJSON is a helper function to unmarshal MessageComponent object. +func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error { + var v struct { + Type ComponentType `json:"type"` + } + err := json.Unmarshal(src, &v) + if err != nil { + return err + } + + switch v.Type { + case ActionsRowComponent: + umc.MessageComponent = &ActionsRow{} + case ButtonComponent: + umc.MessageComponent = &Button{} + case SelectMenuComponent: + umc.MessageComponent = &SelectMenu{} + case TextInputComponent: + umc.MessageComponent = &TextInput{} + default: + return fmt.Errorf("unknown component type: %d", v.Type) + } + return json.Unmarshal(src, umc.MessageComponent) +} + +// MessageComponentFromJSON is a helper function for unmarshaling message components +func MessageComponentFromJSON(b []byte) (MessageComponent, error) { + var u unmarshalableMessageComponent + err := u.UnmarshalJSON(b) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal into MessageComponent: %w", err) + } + return u.MessageComponent, nil +} + +// ActionsRow is a container for components within one row. +type ActionsRow struct { + Components []MessageComponent `json:"components"` +} + +// MarshalJSON is a method for marshaling ActionsRow to a JSON object. +func (r ActionsRow) MarshalJSON() ([]byte, error) { + type actionsRow ActionsRow + + return json.Marshal(struct { + actionsRow + Type ComponentType `json:"type"` + }{ + actionsRow: actionsRow(r), + Type: r.Type(), + }) +} + +// UnmarshalJSON is a helper function to unmarshal Actions Row. +func (r *ActionsRow) UnmarshalJSON(data []byte) error { + var v struct { + RawComponents []unmarshalableMessageComponent `json:"components"` + } + err := json.Unmarshal(data, &v) + if err != nil { + return err + } + r.Components = make([]MessageComponent, len(v.RawComponents)) + for i, v := range v.RawComponents { + r.Components[i] = v.MessageComponent + } + + return err +} + +// Type is a method to get the type of a component. +func (r ActionsRow) Type() ComponentType { + return ActionsRowComponent +} + +// ButtonStyle is style of button. +type ButtonStyle uint + +// Button styles. +const ( + // PrimaryButton is a button with blurple color. + PrimaryButton ButtonStyle = 1 + // SecondaryButton is a button with grey color. + SecondaryButton ButtonStyle = 2 + // SuccessButton is a button with green color. + SuccessButton ButtonStyle = 3 + // DangerButton is a button with red color. + DangerButton ButtonStyle = 4 + // LinkButton is a special type of button which navigates to a URL. Has grey color. + LinkButton ButtonStyle = 5 +) + +// ComponentEmoji represents button emoji, if it does have one. +type ComponentEmoji struct { + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + Animated bool `json:"animated,omitempty"` +} + +// Button represents button component. +type Button struct { + Label string `json:"label"` + Style ButtonStyle `json:"style"` + Disabled bool `json:"disabled"` + Emoji ComponentEmoji `json:"emoji"` + + // NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID. + URL string `json:"url,omitempty"` + CustomID string `json:"custom_id,omitempty"` +} + +// MarshalJSON is a method for marshaling Button to a JSON object. +func (b Button) MarshalJSON() ([]byte, error) { + type button Button + + if b.Style == 0 { + b.Style = PrimaryButton + } + + return json.Marshal(struct { + button + Type ComponentType `json:"type"` + }{ + button: button(b), + Type: b.Type(), + }) +} + +// Type is a method to get the type of a component. +func (Button) Type() ComponentType { + return ButtonComponent +} + +// SelectMenuOption represents an option for a select menu. +type SelectMenuOption struct { + Label string `json:"label,omitempty"` + Value string `json:"value"` + Description string `json:"description"` + Emoji ComponentEmoji `json:"emoji"` + // Determines whenever option is selected by default or not. + Default bool `json:"default"` +} + +// SelectMenu represents select menu component. +type SelectMenu struct { + CustomID string `json:"custom_id,omitempty"` + // The text which will be shown in the menu if there's no default options or all options was deselected and component was closed. + Placeholder string `json:"placeholder"` + // This value determines the minimal amount of selected items in the menu. + MinValues *int `json:"min_values,omitempty"` + // This value determines the maximal amount of selected items in the menu. + // If MaxValues or MinValues are greater than one then the user can select multiple items in the component. + MaxValues int `json:"max_values,omitempty"` + Options []SelectMenuOption `json:"options"` + Disabled bool `json:"disabled"` +} + +// Type is a method to get the type of a component. +func (SelectMenu) Type() ComponentType { + return SelectMenuComponent +} + +// MarshalJSON is a method for marshaling SelectMenu to a JSON object. +func (m SelectMenu) MarshalJSON() ([]byte, error) { + type selectMenu SelectMenu + + return json.Marshal(struct { + selectMenu + Type ComponentType `json:"type"` + }{ + selectMenu: selectMenu(m), + Type: m.Type(), + }) +} + +// TextInput represents text input component. +type TextInput struct { + CustomID string `json:"custom_id"` + Label string `json:"label"` + Style TextInputStyle `json:"style"` + Placeholder string `json:"placeholder,omitempty"` + Value string `json:"value,omitempty"` + Required bool `json:"required,omitempty"` + MinLength int `json:"min_length,omitempty"` + MaxLength int `json:"max_length,omitempty"` +} + +// Type is a method to get the type of a component. +func (TextInput) Type() ComponentType { + return TextInputComponent +} + +// MarshalJSON is a method for marshaling TextInput to a JSON object. +func (m TextInput) MarshalJSON() ([]byte, error) { + type inputText TextInput + + return json.Marshal(struct { + inputText + Type ComponentType `json:"type"` + }{ + inputText: inputText(m), + Type: m.Type(), + }) +} + +// TextInputStyle is style of text in TextInput component. +type TextInputStyle uint + +// Text styles +const ( + TextInputShort TextInputStyle = 1 + TextInputParagraph TextInputStyle = 2 +) diff --git a/vendor/github.com/bwmarrin/discordgo/discord.go b/vendor/github.com/bwmarrin/discordgo/discord.go new file mode 100644 index 00000000..74f4190a --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/discord.go @@ -0,0 +1,60 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains high level helper functions and easy entry points for the +// entire discordgo package. These functions are being developed and are very +// experimental at this point. They will most likely change so please use the +// low level functions if that's a problem. + +// Package discordgo provides Discord binding for Go +package discordgo + +import ( + "net/http" + "runtime" + "time" +) + +// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) +const VERSION = "0.24.0" + +// New creates a new Discord session with provided token. +// If the token is for a bot, it must be prefixed with "Bot " +// e.g. "Bot ..." +// Or if it is an OAuth2 token, it must be prefixed with "Bearer " +// e.g. "Bearer ..." +func New(token string) (s *Session, err error) { + + // Create an empty Session interface. + s = &Session{ + State: NewState(), + Ratelimiter: NewRatelimiter(), + StateEnabled: true, + Compress: true, + ShouldReconnectOnError: true, + ShardID: 0, + ShardCount: 1, + MaxRestRetries: 3, + Client: &http.Client{Timeout: (20 * time.Second)}, + UserAgent: "DiscordBot (https://github.com/bwmarrin/discordgo, v" + VERSION + ")", + sequence: new(int64), + LastHeartbeatAck: time.Now().UTC(), + } + + // Initilize the Identify Package with defaults + // These can be modified prior to calling Open() + s.Identify.Compress = true + s.Identify.LargeThreshold = 250 + s.Identify.GuildSubscriptions = true + s.Identify.Properties.OS = runtime.GOOS + s.Identify.Properties.Browser = "DiscordGo v" + VERSION + s.Identify.Intents = IntentsAllWithoutPrivileged + s.Identify.Token = token + s.Token = token + + return +} diff --git a/vendor/github.com/bwmarrin/discordgo/endpoints.go b/vendor/github.com/bwmarrin/discordgo/endpoints.go new file mode 100644 index 00000000..d39a175b --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/endpoints.go @@ -0,0 +1,210 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains variables for all known Discord end points. All functions +// throughout the Discordgo package use these variables for all connections +// to Discord. These are all exported and you may modify them if needed. + +package discordgo + +import "strconv" + +// APIVersion is the Discord API version used for the REST and Websocket API. +var APIVersion = "9" + +// Known Discord API Endpoints. +var ( + EndpointStatus = "https://status.discord.com/api/v2/" + EndpointSm = EndpointStatus + "scheduled-maintenances/" + EndpointSmActive = EndpointSm + "active.json" + EndpointSmUpcoming = EndpointSm + "upcoming.json" + + EndpointDiscord = "https://discord.com/" + EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/" + EndpointGuilds = EndpointAPI + "guilds/" + EndpointChannels = EndpointAPI + "channels/" + EndpointUsers = EndpointAPI + "users/" + EndpointGateway = EndpointAPI + "gateway" + EndpointGatewayBot = EndpointGateway + "/bot" + EndpointWebhooks = EndpointAPI + "webhooks/" + EndpointStickers = EndpointAPI + "stickers/" + + EndpointCDN = "https://cdn.discordapp.com/" + EndpointCDNAttachments = EndpointCDN + "attachments/" + EndpointCDNAvatars = EndpointCDN + "avatars/" + EndpointCDNIcons = EndpointCDN + "icons/" + EndpointCDNSplashes = EndpointCDN + "splashes/" + EndpointCDNChannelIcons = EndpointCDN + "channel-icons/" + EndpointCDNBanners = EndpointCDN + "banners/" + EndpointCDNGuilds = EndpointCDN + "guilds/" + + EndpointVoice = EndpointAPI + "/voice/" + EndpointVoiceRegions = EndpointVoice + "regions" + + // TODO: EndpointUserGuildMember + + EndpointUser = func(uID string) string { return EndpointUsers + uID } + EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" } + EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" } + EndpointDefaultUserAvatar = func(uDiscriminator string) string { + uDiscriminatorInt, _ := strconv.Atoi(uDiscriminator) + return EndpointCDN + "embed/avatars/" + strconv.Itoa(uDiscriminatorInt%5) + ".png" + } + EndpointUserBanner = func(uID, cID string) string { + return EndpointCDNBanners + uID + "/" + cID + ".png" + } + EndpointUserBannerAnimated = func(uID, cID string) string { + return EndpointCDNBanners + uID + "/" + cID + ".gif" + } + + EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" } + EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } + EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } + EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } + + EndpointGuild = func(gID string) string { return EndpointGuilds + gID } + EndpointGuildThreads = func(gID string) string { return EndpointGuild(gID) + "/threads" } + EndpointGuildActiveThreads = func(gID string) string { return EndpointGuildThreads(gID) + "/active" } + EndpointGuildPreview = func(gID string) string { return EndpointGuilds + gID + "/preview" } + EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" } + EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" } + EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } + EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID } + EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" } + EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID } + EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" } + EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID } + EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" } + EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID } + EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" } + EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" } + EndpointGuildEmbed = EndpointGuildWidget + EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } + EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } + EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" } + EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" } + EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" } + EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" } + EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" } + EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID } + EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" } + EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" } + EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID } + EndpointGuildScheduledEvents = func(gID string) string { return EndpointGuilds + gID + "/scheduled-events" } + EndpointGuildScheduledEvent = func(gID, eID string) string { return EndpointGuilds + gID + "/scheduled-events/" + eID } + EndpointGuildScheduledEventUsers = func(gID, eID string) string { return EndpointGuildScheduledEvent(gID, eID) + "/users" } + EndpointGuildTemplate = func(tID string) string { return EndpointGuilds + "/templates/" + tID } + EndpointGuildTemplates = func(gID string) string { return EndpointGuilds + gID + "/templates" } + EndpointGuildTemplateSync = func(gID, tID string) string { return EndpointGuilds + gID + "/templates/" + tID } + EndpointGuildMemberAvatar = func(gId, uID, aID string) string { + return EndpointCDNGuilds + gId + "/users/" + uID + "/avatars/" + aID + ".png" + } + EndpointGuildMemberAvatarAnimated = func(gId, uID, aID string) string { + return EndpointCDNGuilds + gId + "/users/" + uID + "/avatars/" + aID + ".gif" + } + + EndpointChannel = func(cID string) string { return EndpointChannels + cID } + EndpointChannelThreads = func(cID string) string { return EndpointChannel(cID) + "/threads" } + EndpointChannelActiveThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/active" } + EndpointChannelPublicArchivedThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/archived/public" } + EndpointChannelPrivateArchivedThreads = func(cID string) string { return EndpointChannelThreads(cID) + "/archived/private" } + EndpointChannelJoinedPrivateArchivedThreads = func(cID string) string { return EndpointChannel(cID) + "/users/@me/threads/archived/private" } + EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" } + EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID } + EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" } + EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" } + EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" } + EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID } + EndpointChannelMessageThread = func(cID, mID string) string { return EndpointChannelMessage(cID, mID) + "/threads" } + EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" } + EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" } + EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } + EndpointChannelMessageCrosspost = func(cID, mID string) string { return EndpointChannel(cID) + "/messages/" + mID + "/crosspost" } + EndpointChannelFollow = func(cID string) string { return EndpointChannel(cID) + "/followers" } + EndpointThreadMembers = func(tID string) string { return EndpointChannel(tID) + "/thread-members" } + EndpointThreadMember = func(tID, mID string) string { return EndpointThreadMembers(tID) + "/" + mID } + + EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" } + + EndpointSticker = func(sID string) string { return EndpointStickers + sID } + EndpointNitroStickersPacks = EndpointAPI + "/sticker-packs" + + EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" } + EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID } + EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token } + EndpointWebhookMessage = func(wID, token, messageID string) string { + return EndpointWebhookToken(wID, token) + "/messages/" + messageID + } + + EndpointMessageReactionsAll = func(cID, mID string) string { + return EndpointChannelMessage(cID, mID) + "/reactions" + } + EndpointMessageReactions = func(cID, mID, eID string) string { + return EndpointChannelMessage(cID, mID) + "/reactions/" + eID + } + EndpointMessageReaction = func(cID, mID, eID, uID string) string { + return EndpointMessageReactions(cID, mID, eID) + "/" + uID + } + + EndpointApplicationGlobalCommands = func(aID string) string { + return EndpointApplication(aID) + "/commands" + } + EndpointApplicationGlobalCommand = func(aID, cID string) string { + return EndpointApplicationGlobalCommands(aID) + "/" + cID + } + + EndpointApplicationGuildCommands = func(aID, gID string) string { + return EndpointApplication(aID) + "/guilds/" + gID + "/commands" + } + EndpointApplicationGuildCommand = func(aID, gID, cID string) string { + return EndpointApplicationGuildCommands(aID, gID) + "/" + cID + } + EndpointApplicationCommandPermissions = func(aID, gID, cID string) string { + return EndpointApplicationGuildCommand(aID, gID, cID) + "/permissions" + } + EndpointApplicationCommandsGuildPermissions = func(aID, gID string) string { + return EndpointApplicationGuildCommands(aID, gID) + "/permissions" + } + EndpointInteraction = func(aID, iToken string) string { + return EndpointAPI + "interactions/" + aID + "/" + iToken + } + EndpointInteractionResponse = func(iID, iToken string) string { + return EndpointInteraction(iID, iToken) + "/callback" + } + EndpointInteractionResponseActions = func(aID, iToken string) string { + return EndpointWebhookMessage(aID, iToken, "@original") + } + EndpointFollowupMessage = func(aID, iToken string) string { + return EndpointWebhookToken(aID, iToken) + } + EndpointFollowupMessageActions = func(aID, iToken, mID string) string { + return EndpointWebhookMessage(aID, iToken, mID) + } + + EndpointGuildCreate = EndpointAPI + "guilds" + + EndpointInvite = func(iID string) string { return EndpointAPI + "invites/" + iID } + + EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" } + EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" } + + EndpointApplications = EndpointAPI + "applications" + EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID } + + EndpointOAuth2 = EndpointAPI + "oauth2/" + EndpointOAuth2Applications = EndpointOAuth2 + "applications" + EndpointOAuth2Application = func(aID string) string { return EndpointOAuth2Applications + "/" + aID } + EndpointOAuth2ApplicationsBot = func(aID string) string { return EndpointOAuth2Applications + "/" + aID + "/bot" } + EndpointOAuth2ApplicationAssets = func(aID string) string { return EndpointOAuth2Applications + "/" + aID + "/assets" } + + // TODO: Deprecated, remove in the next release + EndpointOauth2 = EndpointOAuth2 + EndpointOauth2Applications = EndpointOAuth2Applications + EndpointOauth2Application = EndpointOAuth2Application + EndpointOauth2ApplicationsBot = EndpointOAuth2ApplicationsBot + EndpointOauth2ApplicationAssets = EndpointOAuth2ApplicationAssets +) diff --git a/vendor/github.com/bwmarrin/discordgo/event.go b/vendor/github.com/bwmarrin/discordgo/event.go new file mode 100644 index 00000000..84dbdc7f --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/event.go @@ -0,0 +1,247 @@ +package discordgo + +// EventHandler is an interface for Discord events. +type EventHandler interface { + // Type returns the type of event this handler belongs to. + Type() string + + // Handle is called whenever an event of Type() happens. + // It is the receivers responsibility to type assert that the interface + // is the expected struct. + Handle(*Session, interface{}) +} + +// EventInterfaceProvider is an interface for providing empty interfaces for +// Discord events. +type EventInterfaceProvider interface { + // Type is the type of event this handler belongs to. + Type() string + + // New returns a new instance of the struct this event handler handles. + // This is called once per event. + // The struct is provided to all handlers of the same Type(). + New() interface{} +} + +// interfaceEventType is the event handler type for interface{} events. +const interfaceEventType = "__INTERFACE__" + +// interfaceEventHandler is an event handler for interface{} events. +type interfaceEventHandler func(*Session, interface{}) + +// Type returns the event type for interface{} events. +func (eh interfaceEventHandler) Type() string { + return interfaceEventType +} + +// Handle is the handler for an interface{} event. +func (eh interfaceEventHandler) Handle(s *Session, i interface{}) { + eh(s, i) +} + +var registeredInterfaceProviders = map[string]EventInterfaceProvider{} + +// registerInterfaceProvider registers a provider so that DiscordGo can +// access it's New() method. +func registerInterfaceProvider(eh EventInterfaceProvider) { + if _, ok := registeredInterfaceProviders[eh.Type()]; ok { + return + // XXX: + // if we should error here, we need to do something with it. + // fmt.Errorf("event %s already registered", eh.Type()) + } + registeredInterfaceProviders[eh.Type()] = eh + return +} + +// eventHandlerInstance is a wrapper around an event handler, as functions +// cannot be compared directly. +type eventHandlerInstance struct { + eventHandler EventHandler +} + +// addEventHandler adds an event handler that will be fired anytime +// the Discord WSAPI matching eventHandler.Type() fires. +func (s *Session) addEventHandler(eventHandler EventHandler) func() { + s.handlersMu.Lock() + defer s.handlersMu.Unlock() + + if s.handlers == nil { + s.handlers = map[string][]*eventHandlerInstance{} + } + + ehi := &eventHandlerInstance{eventHandler} + s.handlers[eventHandler.Type()] = append(s.handlers[eventHandler.Type()], ehi) + + return func() { + s.removeEventHandlerInstance(eventHandler.Type(), ehi) + } +} + +// addEventHandler adds an event handler that will be fired the next time +// the Discord WSAPI matching eventHandler.Type() fires. +func (s *Session) addEventHandlerOnce(eventHandler EventHandler) func() { + s.handlersMu.Lock() + defer s.handlersMu.Unlock() + + if s.onceHandlers == nil { + s.onceHandlers = map[string][]*eventHandlerInstance{} + } + + ehi := &eventHandlerInstance{eventHandler} + s.onceHandlers[eventHandler.Type()] = append(s.onceHandlers[eventHandler.Type()], ehi) + + return func() { + s.removeEventHandlerInstance(eventHandler.Type(), ehi) + } +} + +// AddHandler allows you to add an event handler that will be fired anytime +// the Discord WSAPI event that matches the function fires. +// The first parameter is a *Session, and the second parameter is a pointer +// to a struct corresponding to the event for which you want to listen. +// +// eg: +// Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { +// }) +// +// or: +// Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) { +// }) +// +// List of events can be found at this page, with corresponding names in the +// library for each event: https://discord.com/developers/docs/topics/gateway#event-names +// There are also synthetic events fired by the library internally which are +// available for handling, like Connect, Disconnect, and RateLimit. +// events.go contains all of the Discord WSAPI and synthetic events that can be handled. +// +// The return value of this method is a function, that when called will remove the +// event handler. +func (s *Session) AddHandler(handler interface{}) func() { + eh := handlerForInterface(handler) + + if eh == nil { + s.log(LogError, "Invalid handler type, handler will never be called") + return func() {} + } + + return s.addEventHandler(eh) +} + +// AddHandlerOnce allows you to add an event handler that will be fired the next time +// the Discord WSAPI event that matches the function fires. +// See AddHandler for more details. +func (s *Session) AddHandlerOnce(handler interface{}) func() { + eh := handlerForInterface(handler) + + if eh == nil { + s.log(LogError, "Invalid handler type, handler will never be called") + return func() {} + } + + return s.addEventHandlerOnce(eh) +} + +// removeEventHandler instance removes an event handler instance. +func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance) { + s.handlersMu.Lock() + defer s.handlersMu.Unlock() + + handlers := s.handlers[t] + for i := range handlers { + if handlers[i] == ehi { + s.handlers[t] = append(handlers[:i], handlers[i+1:]...) + } + } + + onceHandlers := s.onceHandlers[t] + for i := range onceHandlers { + if onceHandlers[i] == ehi { + s.onceHandlers[t] = append(onceHandlers[:i], onceHandlers[i+1:]...) + } + } +} + +// Handles calling permanent and once handlers for an event type. +func (s *Session) handle(t string, i interface{}) { + for _, eh := range s.handlers[t] { + if s.SyncEvents { + eh.eventHandler.Handle(s, i) + } else { + go eh.eventHandler.Handle(s, i) + } + } + + if len(s.onceHandlers[t]) > 0 { + for _, eh := range s.onceHandlers[t] { + if s.SyncEvents { + eh.eventHandler.Handle(s, i) + } else { + go eh.eventHandler.Handle(s, i) + } + } + s.onceHandlers[t] = nil + } +} + +// Handles an event type by calling internal methods, firing handlers and firing the +// interface{} event. +func (s *Session) handleEvent(t string, i interface{}) { + s.handlersMu.RLock() + defer s.handlersMu.RUnlock() + + // All events are dispatched internally first. + s.onInterface(i) + + // Then they are dispatched to anyone handling interface{} events. + s.handle(interfaceEventType, i) + + // Finally they are dispatched to any typed handlers. + s.handle(t, i) +} + +// setGuildIds will set the GuildID on all the members of a guild. +// This is done as event data does not have it set. +func setGuildIds(g *Guild) { + for _, c := range g.Channels { + c.GuildID = g.ID + } + + for _, m := range g.Members { + m.GuildID = g.ID + } + + for _, vs := range g.VoiceStates { + vs.GuildID = g.ID + } +} + +// onInterface handles all internal events and routes them to the appropriate internal handler. +func (s *Session) onInterface(i interface{}) { + switch t := i.(type) { + case *Ready: + for _, g := range t.Guilds { + setGuildIds(g) + } + s.onReady(t) + case *GuildCreate: + setGuildIds(t.Guild) + case *GuildUpdate: + setGuildIds(t.Guild) + case *VoiceServerUpdate: + go s.onVoiceServerUpdate(t) + case *VoiceStateUpdate: + go s.onVoiceStateUpdate(t) + } + err := s.State.OnInterface(s, i) + if err != nil { + s.log(LogDebug, "error dispatching internal event, %s", err) + } +} + +// onReady handles the ready event. +func (s *Session) onReady(r *Ready) { + + // Store the SessionID within the Session struct. + s.sessionID = r.SessionID +} diff --git a/vendor/github.com/bwmarrin/discordgo/eventhandlers.go b/vendor/github.com/bwmarrin/discordgo/eventhandlers.go new file mode 100644 index 00000000..18d6248a --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/eventhandlers.go @@ -0,0 +1,1342 @@ +// Code generated by \"eventhandlers\"; DO NOT EDIT +// See events.go + +package discordgo + +// Following are all the event types. +// Event type values are used to match the events returned by Discord. +// EventTypes surrounded by __ are synthetic and are internal to DiscordGo. +const ( + channelCreateEventType = "CHANNEL_CREATE" + channelDeleteEventType = "CHANNEL_DELETE" + channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE" + channelUpdateEventType = "CHANNEL_UPDATE" + connectEventType = "__CONNECT__" + disconnectEventType = "__DISCONNECT__" + eventEventType = "__EVENT__" + guildBanAddEventType = "GUILD_BAN_ADD" + guildBanRemoveEventType = "GUILD_BAN_REMOVE" + guildCreateEventType = "GUILD_CREATE" + guildDeleteEventType = "GUILD_DELETE" + guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE" + guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE" + guildMemberAddEventType = "GUILD_MEMBER_ADD" + guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE" + guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE" + guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK" + guildRoleCreateEventType = "GUILD_ROLE_CREATE" + guildRoleDeleteEventType = "GUILD_ROLE_DELETE" + guildRoleUpdateEventType = "GUILD_ROLE_UPDATE" + guildUpdateEventType = "GUILD_UPDATE" + guildScheduledEventCreateEventType = "GUILD_SCHEDULED_EVENT_CREATE" + guildScheduledEventUpdateEventType = "GUILD_SCHEDULED_EVENT_UPDATE" + guildScheduledEventDeleteEventType = "GUILD_SCHEDULED_EVENT_DELETE" + interactionCreateEventType = "INTERACTION_CREATE" + inviteCreateEventType = "INVITE_CREATE" + inviteDeleteEventType = "INVITE_DELETE" + messageAckEventType = "MESSAGE_ACK" + messageCreateEventType = "MESSAGE_CREATE" + messageDeleteEventType = "MESSAGE_DELETE" + messageDeleteBulkEventType = "MESSAGE_DELETE_BULK" + messageReactionAddEventType = "MESSAGE_REACTION_ADD" + messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE" + messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL" + messageUpdateEventType = "MESSAGE_UPDATE" + presenceUpdateEventType = "PRESENCE_UPDATE" + presencesReplaceEventType = "PRESENCES_REPLACE" + rateLimitEventType = "__RATE_LIMIT__" + readyEventType = "READY" + relationshipAddEventType = "RELATIONSHIP_ADD" + relationshipRemoveEventType = "RELATIONSHIP_REMOVE" + resumedEventType = "RESUMED" + threadCreateEventType = "THREAD_CREATE" + threadDeleteEventType = "THREAD_DELETE" + threadListSyncEventType = "THREAD_LIST_SYNC" + threadMemberUpdateEventType = "THREAD_MEMBER_UPDATE" + threadMembersUpdateEventType = "THREAD_MEMBERS_UPDATE" + threadUpdateEventType = "THREAD_UPDATE" + typingStartEventType = "TYPING_START" + userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE" + userNoteUpdateEventType = "USER_NOTE_UPDATE" + userSettingsUpdateEventType = "USER_SETTINGS_UPDATE" + userUpdateEventType = "USER_UPDATE" + voiceServerUpdateEventType = "VOICE_SERVER_UPDATE" + voiceStateUpdateEventType = "VOICE_STATE_UPDATE" + webhooksUpdateEventType = "WEBHOOKS_UPDATE" +) + +// channelCreateEventHandler is an event handler for ChannelCreate events. +type channelCreateEventHandler func(*Session, *ChannelCreate) + +// Type returns the event type for ChannelCreate events. +func (eh channelCreateEventHandler) Type() string { + return channelCreateEventType +} + +// New returns a new instance of ChannelCreate. +func (eh channelCreateEventHandler) New() interface{} { + return &ChannelCreate{} +} + +// Handle is the handler for ChannelCreate events. +func (eh channelCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ChannelCreate); ok { + eh(s, t) + } +} + +// channelDeleteEventHandler is an event handler for ChannelDelete events. +type channelDeleteEventHandler func(*Session, *ChannelDelete) + +// Type returns the event type for ChannelDelete events. +func (eh channelDeleteEventHandler) Type() string { + return channelDeleteEventType +} + +// New returns a new instance of ChannelDelete. +func (eh channelDeleteEventHandler) New() interface{} { + return &ChannelDelete{} +} + +// Handle is the handler for ChannelDelete events. +func (eh channelDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ChannelDelete); ok { + eh(s, t) + } +} + +// channelPinsUpdateEventHandler is an event handler for ChannelPinsUpdate events. +type channelPinsUpdateEventHandler func(*Session, *ChannelPinsUpdate) + +// Type returns the event type for ChannelPinsUpdate events. +func (eh channelPinsUpdateEventHandler) Type() string { + return channelPinsUpdateEventType +} + +// New returns a new instance of ChannelPinsUpdate. +func (eh channelPinsUpdateEventHandler) New() interface{} { + return &ChannelPinsUpdate{} +} + +// Handle is the handler for ChannelPinsUpdate events. +func (eh channelPinsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ChannelPinsUpdate); ok { + eh(s, t) + } +} + +// channelUpdateEventHandler is an event handler for ChannelUpdate events. +type channelUpdateEventHandler func(*Session, *ChannelUpdate) + +// Type returns the event type for ChannelUpdate events. +func (eh channelUpdateEventHandler) Type() string { + return channelUpdateEventType +} + +// New returns a new instance of ChannelUpdate. +func (eh channelUpdateEventHandler) New() interface{} { + return &ChannelUpdate{} +} + +// Handle is the handler for ChannelUpdate events. +func (eh channelUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ChannelUpdate); ok { + eh(s, t) + } +} + +// connectEventHandler is an event handler for Connect events. +type connectEventHandler func(*Session, *Connect) + +// Type returns the event type for Connect events. +func (eh connectEventHandler) Type() string { + return connectEventType +} + +// Handle is the handler for Connect events. +func (eh connectEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Connect); ok { + eh(s, t) + } +} + +// disconnectEventHandler is an event handler for Disconnect events. +type disconnectEventHandler func(*Session, *Disconnect) + +// Type returns the event type for Disconnect events. +func (eh disconnectEventHandler) Type() string { + return disconnectEventType +} + +// Handle is the handler for Disconnect events. +func (eh disconnectEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Disconnect); ok { + eh(s, t) + } +} + +// eventEventHandler is an event handler for Event events. +type eventEventHandler func(*Session, *Event) + +// Type returns the event type for Event events. +func (eh eventEventHandler) Type() string { + return eventEventType +} + +// Handle is the handler for Event events. +func (eh eventEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Event); ok { + eh(s, t) + } +} + +// guildBanAddEventHandler is an event handler for GuildBanAdd events. +type guildBanAddEventHandler func(*Session, *GuildBanAdd) + +// Type returns the event type for GuildBanAdd events. +func (eh guildBanAddEventHandler) Type() string { + return guildBanAddEventType +} + +// New returns a new instance of GuildBanAdd. +func (eh guildBanAddEventHandler) New() interface{} { + return &GuildBanAdd{} +} + +// Handle is the handler for GuildBanAdd events. +func (eh guildBanAddEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildBanAdd); ok { + eh(s, t) + } +} + +// guildBanRemoveEventHandler is an event handler for GuildBanRemove events. +type guildBanRemoveEventHandler func(*Session, *GuildBanRemove) + +// Type returns the event type for GuildBanRemove events. +func (eh guildBanRemoveEventHandler) Type() string { + return guildBanRemoveEventType +} + +// New returns a new instance of GuildBanRemove. +func (eh guildBanRemoveEventHandler) New() interface{} { + return &GuildBanRemove{} +} + +// Handle is the handler for GuildBanRemove events. +func (eh guildBanRemoveEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildBanRemove); ok { + eh(s, t) + } +} + +// guildCreateEventHandler is an event handler for GuildCreate events. +type guildCreateEventHandler func(*Session, *GuildCreate) + +// Type returns the event type for GuildCreate events. +func (eh guildCreateEventHandler) Type() string { + return guildCreateEventType +} + +// New returns a new instance of GuildCreate. +func (eh guildCreateEventHandler) New() interface{} { + return &GuildCreate{} +} + +// Handle is the handler for GuildCreate events. +func (eh guildCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildCreate); ok { + eh(s, t) + } +} + +// guildDeleteEventHandler is an event handler for GuildDelete events. +type guildDeleteEventHandler func(*Session, *GuildDelete) + +// Type returns the event type for GuildDelete events. +func (eh guildDeleteEventHandler) Type() string { + return guildDeleteEventType +} + +// New returns a new instance of GuildDelete. +func (eh guildDeleteEventHandler) New() interface{} { + return &GuildDelete{} +} + +// Handle is the handler for GuildDelete events. +func (eh guildDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildDelete); ok { + eh(s, t) + } +} + +// guildEmojisUpdateEventHandler is an event handler for GuildEmojisUpdate events. +type guildEmojisUpdateEventHandler func(*Session, *GuildEmojisUpdate) + +// Type returns the event type for GuildEmojisUpdate events. +func (eh guildEmojisUpdateEventHandler) Type() string { + return guildEmojisUpdateEventType +} + +// New returns a new instance of GuildEmojisUpdate. +func (eh guildEmojisUpdateEventHandler) New() interface{} { + return &GuildEmojisUpdate{} +} + +// Handle is the handler for GuildEmojisUpdate events. +func (eh guildEmojisUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildEmojisUpdate); ok { + eh(s, t) + } +} + +// guildIntegrationsUpdateEventHandler is an event handler for GuildIntegrationsUpdate events. +type guildIntegrationsUpdateEventHandler func(*Session, *GuildIntegrationsUpdate) + +// Type returns the event type for GuildIntegrationsUpdate events. +func (eh guildIntegrationsUpdateEventHandler) Type() string { + return guildIntegrationsUpdateEventType +} + +// New returns a new instance of GuildIntegrationsUpdate. +func (eh guildIntegrationsUpdateEventHandler) New() interface{} { + return &GuildIntegrationsUpdate{} +} + +// Handle is the handler for GuildIntegrationsUpdate events. +func (eh guildIntegrationsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildIntegrationsUpdate); ok { + eh(s, t) + } +} + +// guildScheduledEventCreateEventHandler is an event handler for GuildScheduledEventCreate events. +type guildScheduledEventCreateEventHandler func(*Session, *GuildScheduledEventCreate) + +// Type returns the event type for GuildScheduledEventCreate events. +func (eh guildScheduledEventCreateEventHandler) Type() string { + return guildScheduledEventCreateEventType +} + +// New returns a new instance of GuildScheduledEventCreate. +func (eh guildScheduledEventCreateEventHandler) New() interface{} { + return &GuildScheduledEventCreate{} +} + +// Handle is the handler for GuildScheduledEventCreate events. +func (eh guildScheduledEventCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildScheduledEventCreate); ok { + eh(s, t) + } +} + +// guildScheduledEventUpdateEventHandler is an event handler for GuildScheduledEventUpdate events. +type guildScheduledEventUpdateEventHandler func(*Session, *GuildScheduledEventUpdate) + +// Type returns the event type for GuildScheduledEventUpdate events. +func (eh guildScheduledEventUpdateEventHandler) Type() string { + return guildScheduledEventUpdateEventType +} + +// New returns a new instance of GuildScheduledEventUpdate. +func (eh guildScheduledEventUpdateEventHandler) New() interface{} { + return &GuildScheduledEventUpdate{} +} + +// Handle is the handler for GuildScheduledEventUpdate events. +func (eh guildScheduledEventUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildScheduledEventUpdate); ok { + eh(s, t) + } +} + +// guildScheduledEventDeleteEventHandler is an event handler for GuildScheduledEventDelete events. +type guildScheduledEventDeleteEventHandler func(*Session, *GuildScheduledEventDelete) + +// Type returns the event type for GuildScheduledEventDelete events. +func (eh guildScheduledEventDeleteEventHandler) Type() string { + return guildScheduledEventDeleteEventType +} + +// New returns a new instance of GuildScheduledEventDelete. +func (eh guildScheduledEventDeleteEventHandler) New() interface{} { + return &GuildScheduledEventDelete{} +} + +// Handle is the handler for GuildScheduledEventDelete events. +func (eh guildScheduledEventDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildScheduledEventDelete); ok { + eh(s, t) + } +} + +// guildMemberAddEventHandler is an event handler for GuildMemberAdd events. +type guildMemberAddEventHandler func(*Session, *GuildMemberAdd) + +// Type returns the event type for GuildMemberAdd events. +func (eh guildMemberAddEventHandler) Type() string { + return guildMemberAddEventType +} + +// New returns a new instance of GuildMemberAdd. +func (eh guildMemberAddEventHandler) New() interface{} { + return &GuildMemberAdd{} +} + +// Handle is the handler for GuildMemberAdd events. +func (eh guildMemberAddEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildMemberAdd); ok { + eh(s, t) + } +} + +// guildMemberRemoveEventHandler is an event handler for GuildMemberRemove events. +type guildMemberRemoveEventHandler func(*Session, *GuildMemberRemove) + +// Type returns the event type for GuildMemberRemove events. +func (eh guildMemberRemoveEventHandler) Type() string { + return guildMemberRemoveEventType +} + +// New returns a new instance of GuildMemberRemove. +func (eh guildMemberRemoveEventHandler) New() interface{} { + return &GuildMemberRemove{} +} + +// Handle is the handler for GuildMemberRemove events. +func (eh guildMemberRemoveEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildMemberRemove); ok { + eh(s, t) + } +} + +// guildMemberUpdateEventHandler is an event handler for GuildMemberUpdate events. +type guildMemberUpdateEventHandler func(*Session, *GuildMemberUpdate) + +// Type returns the event type for GuildMemberUpdate events. +func (eh guildMemberUpdateEventHandler) Type() string { + return guildMemberUpdateEventType +} + +// New returns a new instance of GuildMemberUpdate. +func (eh guildMemberUpdateEventHandler) New() interface{} { + return &GuildMemberUpdate{} +} + +// Handle is the handler for GuildMemberUpdate events. +func (eh guildMemberUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildMemberUpdate); ok { + eh(s, t) + } +} + +// guildMembersChunkEventHandler is an event handler for GuildMembersChunk events. +type guildMembersChunkEventHandler func(*Session, *GuildMembersChunk) + +// Type returns the event type for GuildMembersChunk events. +func (eh guildMembersChunkEventHandler) Type() string { + return guildMembersChunkEventType +} + +// New returns a new instance of GuildMembersChunk. +func (eh guildMembersChunkEventHandler) New() interface{} { + return &GuildMembersChunk{} +} + +// Handle is the handler for GuildMembersChunk events. +func (eh guildMembersChunkEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildMembersChunk); ok { + eh(s, t) + } +} + +// guildRoleCreateEventHandler is an event handler for GuildRoleCreate events. +type guildRoleCreateEventHandler func(*Session, *GuildRoleCreate) + +// Type returns the event type for GuildRoleCreate events. +func (eh guildRoleCreateEventHandler) Type() string { + return guildRoleCreateEventType +} + +// New returns a new instance of GuildRoleCreate. +func (eh guildRoleCreateEventHandler) New() interface{} { + return &GuildRoleCreate{} +} + +// Handle is the handler for GuildRoleCreate events. +func (eh guildRoleCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildRoleCreate); ok { + eh(s, t) + } +} + +// guildRoleDeleteEventHandler is an event handler for GuildRoleDelete events. +type guildRoleDeleteEventHandler func(*Session, *GuildRoleDelete) + +// Type returns the event type for GuildRoleDelete events. +func (eh guildRoleDeleteEventHandler) Type() string { + return guildRoleDeleteEventType +} + +// New returns a new instance of GuildRoleDelete. +func (eh guildRoleDeleteEventHandler) New() interface{} { + return &GuildRoleDelete{} +} + +// Handle is the handler for GuildRoleDelete events. +func (eh guildRoleDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildRoleDelete); ok { + eh(s, t) + } +} + +// guildRoleUpdateEventHandler is an event handler for GuildRoleUpdate events. +type guildRoleUpdateEventHandler func(*Session, *GuildRoleUpdate) + +// Type returns the event type for GuildRoleUpdate events. +func (eh guildRoleUpdateEventHandler) Type() string { + return guildRoleUpdateEventType +} + +// New returns a new instance of GuildRoleUpdate. +func (eh guildRoleUpdateEventHandler) New() interface{} { + return &GuildRoleUpdate{} +} + +// Handle is the handler for GuildRoleUpdate events. +func (eh guildRoleUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildRoleUpdate); ok { + eh(s, t) + } +} + +// guildUpdateEventHandler is an event handler for GuildUpdate events. +type guildUpdateEventHandler func(*Session, *GuildUpdate) + +// Type returns the event type for GuildUpdate events. +func (eh guildUpdateEventHandler) Type() string { + return guildUpdateEventType +} + +// New returns a new instance of GuildUpdate. +func (eh guildUpdateEventHandler) New() interface{} { + return &GuildUpdate{} +} + +// Handle is the handler for GuildUpdate events. +func (eh guildUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildUpdate); ok { + eh(s, t) + } +} + +// interactionCreateEventHandler is an event handler for InteractionCreate events. +type interactionCreateEventHandler func(*Session, *InteractionCreate) + +// Type returns the event type for InteractionCreate events. +func (eh interactionCreateEventHandler) Type() string { + return interactionCreateEventType +} + +// New returns a new instance of InteractionCreate. +func (eh interactionCreateEventHandler) New() interface{} { + return &InteractionCreate{} +} + +// Handle is the handler for InteractionCreate events. +func (eh interactionCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*InteractionCreate); ok { + eh(s, t) + } +} + +// inviteCreateEventHandler is an event handler for InviteCreate events. +type inviteCreateEventHandler func(*Session, *InviteCreate) + +// Type returns the event type for InviteCreate events. +func (eh inviteCreateEventHandler) Type() string { + return inviteCreateEventType +} + +// New returns a new instance of InviteCreate. +func (eh inviteCreateEventHandler) New() interface{} { + return &InviteCreate{} +} + +// Handle is the handler for InviteCreate events. +func (eh inviteCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*InviteCreate); ok { + eh(s, t) + } +} + +// inviteDeleteEventHandler is an event handler for InviteDelete events. +type inviteDeleteEventHandler func(*Session, *InviteDelete) + +// Type returns the event type for InviteDelete events. +func (eh inviteDeleteEventHandler) Type() string { + return inviteDeleteEventType +} + +// New returns a new instance of InviteDelete. +func (eh inviteDeleteEventHandler) New() interface{} { + return &InviteDelete{} +} + +// Handle is the handler for InviteDelete events. +func (eh inviteDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*InviteDelete); ok { + eh(s, t) + } +} + +// messageAckEventHandler is an event handler for MessageAck events. +type messageAckEventHandler func(*Session, *MessageAck) + +// Type returns the event type for MessageAck events. +func (eh messageAckEventHandler) Type() string { + return messageAckEventType +} + +// New returns a new instance of MessageAck. +func (eh messageAckEventHandler) New() interface{} { + return &MessageAck{} +} + +// Handle is the handler for MessageAck events. +func (eh messageAckEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageAck); ok { + eh(s, t) + } +} + +// messageCreateEventHandler is an event handler for MessageCreate events. +type messageCreateEventHandler func(*Session, *MessageCreate) + +// Type returns the event type for MessageCreate events. +func (eh messageCreateEventHandler) Type() string { + return messageCreateEventType +} + +// New returns a new instance of MessageCreate. +func (eh messageCreateEventHandler) New() interface{} { + return &MessageCreate{} +} + +// Handle is the handler for MessageCreate events. +func (eh messageCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageCreate); ok { + eh(s, t) + } +} + +// messageDeleteEventHandler is an event handler for MessageDelete events. +type messageDeleteEventHandler func(*Session, *MessageDelete) + +// Type returns the event type for MessageDelete events. +func (eh messageDeleteEventHandler) Type() string { + return messageDeleteEventType +} + +// New returns a new instance of MessageDelete. +func (eh messageDeleteEventHandler) New() interface{} { + return &MessageDelete{} +} + +// Handle is the handler for MessageDelete events. +func (eh messageDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageDelete); ok { + eh(s, t) + } +} + +// messageDeleteBulkEventHandler is an event handler for MessageDeleteBulk events. +type messageDeleteBulkEventHandler func(*Session, *MessageDeleteBulk) + +// Type returns the event type for MessageDeleteBulk events. +func (eh messageDeleteBulkEventHandler) Type() string { + return messageDeleteBulkEventType +} + +// New returns a new instance of MessageDeleteBulk. +func (eh messageDeleteBulkEventHandler) New() interface{} { + return &MessageDeleteBulk{} +} + +// Handle is the handler for MessageDeleteBulk events. +func (eh messageDeleteBulkEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageDeleteBulk); ok { + eh(s, t) + } +} + +// messageReactionAddEventHandler is an event handler for MessageReactionAdd events. +type messageReactionAddEventHandler func(*Session, *MessageReactionAdd) + +// Type returns the event type for MessageReactionAdd events. +func (eh messageReactionAddEventHandler) Type() string { + return messageReactionAddEventType +} + +// New returns a new instance of MessageReactionAdd. +func (eh messageReactionAddEventHandler) New() interface{} { + return &MessageReactionAdd{} +} + +// Handle is the handler for MessageReactionAdd events. +func (eh messageReactionAddEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageReactionAdd); ok { + eh(s, t) + } +} + +// messageReactionRemoveEventHandler is an event handler for MessageReactionRemove events. +type messageReactionRemoveEventHandler func(*Session, *MessageReactionRemove) + +// Type returns the event type for MessageReactionRemove events. +func (eh messageReactionRemoveEventHandler) Type() string { + return messageReactionRemoveEventType +} + +// New returns a new instance of MessageReactionRemove. +func (eh messageReactionRemoveEventHandler) New() interface{} { + return &MessageReactionRemove{} +} + +// Handle is the handler for MessageReactionRemove events. +func (eh messageReactionRemoveEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageReactionRemove); ok { + eh(s, t) + } +} + +// messageReactionRemoveAllEventHandler is an event handler for MessageReactionRemoveAll events. +type messageReactionRemoveAllEventHandler func(*Session, *MessageReactionRemoveAll) + +// Type returns the event type for MessageReactionRemoveAll events. +func (eh messageReactionRemoveAllEventHandler) Type() string { + return messageReactionRemoveAllEventType +} + +// New returns a new instance of MessageReactionRemoveAll. +func (eh messageReactionRemoveAllEventHandler) New() interface{} { + return &MessageReactionRemoveAll{} +} + +// Handle is the handler for MessageReactionRemoveAll events. +func (eh messageReactionRemoveAllEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageReactionRemoveAll); ok { + eh(s, t) + } +} + +// messageUpdateEventHandler is an event handler for MessageUpdate events. +type messageUpdateEventHandler func(*Session, *MessageUpdate) + +// Type returns the event type for MessageUpdate events. +func (eh messageUpdateEventHandler) Type() string { + return messageUpdateEventType +} + +// New returns a new instance of MessageUpdate. +func (eh messageUpdateEventHandler) New() interface{} { + return &MessageUpdate{} +} + +// Handle is the handler for MessageUpdate events. +func (eh messageUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageUpdate); ok { + eh(s, t) + } +} + +// presenceUpdateEventHandler is an event handler for PresenceUpdate events. +type presenceUpdateEventHandler func(*Session, *PresenceUpdate) + +// Type returns the event type for PresenceUpdate events. +func (eh presenceUpdateEventHandler) Type() string { + return presenceUpdateEventType +} + +// New returns a new instance of PresenceUpdate. +func (eh presenceUpdateEventHandler) New() interface{} { + return &PresenceUpdate{} +} + +// Handle is the handler for PresenceUpdate events. +func (eh presenceUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*PresenceUpdate); ok { + eh(s, t) + } +} + +// presencesReplaceEventHandler is an event handler for PresencesReplace events. +type presencesReplaceEventHandler func(*Session, *PresencesReplace) + +// Type returns the event type for PresencesReplace events. +func (eh presencesReplaceEventHandler) Type() string { + return presencesReplaceEventType +} + +// New returns a new instance of PresencesReplace. +func (eh presencesReplaceEventHandler) New() interface{} { + return &PresencesReplace{} +} + +// Handle is the handler for PresencesReplace events. +func (eh presencesReplaceEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*PresencesReplace); ok { + eh(s, t) + } +} + +// rateLimitEventHandler is an event handler for RateLimit events. +type rateLimitEventHandler func(*Session, *RateLimit) + +// Type returns the event type for RateLimit events. +func (eh rateLimitEventHandler) Type() string { + return rateLimitEventType +} + +// Handle is the handler for RateLimit events. +func (eh rateLimitEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*RateLimit); ok { + eh(s, t) + } +} + +// readyEventHandler is an event handler for Ready events. +type readyEventHandler func(*Session, *Ready) + +// Type returns the event type for Ready events. +func (eh readyEventHandler) Type() string { + return readyEventType +} + +// New returns a new instance of Ready. +func (eh readyEventHandler) New() interface{} { + return &Ready{} +} + +// Handle is the handler for Ready events. +func (eh readyEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Ready); ok { + eh(s, t) + } +} + +// relationshipAddEventHandler is an event handler for RelationshipAdd events. +type relationshipAddEventHandler func(*Session, *RelationshipAdd) + +// Type returns the event type for RelationshipAdd events. +func (eh relationshipAddEventHandler) Type() string { + return relationshipAddEventType +} + +// New returns a new instance of RelationshipAdd. +func (eh relationshipAddEventHandler) New() interface{} { + return &RelationshipAdd{} +} + +// Handle is the handler for RelationshipAdd events. +func (eh relationshipAddEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*RelationshipAdd); ok { + eh(s, t) + } +} + +// relationshipRemoveEventHandler is an event handler for RelationshipRemove events. +type relationshipRemoveEventHandler func(*Session, *RelationshipRemove) + +// Type returns the event type for RelationshipRemove events. +func (eh relationshipRemoveEventHandler) Type() string { + return relationshipRemoveEventType +} + +// New returns a new instance of RelationshipRemove. +func (eh relationshipRemoveEventHandler) New() interface{} { + return &RelationshipRemove{} +} + +// Handle is the handler for RelationshipRemove events. +func (eh relationshipRemoveEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*RelationshipRemove); ok { + eh(s, t) + } +} + +// resumedEventHandler is an event handler for Resumed events. +type resumedEventHandler func(*Session, *Resumed) + +// Type returns the event type for Resumed events. +func (eh resumedEventHandler) Type() string { + return resumedEventType +} + +// New returns a new instance of Resumed. +func (eh resumedEventHandler) New() interface{} { + return &Resumed{} +} + +// Handle is the handler for Resumed events. +func (eh resumedEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Resumed); ok { + eh(s, t) + } +} + +// threadCreateEventHandler is an event handler for ThreadCreate events. +type threadCreateEventHandler func(*Session, *ThreadCreate) + +// Type returns the event type for ThreadCreate events. +func (eh threadCreateEventHandler) Type() string { + return threadCreateEventType +} + +// New returns a new instance of ThreadCreate. +func (eh threadCreateEventHandler) New() interface{} { + return &ThreadCreate{} +} + +// Handle is the handler for ThreadCreate events. +func (eh threadCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadCreate); ok { + eh(s, t) + } +} + +// threadDeleteEventHandler is an event handler for ThreadDelete events. +type threadDeleteEventHandler func(*Session, *ThreadDelete) + +// Type returns the event type for ThreadDelete events. +func (eh threadDeleteEventHandler) Type() string { + return threadDeleteEventType +} + +// New returns a new instance of ThreadDelete. +func (eh threadDeleteEventHandler) New() interface{} { + return &ThreadDelete{} +} + +// Handle is the handler for ThreadDelete events. +func (eh threadDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadDelete); ok { + eh(s, t) + } +} + +// threadListSyncEventHandler is an event handler for ThreadListSync events. +type threadListSyncEventHandler func(*Session, *ThreadListSync) + +// Type returns the event type for ThreadListSync events. +func (eh threadListSyncEventHandler) Type() string { + return threadListSyncEventType +} + +// New returns a new instance of ThreadListSync. +func (eh threadListSyncEventHandler) New() interface{} { + return &ThreadListSync{} +} + +// Handle is the handler for ThreadListSync events. +func (eh threadListSyncEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadListSync); ok { + eh(s, t) + } +} + +// threadMemberUpdateEventHandler is an event handler for ThreadMemberUpdate events. +type threadMemberUpdateEventHandler func(*Session, *ThreadMemberUpdate) + +// Type returns the event type for ThreadMemberUpdate events. +func (eh threadMemberUpdateEventHandler) Type() string { + return threadMemberUpdateEventType +} + +// New returns a new instance of ThreadMemberUpdate. +func (eh threadMemberUpdateEventHandler) New() interface{} { + return &ThreadMemberUpdate{} +} + +// Handle is the handler for ThreadMemberUpdate events. +func (eh threadMemberUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadMemberUpdate); ok { + eh(s, t) + } +} + +// threadMembersUpdateEventHandler is an event handler for ThreadMembersUpdate events. +type threadMembersUpdateEventHandler func(*Session, *ThreadMembersUpdate) + +// Type returns the event type for ThreadMembersUpdate events. +func (eh threadMembersUpdateEventHandler) Type() string { + return threadMembersUpdateEventType +} + +// New returns a new instance of ThreadMembersUpdate. +func (eh threadMembersUpdateEventHandler) New() interface{} { + return &ThreadMembersUpdate{} +} + +// Handle is the handler for ThreadMembersUpdate events. +func (eh threadMembersUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadMembersUpdate); ok { + eh(s, t) + } +} + +// threadUpdateEventHandler is an event handler for ThreadUpdate events. +type threadUpdateEventHandler func(*Session, *ThreadUpdate) + +// Type returns the event type for ThreadUpdate events. +func (eh threadUpdateEventHandler) Type() string { + return threadUpdateEventType +} + +// New returns a new instance of ThreadUpdate. +func (eh threadUpdateEventHandler) New() interface{} { + return &ThreadUpdate{} +} + +// Handle is the handler for ThreadUpdate events. +func (eh threadUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ThreadUpdate); ok { + eh(s, t) + } +} + +// typingStartEventHandler is an event handler for TypingStart events. +type typingStartEventHandler func(*Session, *TypingStart) + +// Type returns the event type for TypingStart events. +func (eh typingStartEventHandler) Type() string { + return typingStartEventType +} + +// New returns a new instance of TypingStart. +func (eh typingStartEventHandler) New() interface{} { + return &TypingStart{} +} + +// Handle is the handler for TypingStart events. +func (eh typingStartEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*TypingStart); ok { + eh(s, t) + } +} + +// userGuildSettingsUpdateEventHandler is an event handler for UserGuildSettingsUpdate events. +type userGuildSettingsUpdateEventHandler func(*Session, *UserGuildSettingsUpdate) + +// Type returns the event type for UserGuildSettingsUpdate events. +func (eh userGuildSettingsUpdateEventHandler) Type() string { + return userGuildSettingsUpdateEventType +} + +// New returns a new instance of UserGuildSettingsUpdate. +func (eh userGuildSettingsUpdateEventHandler) New() interface{} { + return &UserGuildSettingsUpdate{} +} + +// Handle is the handler for UserGuildSettingsUpdate events. +func (eh userGuildSettingsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*UserGuildSettingsUpdate); ok { + eh(s, t) + } +} + +// userNoteUpdateEventHandler is an event handler for UserNoteUpdate events. +type userNoteUpdateEventHandler func(*Session, *UserNoteUpdate) + +// Type returns the event type for UserNoteUpdate events. +func (eh userNoteUpdateEventHandler) Type() string { + return userNoteUpdateEventType +} + +// New returns a new instance of UserNoteUpdate. +func (eh userNoteUpdateEventHandler) New() interface{} { + return &UserNoteUpdate{} +} + +// Handle is the handler for UserNoteUpdate events. +func (eh userNoteUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*UserNoteUpdate); ok { + eh(s, t) + } +} + +// userSettingsUpdateEventHandler is an event handler for UserSettingsUpdate events. +type userSettingsUpdateEventHandler func(*Session, *UserSettingsUpdate) + +// Type returns the event type for UserSettingsUpdate events. +func (eh userSettingsUpdateEventHandler) Type() string { + return userSettingsUpdateEventType +} + +// New returns a new instance of UserSettingsUpdate. +func (eh userSettingsUpdateEventHandler) New() interface{} { + return &UserSettingsUpdate{} +} + +// Handle is the handler for UserSettingsUpdate events. +func (eh userSettingsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*UserSettingsUpdate); ok { + eh(s, t) + } +} + +// userUpdateEventHandler is an event handler for UserUpdate events. +type userUpdateEventHandler func(*Session, *UserUpdate) + +// Type returns the event type for UserUpdate events. +func (eh userUpdateEventHandler) Type() string { + return userUpdateEventType +} + +// New returns a new instance of UserUpdate. +func (eh userUpdateEventHandler) New() interface{} { + return &UserUpdate{} +} + +// Handle is the handler for UserUpdate events. +func (eh userUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*UserUpdate); ok { + eh(s, t) + } +} + +// voiceServerUpdateEventHandler is an event handler for VoiceServerUpdate events. +type voiceServerUpdateEventHandler func(*Session, *VoiceServerUpdate) + +// Type returns the event type for VoiceServerUpdate events. +func (eh voiceServerUpdateEventHandler) Type() string { + return voiceServerUpdateEventType +} + +// New returns a new instance of VoiceServerUpdate. +func (eh voiceServerUpdateEventHandler) New() interface{} { + return &VoiceServerUpdate{} +} + +// Handle is the handler for VoiceServerUpdate events. +func (eh voiceServerUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*VoiceServerUpdate); ok { + eh(s, t) + } +} + +// voiceStateUpdateEventHandler is an event handler for VoiceStateUpdate events. +type voiceStateUpdateEventHandler func(*Session, *VoiceStateUpdate) + +// Type returns the event type for VoiceStateUpdate events. +func (eh voiceStateUpdateEventHandler) Type() string { + return voiceStateUpdateEventType +} + +// New returns a new instance of VoiceStateUpdate. +func (eh voiceStateUpdateEventHandler) New() interface{} { + return &VoiceStateUpdate{} +} + +// Handle is the handler for VoiceStateUpdate events. +func (eh voiceStateUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*VoiceStateUpdate); ok { + eh(s, t) + } +} + +// webhooksUpdateEventHandler is an event handler for WebhooksUpdate events. +type webhooksUpdateEventHandler func(*Session, *WebhooksUpdate) + +// Type returns the event type for WebhooksUpdate events. +func (eh webhooksUpdateEventHandler) Type() string { + return webhooksUpdateEventType +} + +// New returns a new instance of WebhooksUpdate. +func (eh webhooksUpdateEventHandler) New() interface{} { + return &WebhooksUpdate{} +} + +// Handle is the handler for WebhooksUpdate events. +func (eh webhooksUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*WebhooksUpdate); ok { + eh(s, t) + } +} + +func handlerForInterface(handler interface{}) EventHandler { + switch v := handler.(type) { + case func(*Session, interface{}): + return interfaceEventHandler(v) + case func(*Session, *ChannelCreate): + return channelCreateEventHandler(v) + case func(*Session, *ChannelDelete): + return channelDeleteEventHandler(v) + case func(*Session, *ChannelPinsUpdate): + return channelPinsUpdateEventHandler(v) + case func(*Session, *ChannelUpdate): + return channelUpdateEventHandler(v) + case func(*Session, *Connect): + return connectEventHandler(v) + case func(*Session, *Disconnect): + return disconnectEventHandler(v) + case func(*Session, *Event): + return eventEventHandler(v) + case func(*Session, *GuildBanAdd): + return guildBanAddEventHandler(v) + case func(*Session, *GuildBanRemove): + return guildBanRemoveEventHandler(v) + case func(*Session, *GuildCreate): + return guildCreateEventHandler(v) + case func(*Session, *GuildDelete): + return guildDeleteEventHandler(v) + case func(*Session, *GuildEmojisUpdate): + return guildEmojisUpdateEventHandler(v) + case func(*Session, *GuildIntegrationsUpdate): + return guildIntegrationsUpdateEventHandler(v) + case func(*Session, *GuildScheduledEventCreate): + return guildScheduledEventCreateEventHandler(v) + case func(*Session, *GuildScheduledEventUpdate): + return guildScheduledEventUpdateEventHandler(v) + case func(*Session, *GuildScheduledEventDelete): + return guildScheduledEventDeleteEventHandler(v) + case func(*Session, *GuildMemberAdd): + return guildMemberAddEventHandler(v) + case func(*Session, *GuildMemberRemove): + return guildMemberRemoveEventHandler(v) + case func(*Session, *GuildMemberUpdate): + return guildMemberUpdateEventHandler(v) + case func(*Session, *GuildMembersChunk): + return guildMembersChunkEventHandler(v) + case func(*Session, *GuildRoleCreate): + return guildRoleCreateEventHandler(v) + case func(*Session, *GuildRoleDelete): + return guildRoleDeleteEventHandler(v) + case func(*Session, *GuildRoleUpdate): + return guildRoleUpdateEventHandler(v) + case func(*Session, *GuildUpdate): + return guildUpdateEventHandler(v) + case func(*Session, *InteractionCreate): + return interactionCreateEventHandler(v) + case func(*Session, *InviteCreate): + return inviteCreateEventHandler(v) + case func(*Session, *InviteDelete): + return inviteDeleteEventHandler(v) + case func(*Session, *MessageAck): + return messageAckEventHandler(v) + case func(*Session, *MessageCreate): + return messageCreateEventHandler(v) + case func(*Session, *MessageDelete): + return messageDeleteEventHandler(v) + case func(*Session, *MessageDeleteBulk): + return messageDeleteBulkEventHandler(v) + case func(*Session, *MessageReactionAdd): + return messageReactionAddEventHandler(v) + case func(*Session, *MessageReactionRemove): + return messageReactionRemoveEventHandler(v) + case func(*Session, *MessageReactionRemoveAll): + return messageReactionRemoveAllEventHandler(v) + case func(*Session, *MessageUpdate): + return messageUpdateEventHandler(v) + case func(*Session, *PresenceUpdate): + return presenceUpdateEventHandler(v) + case func(*Session, *PresencesReplace): + return presencesReplaceEventHandler(v) + case func(*Session, *RateLimit): + return rateLimitEventHandler(v) + case func(*Session, *Ready): + return readyEventHandler(v) + case func(*Session, *RelationshipAdd): + return relationshipAddEventHandler(v) + case func(*Session, *RelationshipRemove): + return relationshipRemoveEventHandler(v) + case func(*Session, *Resumed): + return resumedEventHandler(v) + case func(*Session, *ThreadCreate): + return threadCreateEventHandler(v) + case func(*Session, *ThreadDelete): + return threadDeleteEventHandler(v) + case func(*Session, *ThreadListSync): + return threadListSyncEventHandler(v) + case func(*Session, *ThreadMemberUpdate): + return threadMemberUpdateEventHandler(v) + case func(*Session, *ThreadMembersUpdate): + return threadMembersUpdateEventHandler(v) + case func(*Session, *ThreadUpdate): + return threadUpdateEventHandler(v) + case func(*Session, *TypingStart): + return typingStartEventHandler(v) + case func(*Session, *UserGuildSettingsUpdate): + return userGuildSettingsUpdateEventHandler(v) + case func(*Session, *UserNoteUpdate): + return userNoteUpdateEventHandler(v) + case func(*Session, *UserSettingsUpdate): + return userSettingsUpdateEventHandler(v) + case func(*Session, *UserUpdate): + return userUpdateEventHandler(v) + case func(*Session, *VoiceServerUpdate): + return voiceServerUpdateEventHandler(v) + case func(*Session, *VoiceStateUpdate): + return voiceStateUpdateEventHandler(v) + case func(*Session, *WebhooksUpdate): + return webhooksUpdateEventHandler(v) + } + + return nil +} + +func init() { + registerInterfaceProvider(channelCreateEventHandler(nil)) + registerInterfaceProvider(channelDeleteEventHandler(nil)) + registerInterfaceProvider(channelPinsUpdateEventHandler(nil)) + registerInterfaceProvider(channelUpdateEventHandler(nil)) + registerInterfaceProvider(guildBanAddEventHandler(nil)) + registerInterfaceProvider(guildBanRemoveEventHandler(nil)) + registerInterfaceProvider(guildCreateEventHandler(nil)) + registerInterfaceProvider(guildDeleteEventHandler(nil)) + registerInterfaceProvider(guildEmojisUpdateEventHandler(nil)) + registerInterfaceProvider(guildIntegrationsUpdateEventHandler(nil)) + registerInterfaceProvider(guildScheduledEventCreateEventHandler(nil)) + registerInterfaceProvider(guildScheduledEventUpdateEventHandler(nil)) + registerInterfaceProvider(guildScheduledEventDeleteEventHandler(nil)) + registerInterfaceProvider(guildMemberAddEventHandler(nil)) + registerInterfaceProvider(guildMemberRemoveEventHandler(nil)) + registerInterfaceProvider(guildMemberUpdateEventHandler(nil)) + registerInterfaceProvider(guildMembersChunkEventHandler(nil)) + registerInterfaceProvider(guildRoleCreateEventHandler(nil)) + registerInterfaceProvider(guildRoleDeleteEventHandler(nil)) + registerInterfaceProvider(guildRoleUpdateEventHandler(nil)) + registerInterfaceProvider(guildUpdateEventHandler(nil)) + registerInterfaceProvider(interactionCreateEventHandler(nil)) + registerInterfaceProvider(inviteCreateEventHandler(nil)) + registerInterfaceProvider(inviteDeleteEventHandler(nil)) + registerInterfaceProvider(messageAckEventHandler(nil)) + registerInterfaceProvider(messageCreateEventHandler(nil)) + registerInterfaceProvider(messageDeleteEventHandler(nil)) + registerInterfaceProvider(messageDeleteBulkEventHandler(nil)) + registerInterfaceProvider(messageReactionAddEventHandler(nil)) + registerInterfaceProvider(messageReactionRemoveEventHandler(nil)) + registerInterfaceProvider(messageReactionRemoveAllEventHandler(nil)) + registerInterfaceProvider(messageUpdateEventHandler(nil)) + registerInterfaceProvider(presenceUpdateEventHandler(nil)) + registerInterfaceProvider(presencesReplaceEventHandler(nil)) + registerInterfaceProvider(readyEventHandler(nil)) + registerInterfaceProvider(relationshipAddEventHandler(nil)) + registerInterfaceProvider(relationshipRemoveEventHandler(nil)) + registerInterfaceProvider(resumedEventHandler(nil)) + registerInterfaceProvider(threadCreateEventHandler(nil)) + registerInterfaceProvider(threadDeleteEventHandler(nil)) + registerInterfaceProvider(threadListSyncEventHandler(nil)) + registerInterfaceProvider(threadMemberUpdateEventHandler(nil)) + registerInterfaceProvider(threadMembersUpdateEventHandler(nil)) + registerInterfaceProvider(threadUpdateEventHandler(nil)) + registerInterfaceProvider(typingStartEventHandler(nil)) + registerInterfaceProvider(userGuildSettingsUpdateEventHandler(nil)) + registerInterfaceProvider(userNoteUpdateEventHandler(nil)) + registerInterfaceProvider(userSettingsUpdateEventHandler(nil)) + registerInterfaceProvider(userUpdateEventHandler(nil)) + registerInterfaceProvider(voiceServerUpdateEventHandler(nil)) + registerInterfaceProvider(voiceStateUpdateEventHandler(nil)) + registerInterfaceProvider(webhooksUpdateEventHandler(nil)) +} diff --git a/vendor/github.com/bwmarrin/discordgo/events.go b/vendor/github.com/bwmarrin/discordgo/events.go new file mode 100644 index 00000000..cc5d7116 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/events.go @@ -0,0 +1,372 @@ +package discordgo + +import ( + "encoding/json" +) + +// This file contains all the possible structs that can be +// handled by AddHandler/EventHandler. +// DO NOT ADD ANYTHING BUT EVENT HANDLER STRUCTS TO THIS FILE. +//go:generate go run tools/cmd/eventhandlers/main.go + +// Connect is the data for a Connect event. +// This is a synthetic event and is not dispatched by Discord. +type Connect struct{} + +// Disconnect is the data for a Disconnect event. +// This is a synthetic event and is not dispatched by Discord. +type Disconnect struct{} + +// RateLimit is the data for a RateLimit event. +// This is a synthetic event and is not dispatched by Discord. +type RateLimit struct { + *TooManyRequests + URL string +} + +// Event provides a basic initial struct for all websocket events. +type Event struct { + Operation int `json:"op"` + Sequence int64 `json:"s"` + Type string `json:"t"` + RawData json.RawMessage `json:"d"` + // Struct contains one of the other types in this file. + Struct interface{} `json:"-"` +} + +// A Ready stores all data for the websocket READY event. +type Ready struct { + Version int `json:"v"` + SessionID string `json:"session_id"` + User *User `json:"user"` + ReadState []*ReadState `json:"read_state"` + PrivateChannels []*Channel `json:"private_channels"` + Guilds []*Guild `json:"guilds"` + + // Undocumented fields + Settings *Settings `json:"user_settings"` + UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"` + Relationships []*Relationship `json:"relationships"` + Presences []*Presence `json:"presences"` + Notes map[string]string `json:"notes"` +} + +// ChannelCreate is the data for a ChannelCreate event. +type ChannelCreate struct { + *Channel +} + +// ChannelUpdate is the data for a ChannelUpdate event. +type ChannelUpdate struct { + *Channel +} + +// ChannelDelete is the data for a ChannelDelete event. +type ChannelDelete struct { + *Channel +} + +// ChannelPinsUpdate stores data for a ChannelPinsUpdate event. +type ChannelPinsUpdate struct { + LastPinTimestamp string `json:"last_pin_timestamp"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id,omitempty"` +} + +// ThreadCreate is the data for a ThreadCreate event. +type ThreadCreate struct { + *Channel + NewlyCreated bool `json:"newly_created"` +} + +// ThreadUpdate is the data for a ThreadUpdate event. +type ThreadUpdate struct { + *Channel + BeforeUpdate *Channel `json:"-"` +} + +// ThreadDelete is the data for a ThreadDelete event. +type ThreadDelete struct { + *Channel +} + +// ThreadListSync is the data for a ThreadListSync event. +type ThreadListSync struct { + // The id of the guild + GuildID string `json:"guild_id"` + // The parent channel ids whose threads are being synced. + // If omitted, then threads were synced for the entire guild. + // This array may contain channel_ids that have no active threads as well, so you know to clear that data. + ChannelIDs []string `json:"channel_ids"` + // All active threads in the given channels that the current user can access + Threads []*Channel `json:"threads"` + // All thread member objects from the synced threads for the current user, + // indicating which threads the current user has been added to + Members []*ThreadMember `json:"members"` +} + +// ThreadMemberUpdate is the data for a ThreadMemberUpdate event. +type ThreadMemberUpdate struct { + *ThreadMember + GuildID string `json:"guild_id"` +} + +// ThreadMembersUpdate is the data for a ThreadMembersUpdate event. +type ThreadMembersUpdate struct { + ID string `json:"id"` + GuildID string `json:"guild_id"` + MemberCount int `json:"member_count"` + AddedMembers []AddedThreadMember `json:"added_members"` + RemovedMembers []string `json:"removed_member_ids"` +} + +// GuildCreate is the data for a GuildCreate event. +type GuildCreate struct { + *Guild +} + +// GuildUpdate is the data for a GuildUpdate event. +type GuildUpdate struct { + *Guild +} + +// GuildDelete is the data for a GuildDelete event. +type GuildDelete struct { + *Guild + BeforeDelete *Guild `json:"-"` +} + +// GuildBanAdd is the data for a GuildBanAdd event. +type GuildBanAdd struct { + User *User `json:"user"` + GuildID string `json:"guild_id"` +} + +// GuildBanRemove is the data for a GuildBanRemove event. +type GuildBanRemove struct { + User *User `json:"user"` + GuildID string `json:"guild_id"` +} + +// GuildMemberAdd is the data for a GuildMemberAdd event. +type GuildMemberAdd struct { + *Member +} + +// GuildMemberUpdate is the data for a GuildMemberUpdate event. +type GuildMemberUpdate struct { + *Member +} + +// GuildMemberRemove is the data for a GuildMemberRemove event. +type GuildMemberRemove struct { + *Member +} + +// GuildRoleCreate is the data for a GuildRoleCreate event. +type GuildRoleCreate struct { + *GuildRole +} + +// GuildRoleUpdate is the data for a GuildRoleUpdate event. +type GuildRoleUpdate struct { + *GuildRole +} + +// A GuildRoleDelete is the data for a GuildRoleDelete event. +type GuildRoleDelete struct { + RoleID string `json:"role_id"` + GuildID string `json:"guild_id"` +} + +// A GuildEmojisUpdate is the data for a guild emoji update event. +type GuildEmojisUpdate struct { + GuildID string `json:"guild_id"` + Emojis []*Emoji `json:"emojis"` +} + +// A GuildMembersChunk is the data for a GuildMembersChunk event. +type GuildMembersChunk struct { + GuildID string `json:"guild_id"` + Members []*Member `json:"members"` + ChunkIndex int `json:"chunk_index"` + ChunkCount int `json:"chunk_count"` + Presences []*Presence `json:"presences,omitempty"` +} + +// GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event. +type GuildIntegrationsUpdate struct { + GuildID string `json:"guild_id"` +} + +// GuildScheduledEventCreate is the data for a GuildScheduledEventCreate event. +type GuildScheduledEventCreate struct { + *GuildScheduledEvent +} + +// GuildScheduledEventUpdate is the data for a GuildScheduledEventUpdate event. +type GuildScheduledEventUpdate struct { + *GuildScheduledEvent +} + +// GuildScheduledEventDelete is the data for a GuildScheduledEventDelete event. +type GuildScheduledEventDelete struct { + *GuildScheduledEvent +} + +// MessageAck is the data for a MessageAck event. +type MessageAck struct { + MessageID string `json:"message_id"` + ChannelID string `json:"channel_id"` +} + +// MessageCreate is the data for a MessageCreate event. +type MessageCreate struct { + *Message +} + +// UnmarshalJSON is a helper function to unmarshal MessageCreate object. +func (m *MessageCreate) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &m.Message) +} + +// MessageUpdate is the data for a MessageUpdate event. +type MessageUpdate struct { + *Message + // BeforeUpdate will be nil if the Message was not previously cached in the state cache. + BeforeUpdate *Message `json:"-"` +} + +// UnmarshalJSON is a helper function to unmarshal MessageUpdate object. +func (m *MessageUpdate) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &m.Message) +} + +// MessageDelete is the data for a MessageDelete event. +type MessageDelete struct { + *Message + BeforeDelete *Message `json:"-"` +} + +// UnmarshalJSON is a helper function to unmarshal MessageDelete object. +func (m *MessageDelete) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &m.Message) +} + +// MessageReactionAdd is the data for a MessageReactionAdd event. +type MessageReactionAdd struct { + *MessageReaction + Member *Member `json:"member,omitempty"` +} + +// MessageReactionRemove is the data for a MessageReactionRemove event. +type MessageReactionRemove struct { + *MessageReaction +} + +// MessageReactionRemoveAll is the data for a MessageReactionRemoveAll event. +type MessageReactionRemoveAll struct { + *MessageReaction +} + +// PresencesReplace is the data for a PresencesReplace event. +type PresencesReplace []*Presence + +// PresenceUpdate is the data for a PresenceUpdate event. +type PresenceUpdate struct { + Presence + GuildID string `json:"guild_id"` +} + +// Resumed is the data for a Resumed event. +type Resumed struct { + Trace []string `json:"_trace"` +} + +// RelationshipAdd is the data for a RelationshipAdd event. +type RelationshipAdd struct { + *Relationship +} + +// RelationshipRemove is the data for a RelationshipRemove event. +type RelationshipRemove struct { + *Relationship +} + +// TypingStart is the data for a TypingStart event. +type TypingStart struct { + UserID string `json:"user_id"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id,omitempty"` + Timestamp int `json:"timestamp"` +} + +// UserUpdate is the data for a UserUpdate event. +type UserUpdate struct { + *User +} + +// UserSettingsUpdate is the data for a UserSettingsUpdate event. +type UserSettingsUpdate map[string]interface{} + +// UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event. +type UserGuildSettingsUpdate struct { + *UserGuildSettings +} + +// UserNoteUpdate is the data for a UserNoteUpdate event. +type UserNoteUpdate struct { + ID string `json:"id"` + Note string `json:"note"` +} + +// VoiceServerUpdate is the data for a VoiceServerUpdate event. +type VoiceServerUpdate struct { + Token string `json:"token"` + GuildID string `json:"guild_id"` + Endpoint string `json:"endpoint"` +} + +// VoiceStateUpdate is the data for a VoiceStateUpdate event. +type VoiceStateUpdate struct { + *VoiceState + // BeforeUpdate will be nil if the VoiceState was not previously cached in the state cache. + BeforeUpdate *VoiceState `json:"-"` +} + +// MessageDeleteBulk is the data for a MessageDeleteBulk event +type MessageDeleteBulk struct { + Messages []string `json:"ids"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id"` +} + +// WebhooksUpdate is the data for a WebhooksUpdate event +type WebhooksUpdate struct { + GuildID string `json:"guild_id"` + ChannelID string `json:"channel_id"` +} + +// InteractionCreate is the data for a InteractionCreate event +type InteractionCreate struct { + *Interaction +} + +// UnmarshalJSON is a helper function to unmarshal Interaction object. +func (i *InteractionCreate) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &i.Interaction) +} + +// InviteCreate is the data for a InviteCreate event +type InviteCreate struct { + *Invite + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id"` +} + +// InviteDelete is the data for a InviteDelete event +type InviteDelete struct { + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id"` + Code string `json:"code"` +} diff --git a/vendor/github.com/bwmarrin/discordgo/interactions.go b/vendor/github.com/bwmarrin/discordgo/interactions.go new file mode 100644 index 00000000..0e5ae3c4 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/interactions.go @@ -0,0 +1,568 @@ +package discordgo + +import ( + "bytes" + "crypto/ed25519" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "time" +) + +// InteractionDeadline is the time allowed to respond to an interaction. +const InteractionDeadline = time.Second * 3 + +// ApplicationCommandType represents the type of application command. +type ApplicationCommandType uint8 + +// Application command types +const ( + // ChatApplicationCommand is default command type. They are slash commands (i.e. called directly from the chat). + ChatApplicationCommand ApplicationCommandType = 1 + // UserApplicationCommand adds command to user context menu. + UserApplicationCommand ApplicationCommandType = 2 + // MessageApplicationCommand adds command to message context menu. + MessageApplicationCommand ApplicationCommandType = 3 +) + +// ApplicationCommand represents an application's slash command. +type ApplicationCommand struct { + ID string `json:"id,omitempty"` + ApplicationID string `json:"application_id,omitempty"` + Version string `json:"version,omitempty"` + Type ApplicationCommandType `json:"type,omitempty"` + Name string `json:"name"` + DefaultPermission *bool `json:"default_permission,omitempty"` + + // NOTE: Chat commands only. Otherwise it mustn't be set. + + Description string `json:"description,omitempty"` + Options []*ApplicationCommandOption `json:"options"` +} + +// ApplicationCommandOptionType indicates the type of a slash command's option. +type ApplicationCommandOptionType uint8 + +// Application command option types. +const ( + ApplicationCommandOptionSubCommand ApplicationCommandOptionType = 1 + ApplicationCommandOptionSubCommandGroup ApplicationCommandOptionType = 2 + ApplicationCommandOptionString ApplicationCommandOptionType = 3 + ApplicationCommandOptionInteger ApplicationCommandOptionType = 4 + ApplicationCommandOptionBoolean ApplicationCommandOptionType = 5 + ApplicationCommandOptionUser ApplicationCommandOptionType = 6 + ApplicationCommandOptionChannel ApplicationCommandOptionType = 7 + ApplicationCommandOptionRole ApplicationCommandOptionType = 8 + ApplicationCommandOptionMentionable ApplicationCommandOptionType = 9 + ApplicationCommandOptionNumber ApplicationCommandOptionType = 10 + ApplicationCommandOptionAttachment ApplicationCommandOptionType = 11 +) + +func (t ApplicationCommandOptionType) String() string { + switch t { + case ApplicationCommandOptionSubCommand: + return "SubCommand" + case ApplicationCommandOptionSubCommandGroup: + return "SubCommandGroup" + case ApplicationCommandOptionString: + return "String" + case ApplicationCommandOptionInteger: + return "Integer" + case ApplicationCommandOptionBoolean: + return "Boolean" + case ApplicationCommandOptionUser: + return "User" + case ApplicationCommandOptionChannel: + return "Channel" + case ApplicationCommandOptionRole: + return "Role" + case ApplicationCommandOptionMentionable: + return "Mentionable" + case ApplicationCommandOptionNumber: + return "Number" + case ApplicationCommandOptionAttachment: + return "Attachment" + } + return fmt.Sprintf("ApplicationCommandOptionType(%d)", t) +} + +// ApplicationCommandOption represents an option/subcommand/subcommands group. +type ApplicationCommandOption struct { + Type ApplicationCommandOptionType `json:"type"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + // NOTE: This feature was on the API, but at some point developers decided to remove it. + // So I commented it, until it will be officially on the docs. + // Default bool `json:"default"` + + ChannelTypes []ChannelType `json:"channel_types"` + Required bool `json:"required"` + Options []*ApplicationCommandOption `json:"options"` + + // NOTE: mutually exclusive with Choices. + Autocomplete bool `json:"autocomplete"` + Choices []*ApplicationCommandOptionChoice `json:"choices"` + // Minimal value of number/integer option. + MinValue *float64 `json:"min_value,omitempty"` + // Maximum value of number/integer option. + MaxValue float64 `json:"max_value,omitempty"` +} + +// ApplicationCommandOptionChoice represents a slash command option choice. +type ApplicationCommandOptionChoice struct { + Name string `json:"name"` + Value interface{} `json:"value"` +} + +// ApplicationCommandPermissions represents a single user or role permission for a command. +type ApplicationCommandPermissions struct { + ID string `json:"id"` + Type ApplicationCommandPermissionType `json:"type"` + Permission bool `json:"permission"` +} + +// ApplicationCommandPermissionsList represents a list of ApplicationCommandPermissions, needed for serializing to JSON. +type ApplicationCommandPermissionsList struct { + Permissions []*ApplicationCommandPermissions `json:"permissions"` +} + +// GuildApplicationCommandPermissions represents all permissions for a single guild command. +type GuildApplicationCommandPermissions struct { + ID string `json:"id"` + ApplicationID string `json:"application_id"` + GuildID string `json:"guild_id"` + Permissions []*ApplicationCommandPermissions `json:"permissions"` +} + +// ApplicationCommandPermissionType indicates whether a permission is user or role based. +type ApplicationCommandPermissionType uint8 + +// Application command permission types. +const ( + ApplicationCommandPermissionTypeRole ApplicationCommandPermissionType = 1 + ApplicationCommandPermissionTypeUser ApplicationCommandPermissionType = 2 +) + +// InteractionType indicates the type of an interaction event. +type InteractionType uint8 + +// Interaction types +const ( + InteractionPing InteractionType = 1 + InteractionApplicationCommand InteractionType = 2 + InteractionMessageComponent InteractionType = 3 + InteractionApplicationCommandAutocomplete InteractionType = 4 + InteractionModalSubmit InteractionType = 5 +) + +func (t InteractionType) String() string { + switch t { + case InteractionPing: + return "Ping" + case InteractionApplicationCommand: + return "ApplicationCommand" + case InteractionMessageComponent: + return "MessageComponent" + case InteractionModalSubmit: + return "ModalSubmit" + } + return fmt.Sprintf("InteractionType(%d)", t) +} + +// Interaction represents data of an interaction. +type Interaction struct { + ID string `json:"id"` + Type InteractionType `json:"type"` + Data InteractionData `json:"data"` + GuildID string `json:"guild_id"` + ChannelID string `json:"channel_id"` + + // The message on which interaction was used. + // NOTE: this field is only filled when a button click triggered the interaction. Otherwise it will be nil. + Message *Message `json:"message"` + + // The member who invoked this interaction. + // NOTE: this field is only filled when the slash command was invoked in a guild; + // if it was invoked in a DM, the `User` field will be filled instead. + // Make sure to check for `nil` before using this field. + Member *Member `json:"member"` + // The user who invoked this interaction. + // NOTE: this field is only filled when the slash command was invoked in a DM; + // if it was invoked in a guild, the `Member` field will be filled instead. + // Make sure to check for `nil` before using this field. + User *User `json:"user"` + + // The user's discord client locale. + Locale Locale `json:"locale"` + // The guild's locale. This defaults to EnglishUS + // NOTE: this field is only filled when the interaction was invoked in a guild. + GuildLocale *Locale `json:"guild_locale"` + + Token string `json:"token"` + Version int `json:"version"` +} + +type interaction Interaction + +type rawInteraction struct { + interaction + Data json.RawMessage `json:"data"` +} + +// UnmarshalJSON is a method for unmarshalling JSON object to Interaction. +func (i *Interaction) UnmarshalJSON(raw []byte) error { + var tmp rawInteraction + err := json.Unmarshal(raw, &tmp) + if err != nil { + return err + } + + *i = Interaction(tmp.interaction) + + switch tmp.Type { + case InteractionApplicationCommand, InteractionApplicationCommandAutocomplete: + v := ApplicationCommandInteractionData{} + err = json.Unmarshal(tmp.Data, &v) + if err != nil { + return err + } + i.Data = v + case InteractionMessageComponent: + v := MessageComponentInteractionData{} + err = json.Unmarshal(tmp.Data, &v) + if err != nil { + return err + } + i.Data = v + case InteractionModalSubmit: + v := ModalSubmitInteractionData{} + err = json.Unmarshal(tmp.Data, &v) + if err != nil { + return err + } + i.Data = v + } + return nil +} + +// MessageComponentData is helper function to assert the inner InteractionData to MessageComponentInteractionData. +// Make sure to check that the Type of the interaction is InteractionMessageComponent before calling. +func (i Interaction) MessageComponentData() (data MessageComponentInteractionData) { + if i.Type != InteractionMessageComponent { + panic("MessageComponentData called on interaction of type " + i.Type.String()) + } + return i.Data.(MessageComponentInteractionData) +} + +// ApplicationCommandData is helper function to assert the inner InteractionData to ApplicationCommandInteractionData. +// Make sure to check that the Type of the interaction is InteractionApplicationCommand before calling. +func (i Interaction) ApplicationCommandData() (data ApplicationCommandInteractionData) { + if i.Type != InteractionApplicationCommand && i.Type != InteractionApplicationCommandAutocomplete { + panic("ApplicationCommandData called on interaction of type " + i.Type.String()) + } + return i.Data.(ApplicationCommandInteractionData) +} + +// ModalSubmitData is helper function to assert the inner InteractionData to ModalSubmitInteractionData. +// Make sure to check that the Type of the interaction is InteractionModalSubmit before calling. +func (i Interaction) ModalSubmitData() (data ModalSubmitInteractionData) { + if i.Type != InteractionModalSubmit { + panic("ModalSubmitData called on interaction of type " + i.Type.String()) + } + return i.Data.(ModalSubmitInteractionData) +} + +// InteractionData is a common interface for all types of interaction data. +type InteractionData interface { + Type() InteractionType +} + +// ApplicationCommandInteractionData contains the data of application command interaction. +type ApplicationCommandInteractionData struct { + ID string `json:"id"` + Name string `json:"name"` + Resolved *ApplicationCommandInteractionDataResolved `json:"resolved"` + + // Slash command options + Options []*ApplicationCommandInteractionDataOption `json:"options"` + // Target (user/message) id on which context menu command was called. + // The details are stored in Resolved according to command type. + TargetID string `json:"target_id"` +} + +// ApplicationCommandInteractionDataResolved contains resolved data of command execution. +// Partial Member objects are missing user, deaf and mute fields. +// Partial Channel objects only have id, name, type and permissions fields. +type ApplicationCommandInteractionDataResolved struct { + Users map[string]*User `json:"users"` + Members map[string]*Member `json:"members"` + Roles map[string]*Role `json:"roles"` + Channels map[string]*Channel `json:"channels"` + Messages map[string]*Message `json:"messages"` + Attachments map[string]*MessageAttachment `json:"attachments"` +} + +// Type returns the type of interaction data. +func (ApplicationCommandInteractionData) Type() InteractionType { + return InteractionApplicationCommand +} + +// MessageComponentInteractionData contains the data of message component interaction. +type MessageComponentInteractionData struct { + CustomID string `json:"custom_id"` + ComponentType ComponentType `json:"component_type"` + + // NOTE: Only filled when ComponentType is SelectMenuComponent (3). Otherwise is nil. + Values []string `json:"values"` +} + +// Type returns the type of interaction data. +func (MessageComponentInteractionData) Type() InteractionType { + return InteractionMessageComponent +} + +// ModalSubmitInteractionData contains the data of modal submit interaction. +type ModalSubmitInteractionData struct { + CustomID string `json:"custom_id"` + Components []MessageComponent `json:"-"` +} + +// Type returns the type of interaction data. +func (ModalSubmitInteractionData) Type() InteractionType { + return InteractionModalSubmit +} + +// UnmarshalJSON is a helper function to correctly unmarshal Components. +func (d *ModalSubmitInteractionData) UnmarshalJSON(data []byte) error { + type modalSubmitInteractionData ModalSubmitInteractionData + var v struct { + modalSubmitInteractionData + RawComponents []unmarshalableMessageComponent `json:"components"` + } + err := json.Unmarshal(data, &v) + if err != nil { + return err + } + *d = ModalSubmitInteractionData(v.modalSubmitInteractionData) + d.Components = make([]MessageComponent, len(v.RawComponents)) + for i, v := range v.RawComponents { + d.Components[i] = v.MessageComponent + } + return err +} + +// ApplicationCommandInteractionDataOption represents an option of a slash command. +type ApplicationCommandInteractionDataOption struct { + Name string `json:"name"` + Type ApplicationCommandOptionType `json:"type"` + // NOTE: Contains the value specified by Type. + Value interface{} `json:"value,omitempty"` + Options []*ApplicationCommandInteractionDataOption `json:"options,omitempty"` + + // NOTE: autocomplete interaction only. + Focused bool `json:"focused,omitempty"` +} + +// IntValue is a utility function for casting option value to integer +func (o ApplicationCommandInteractionDataOption) IntValue() int64 { + if o.Type != ApplicationCommandOptionInteger { + panic("IntValue called on data option of type " + o.Type.String()) + } + return int64(o.Value.(float64)) +} + +// UintValue is a utility function for casting option value to unsigned integer +func (o ApplicationCommandInteractionDataOption) UintValue() uint64 { + if o.Type != ApplicationCommandOptionInteger { + panic("UintValue called on data option of type " + o.Type.String()) + } + return uint64(o.Value.(float64)) +} + +// FloatValue is a utility function for casting option value to float +func (o ApplicationCommandInteractionDataOption) FloatValue() float64 { + if o.Type != ApplicationCommandOptionNumber { + panic("FloatValue called on data option of type " + o.Type.String()) + } + return o.Value.(float64) +} + +// StringValue is a utility function for casting option value to string +func (o ApplicationCommandInteractionDataOption) StringValue() string { + if o.Type != ApplicationCommandOptionString { + panic("StringValue called on data option of type " + o.Type.String()) + } + return o.Value.(string) +} + +// BoolValue is a utility function for casting option value to bool +func (o ApplicationCommandInteractionDataOption) BoolValue() bool { + if o.Type != ApplicationCommandOptionBoolean { + panic("BoolValue called on data option of type " + o.Type.String()) + } + return o.Value.(bool) +} + +// ChannelValue is a utility function for casting option value to channel object. +// s : Session object, if not nil, function additionally fetches all channel's data +func (o ApplicationCommandInteractionDataOption) ChannelValue(s *Session) *Channel { + if o.Type != ApplicationCommandOptionChannel { + panic("ChannelValue called on data option of type " + o.Type.String()) + } + chanID := o.Value.(string) + + if s == nil { + return &Channel{ID: chanID} + } + + ch, err := s.State.Channel(chanID) + if err != nil { + ch, err = s.Channel(chanID) + if err != nil { + return &Channel{ID: chanID} + } + } + + return ch +} + +// RoleValue is a utility function for casting option value to role object. +// s : Session object, if not nil, function additionally fetches all role's data +func (o ApplicationCommandInteractionDataOption) RoleValue(s *Session, gID string) *Role { + if o.Type != ApplicationCommandOptionRole && o.Type != ApplicationCommandOptionMentionable { + panic("RoleValue called on data option of type " + o.Type.String()) + } + roleID := o.Value.(string) + + if s == nil || gID == "" { + return &Role{ID: roleID} + } + + r, err := s.State.Role(roleID, gID) + if err != nil { + roles, err := s.GuildRoles(gID) + if err == nil { + for _, r = range roles { + if r.ID == roleID { + return r + } + } + } + return &Role{ID: roleID} + } + + return r +} + +// UserValue is a utility function for casting option value to user object. +// s : Session object, if not nil, function additionally fetches all user's data +func (o ApplicationCommandInteractionDataOption) UserValue(s *Session) *User { + if o.Type != ApplicationCommandOptionUser && o.Type != ApplicationCommandOptionMentionable { + panic("UserValue called on data option of type " + o.Type.String()) + } + userID := o.Value.(string) + + if s == nil { + return &User{ID: userID} + } + + u, err := s.User(userID) + if err != nil { + return &User{ID: userID} + } + + return u +} + +// InteractionResponseType is type of interaction response. +type InteractionResponseType uint8 + +// Interaction response types. +const ( + // InteractionResponsePong is for ACK ping event. + InteractionResponsePong InteractionResponseType = 1 + // InteractionResponseChannelMessageWithSource is for responding with a message, showing the user's input. + InteractionResponseChannelMessageWithSource InteractionResponseType = 4 + // InteractionResponseDeferredChannelMessageWithSource acknowledges that the event was received, and that a follow-up will come later. + InteractionResponseDeferredChannelMessageWithSource InteractionResponseType = 5 + // InteractionResponseDeferredMessageUpdate acknowledges that the message component interaction event was received, and message will be updated later. + InteractionResponseDeferredMessageUpdate InteractionResponseType = 6 + // InteractionResponseUpdateMessage is for updating the message to which message component was attached. + InteractionResponseUpdateMessage InteractionResponseType = 7 + // InteractionApplicationCommandAutocompleteResult shows autocompletion results. Autocomplete interaction only. + InteractionApplicationCommandAutocompleteResult InteractionResponseType = 8 + // InteractionResponseModal is for responding to an interaction with a modal window. + InteractionResponseModal InteractionResponseType = 9 +) + +// InteractionResponse represents a response for an interaction event. +type InteractionResponse struct { + Type InteractionResponseType `json:"type,omitempty"` + Data *InteractionResponseData `json:"data,omitempty"` +} + +// InteractionResponseData is response data for an interaction. +type InteractionResponseData struct { + TTS bool `json:"tts"` + Content string `json:"content"` + Components []MessageComponent `json:"components"` + Embeds []*MessageEmbed `json:"embeds,omitempty"` + AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` + Flags uint64 `json:"flags,omitempty"` + Files []*File `json:"-"` + + // NOTE: autocomplete interaction only. + Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"` + + // NOTE: modal interaction only. + + CustomID string `json:"custom_id,omitempty"` + Title string `json:"title,omitempty"` +} + +// VerifyInteraction implements message verification of the discord interactions api +// signing algorithm, as documented here: +// https://discord.com/developers/docs/interactions/receiving-and-responding#security-and-authorization +func VerifyInteraction(r *http.Request, key ed25519.PublicKey) bool { + var msg bytes.Buffer + + signature := r.Header.Get("X-Signature-Ed25519") + if signature == "" { + return false + } + + sig, err := hex.DecodeString(signature) + if err != nil { + return false + } + + if len(sig) != ed25519.SignatureSize { + return false + } + + timestamp := r.Header.Get("X-Signature-Timestamp") + if timestamp == "" { + return false + } + + msg.WriteString(timestamp) + + defer r.Body.Close() + var body bytes.Buffer + + // at the end of the function, copy the original body back into the request + defer func() { + r.Body = ioutil.NopCloser(&body) + }() + + // copy body into buffers + _, err = io.Copy(&msg, io.TeeReader(r.Body, &body)) + if err != nil { + return false + } + + return ed25519.Verify(key, msg.Bytes(), sig) +} diff --git a/vendor/github.com/bwmarrin/discordgo/locales.go b/vendor/github.com/bwmarrin/discordgo/locales.go new file mode 100644 index 00000000..a6a1c521 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/locales.go @@ -0,0 +1,83 @@ +package discordgo + +// Locale represents the accepted languages for Discord. +// https://discord.com/developers/docs/reference#locales +type Locale string + +// String returns the human-readable string of the locale +func (l Locale) String() string { + if name, ok := Locales[l]; ok { + return name + } + return Unknown.String() +} + +// All defined locales in Discord +const ( + EnglishUS Locale = "en-US" + EnglishGB Locale = "en-GB" + Bulgarian Locale = "bg" + ChineseCN Locale = "zh-CN" + ChineseTW Locale = "zh-TW" + Croatian Locale = "hr" + Czech Locale = "cs" + Danish Locale = "da" + Dutch Locale = "nl" + Finnish Locale = "fi" + French Locale = "fr" + German Locale = "de" + Greek Locale = "el" + Hindi Locale = "hi" + Hungarian Locale = "hu" + Italian Locale = "it" + Japanese Locale = "ja" + Korean Locale = "ko" + Lithuanian Locale = "lt" + Norwegian Locale = "no" + Polish Locale = "pl" + PortugueseBR Locale = "pt-BR" + Romanian Locale = "ro" + Russian Locale = "ru" + SpanishES Locale = "es-ES" + Swedish Locale = "sv-SE" + Thai Locale = "th" + Turkish Locale = "tr" + Ukrainian Locale = "uk" + Vietnamese Locale = "vi" + Unknown Locale = "" +) + +// Locales is a map of all the languages codes to their names. +var Locales = map[Locale]string{ + EnglishUS: "English (United States)", + EnglishGB: "English (Great Britain)", + Bulgarian: "Bulgarian", + ChineseCN: "Chinese (China)", + ChineseTW: "Chinese (Taiwan)", + Croatian: "Croatian", + Czech: "Czech", + Danish: "Danish", + Dutch: "Dutch", + Finnish: "Finnish", + French: "French", + German: "German", + Greek: "Greek", + Hindi: "Hindi", + Hungarian: "Hungarian", + Italian: "Italian", + Japanese: "Japanese", + Korean: "Korean", + Lithuanian: "Lithuanian", + Norwegian: "Norwegian", + Polish: "Polish", + PortugueseBR: "Portuguese (Brazil)", + Romanian: "Romanian", + Russian: "Russian", + SpanishES: "Spanish (Spain)", + Swedish: "Swedish", + Thai: "Thai", + Turkish: "Turkish", + Ukrainian: "Ukrainian", + Vietnamese: "Vietnamese", + Unknown: "unknown", +} diff --git a/vendor/github.com/bwmarrin/discordgo/logging.go b/vendor/github.com/bwmarrin/discordgo/logging.go new file mode 100644 index 00000000..41f0481f --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/logging.go @@ -0,0 +1,103 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains code related to discordgo package logging + +package discordgo + +import ( + "fmt" + "log" + "runtime" + "strings" +) + +const ( + + // LogError level is used for critical errors that could lead to data loss + // or panic that would not be returned to a calling function. + LogError int = iota + + // LogWarning level is used for very abnormal events and errors that are + // also returned to a calling function. + LogWarning + + // LogInformational level is used for normal non-error activity + LogInformational + + // LogDebug level is for very detailed non-error activity. This is + // very spammy and will impact performance. + LogDebug +) + +// Logger can be used to replace the standard logging for discordgo +var Logger func(msgL, caller int, format string, a ...interface{}) + +// msglog provides package wide logging consistency for discordgo +// the format, a... portion this command follows that of fmt.Printf +// msgL : LogLevel of the message +// caller : 1 + the number of callers away from the message source +// format : Printf style message format +// a ... : comma separated list of values to pass +func msglog(msgL, caller int, format string, a ...interface{}) { + + if Logger != nil { + Logger(msgL, caller, format, a...) + } else { + + pc, file, line, _ := runtime.Caller(caller) + + files := strings.Split(file, "/") + file = files[len(files)-1] + + name := runtime.FuncForPC(pc).Name() + fns := strings.Split(name, ".") + name = fns[len(fns)-1] + + msg := fmt.Sprintf(format, a...) + + log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) + } +} + +// helper function that wraps msglog for the Session struct +// This adds a check to insure the message is only logged +// if the session log level is equal or higher than the +// message log level +func (s *Session) log(msgL int, format string, a ...interface{}) { + + if msgL > s.LogLevel { + return + } + + msglog(msgL, 2, format, a...) +} + +// helper function that wraps msglog for the VoiceConnection struct +// This adds a check to insure the message is only logged +// if the voice connection log level is equal or higher than the +// message log level +func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) { + + if msgL > v.LogLevel { + return + } + + msglog(msgL, 2, format, a...) +} + +// printJSON is a helper function to display JSON data in a easy to read format. +/* NOT USED ATM +func printJSON(body []byte) { + var prettyJSON bytes.Buffer + error := json.Indent(&prettyJSON, body, "", "\t") + if error != nil { + log.Print("JSON parse error: ", error) + } + log.Println(string(prettyJSON.Bytes())) +} +*/ diff --git a/vendor/github.com/bwmarrin/discordgo/message.go b/vendor/github.com/bwmarrin/discordgo/message.go new file mode 100644 index 00000000..eb2f4962 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/message.go @@ -0,0 +1,541 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains code related to the Message struct + +package discordgo + +import ( + "encoding/json" + "io" + "regexp" + "strings" + "time" +) + +// MessageType is the type of Message +// https://discord.com/developers/docs/resources/channel#message-object-message-types +type MessageType int + +// Block contains the valid known MessageType values +const ( + MessageTypeDefault MessageType = 0 + MessageTypeRecipientAdd MessageType = 1 + MessageTypeRecipientRemove MessageType = 2 + MessageTypeCall MessageType = 3 + MessageTypeChannelNameChange MessageType = 4 + MessageTypeChannelIconChange MessageType = 5 + MessageTypeChannelPinnedMessage MessageType = 6 + MessageTypeGuildMemberJoin MessageType = 7 + MessageTypeUserPremiumGuildSubscription MessageType = 8 + MessageTypeUserPremiumGuildSubscriptionTierOne MessageType = 9 + MessageTypeUserPremiumGuildSubscriptionTierTwo MessageType = 10 + MessageTypeUserPremiumGuildSubscriptionTierThree MessageType = 11 + MessageTypeChannelFollowAdd MessageType = 12 + MessageTypeGuildDiscoveryDisqualified MessageType = 14 + MessageTypeGuildDiscoveryRequalified MessageType = 15 + MessageTypeThreadCreated MessageType = 18 + MessageTypeReply MessageType = 19 + MessageTypeChatInputCommand MessageType = 20 + MessageTypeThreadStarterMessage MessageType = 21 + MessageTypeContextMenuCommand MessageType = 23 +) + +// A Message stores all data related to a specific Discord message. +type Message struct { + // The ID of the message. + ID string `json:"id"` + + // The ID of the channel in which the message was sent. + ChannelID string `json:"channel_id"` + + // The ID of the guild in which the message was sent. + GuildID string `json:"guild_id,omitempty"` + + // The content of the message. + Content string `json:"content"` + + // The time at which the messsage was sent. + // CAUTION: this field may be removed in a + // future API version; it is safer to calculate + // the creation time via the ID. + Timestamp time.Time `json:"timestamp"` + + // The time at which the last edit of the message + // occurred, if it has been edited. + EditedTimestamp *time.Time `json:"edited_timestamp"` + + // The roles mentioned in the message. + MentionRoles []string `json:"mention_roles"` + + // Whether the message is text-to-speech. + TTS bool `json:"tts"` + + // Whether the message mentions everyone. + MentionEveryone bool `json:"mention_everyone"` + + // The author of the message. This is not guaranteed to be a + // valid user (webhook-sent messages do not possess a full author). + Author *User `json:"author"` + + // A list of attachments present in the message. + Attachments []*MessageAttachment `json:"attachments"` + + // A list of components attached to the message. + Components []MessageComponent `json:"-"` + + // A list of embeds present in the message. + Embeds []*MessageEmbed `json:"embeds"` + + // A list of users mentioned in the message. + Mentions []*User `json:"mentions"` + + // A list of reactions to the message. + Reactions []*MessageReactions `json:"reactions"` + + // Whether the message is pinned or not. + Pinned bool `json:"pinned"` + + // The type of the message. + Type MessageType `json:"type"` + + // The webhook ID of the message, if it was generated by a webhook + WebhookID string `json:"webhook_id"` + + // Member properties for this message's author, + // contains only partial information + Member *Member `json:"member"` + + // Channels specifically mentioned in this message + // Not all channel mentions in a message will appear in mention_channels. + // Only textual channels that are visible to everyone in a lurkable guild will ever be included. + // Only crossposted messages (via Channel Following) currently include mention_channels at all. + // If no mentions in the message meet these requirements, this field will not be sent. + MentionChannels []*Channel `json:"mention_channels"` + + // Is sent with Rich Presence-related chat embeds + Activity *MessageActivity `json:"activity"` + + // Is sent with Rich Presence-related chat embeds + Application *MessageApplication `json:"application"` + + // MessageReference contains reference data sent with crossposted or reply messages. + // This does not contain the reference *to* this message; this is for when *this* message references another. + // To generate a reference to this message, use (*Message).Reference(). + MessageReference *MessageReference `json:"message_reference"` + + // The message associated with the message_reference + // NOTE: This field is only returned for messages with a type of 19 (REPLY) or 21 (THREAD_STARTER_MESSAGE). + // If the message is a reply but the referenced_message field is not present, + // the backend did not attempt to fetch the message that was being replied to, so its state is unknown. + // If the field exists but is null, the referenced message was deleted. + ReferencedMessage *Message `json:"referenced_message"` + + // Is sent when the message is a response to an Interaction, without an existing message. + // This means responses to message component interactions do not include this property, + // instead including a MessageReference, as components exist on preexisting messages. + Interaction *MessageInteraction `json:"interaction"` + + // The flags of the message, which describe extra features of a message. + // This is a combination of bit masks; the presence of a certain permission can + // be checked by performing a bitwise AND between this int and the flag. + Flags MessageFlags `json:"flags"` + + // The thread that was started from this message, includes thread member object + Thread *Channel `json:"thread,omitempty"` + + // An array of Sticker objects, if any were sent. + StickerItems []*Sticker `json:"sticker_items"` +} + +// UnmarshalJSON is a helper function to unmarshal the Message. +func (m *Message) UnmarshalJSON(data []byte) error { + type message Message + var v struct { + message + RawComponents []unmarshalableMessageComponent `json:"components"` + } + err := json.Unmarshal(data, &v) + if err != nil { + return err + } + *m = Message(v.message) + m.Components = make([]MessageComponent, len(v.RawComponents)) + for i, v := range v.RawComponents { + m.Components[i] = v.MessageComponent + } + return err +} + +// GetCustomEmojis pulls out all the custom (Non-unicode) emojis from a message and returns a Slice of the Emoji struct. +func (m *Message) GetCustomEmojis() []*Emoji { + var toReturn []*Emoji + emojis := EmojiRegex.FindAllString(m.Content, -1) + if len(emojis) < 1 { + return toReturn + } + for _, em := range emojis { + parts := strings.Split(em, ":") + toReturn = append(toReturn, &Emoji{ + ID: parts[2][:len(parts[2])-1], + Name: parts[1], + Animated: strings.HasPrefix(em, " mentions with the +// username of the mention. +func (m *Message) ContentWithMentionsReplaced() (content string) { + content = m.Content + + for _, user := range m.Mentions { + content = strings.NewReplacer( + "<@"+user.ID+">", "@"+user.Username, + "<@!"+user.ID+">", "@"+user.Username, + ).Replace(content) + } + return +} + +var patternChannels = regexp.MustCompile("<#[^>]*>") + +// ContentWithMoreMentionsReplaced will replace all @ mentions with the +// username of the mention, but also role IDs and more. +func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, err error) { + content = m.Content + + if !s.StateEnabled { + content = m.ContentWithMentionsReplaced() + return + } + + channel, err := s.State.Channel(m.ChannelID) + if err != nil { + content = m.ContentWithMentionsReplaced() + return + } + + for _, user := range m.Mentions { + nick := user.Username + + member, err := s.State.Member(channel.GuildID, user.ID) + if err == nil && member.Nick != "" { + nick = member.Nick + } + + content = strings.NewReplacer( + "<@"+user.ID+">", "@"+user.Username, + "<@!"+user.ID+">", "@"+nick, + ).Replace(content) + } + for _, roleID := range m.MentionRoles { + role, err := s.State.Role(channel.GuildID, roleID) + if err != nil || !role.Mentionable { + continue + } + + content = strings.Replace(content, "<@&"+role.ID+">", "@"+role.Name, -1) + } + + content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string { + channel, err := s.State.Channel(mention[2 : len(mention)-1]) + if err != nil || channel.Type == ChannelTypeGuildVoice { + return mention + } + + return "#" + channel.Name + }) + return +} + +// MessageInteraction contains information about the application command interaction which generated the message. +type MessageInteraction struct { + ID string `json:"id"` + Type InteractionType `json:"type"` + Name string `json:"name"` + User *User `json:"user"` + + // Member is only present when the interaction is from a guild. + Member *Member `json:"member"` +} diff --git a/vendor/github.com/bwmarrin/discordgo/mkdocs.yml b/vendor/github.com/bwmarrin/discordgo/mkdocs.yml new file mode 100644 index 00000000..3ee8eb37 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/mkdocs.yml @@ -0,0 +1,17 @@ +site_name: DiscordGo +site_author: Bruce Marriner +site_url: http://bwmarrin.github.io/discordgo/ +repo_url: https://github.com/bwmarrin/discordgo + +dev_addr: 0.0.0.0:8000 +theme: yeti + +markdown_extensions: + - smarty + - toc: + permalink: True + - sane_lists + +pages: + - 'Home': 'index.md' + - 'Getting Started': 'GettingStarted.md' diff --git a/vendor/github.com/bwmarrin/discordgo/oauth2.go b/vendor/github.com/bwmarrin/discordgo/oauth2.go new file mode 100644 index 00000000..2fa2d8d5 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/oauth2.go @@ -0,0 +1,154 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains functions related to Discord OAuth2 endpoints + +package discordgo + +// ------------------------------------------------------------------------------------------------ +// Code specific to Discord OAuth2 Applications +// ------------------------------------------------------------------------------------------------ + +// The MembershipState represents whether the user is in the team or has been invited into it +type MembershipState int + +// Constants for the different stages of the MembershipState +const ( + MembershipStateInvited MembershipState = 1 + MembershipStateAccepted MembershipState = 2 +) + +// A TeamMember struct stores values for a single Team Member, extending the normal User data - note that the user field is partial +type TeamMember struct { + User *User `json:"user"` + TeamID string `json:"team_id"` + MembershipState MembershipState `json:"membership_state"` + Permissions []string `json:"permissions"` +} + +// A Team struct stores the members of a Discord Developer Team as well as some metadata about it +type Team struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Icon string `json:"icon"` + OwnerID string `json:"owner_user_id"` + Members []*TeamMember `json:"members"` +} + +// Application returns an Application structure of a specific Application +// appID : The ID of an Application +func (s *Session) Application(appID string) (st *Application, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointOAuth2Application(appID), nil, EndpointOAuth2Application("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// Applications returns all applications for the authenticated user +func (s *Session) Applications() (st []*Application, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointOAuth2Applications, nil, EndpointOAuth2Applications) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ApplicationCreate creates a new Application +// name : Name of Application / Bot +// uris : Redirect URIs (Not required) +func (s *Session) ApplicationCreate(ap *Application) (st *Application, err error) { + + data := struct { + Name string `json:"name"` + Description string `json:"description"` + }{ap.Name, ap.Description} + + body, err := s.RequestWithBucketID("POST", EndpointOAuth2Applications, data, EndpointOAuth2Applications) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ApplicationUpdate updates an existing Application +// var : desc +func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Application, err error) { + + data := struct { + Name string `json:"name"` + Description string `json:"description"` + }{ap.Name, ap.Description} + + body, err := s.RequestWithBucketID("PUT", EndpointOAuth2Application(appID), data, EndpointOAuth2Application("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ApplicationDelete deletes an existing Application +// appID : The ID of an Application +func (s *Session) ApplicationDelete(appID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointOAuth2Application(appID), nil, EndpointOAuth2Application("")) + if err != nil { + return + } + + return +} + +// Asset struct stores values for an asset of an application +type Asset struct { + Type int `json:"type"` + ID string `json:"id"` + Name string `json:"name"` +} + +// ApplicationAssets returns an application's assets +func (s *Session) ApplicationAssets(appID string) (ass []*Asset, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointOAuth2ApplicationAssets(appID), nil, EndpointOAuth2ApplicationAssets("")) + if err != nil { + return + } + + err = unmarshal(body, &ass) + return +} + +// ------------------------------------------------------------------------------------------------ +// Code specific to Discord OAuth2 Application Bots +// ------------------------------------------------------------------------------------------------ + +// ApplicationBotCreate creates an Application Bot Account +// +// appID : The ID of an Application +// +// NOTE: func name may change, if I can think up something better. +func (s *Session) ApplicationBotCreate(appID string) (st *User, err error) { + + body, err := s.RequestWithBucketID("POST", EndpointOAuth2ApplicationsBot(appID), nil, EndpointOAuth2ApplicationsBot("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} diff --git a/vendor/github.com/bwmarrin/discordgo/ratelimit.go b/vendor/github.com/bwmarrin/discordgo/ratelimit.go new file mode 100644 index 00000000..c992fd45 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/ratelimit.go @@ -0,0 +1,197 @@ +package discordgo + +import ( + "math" + "net/http" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +// customRateLimit holds information for defining a custom rate limit +type customRateLimit struct { + suffix string + requests int + reset time.Duration +} + +// RateLimiter holds all ratelimit buckets +type RateLimiter struct { + sync.Mutex + global *int64 + buckets map[string]*Bucket + globalRateLimit time.Duration + customRateLimits []*customRateLimit +} + +// NewRatelimiter returns a new RateLimiter +func NewRatelimiter() *RateLimiter { + + return &RateLimiter{ + buckets: make(map[string]*Bucket), + global: new(int64), + customRateLimits: []*customRateLimit{ + { + suffix: "//reactions//", + requests: 1, + reset: 200 * time.Millisecond, + }, + }, + } +} + +// GetBucket retrieves or creates a bucket +func (r *RateLimiter) GetBucket(key string) *Bucket { + r.Lock() + defer r.Unlock() + + if bucket, ok := r.buckets[key]; ok { + return bucket + } + + b := &Bucket{ + Remaining: 1, + Key: key, + global: r.global, + } + + // Check if there is a custom ratelimit set for this bucket ID. + for _, rl := range r.customRateLimits { + if strings.HasSuffix(b.Key, rl.suffix) { + b.customRateLimit = rl + break + } + } + + r.buckets[key] = b + return b +} + +// GetWaitTime returns the duration you should wait for a Bucket +func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration { + // If we ran out of calls and the reset time is still ahead of us + // then we need to take it easy and relax a little + if b.Remaining < minRemaining && b.reset.After(time.Now()) { + return b.reset.Sub(time.Now()) + } + + // Check for global ratelimits + sleepTo := time.Unix(0, atomic.LoadInt64(r.global)) + if now := time.Now(); now.Before(sleepTo) { + return sleepTo.Sub(now) + } + + return 0 +} + +// LockBucket Locks until a request can be made +func (r *RateLimiter) LockBucket(bucketID string) *Bucket { + return r.LockBucketObject(r.GetBucket(bucketID)) +} + +// LockBucketObject Locks an already resolved bucket until a request can be made +func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket { + b.Lock() + + if wait := r.GetWaitTime(b, 1); wait > 0 { + time.Sleep(wait) + } + + b.Remaining-- + return b +} + +// Bucket represents a ratelimit bucket, each bucket gets ratelimited individually (-global ratelimits) +type Bucket struct { + sync.Mutex + Key string + Remaining int + limit int + reset time.Time + global *int64 + + lastReset time.Time + customRateLimit *customRateLimit + Userdata interface{} +} + +// Release unlocks the bucket and reads the headers to update the buckets ratelimit info +// and locks up the whole thing in case if there's a global ratelimit. +func (b *Bucket) Release(headers http.Header) error { + defer b.Unlock() + + // Check if the bucket uses a custom ratelimiter + if rl := b.customRateLimit; rl != nil { + if time.Now().Sub(b.lastReset) >= rl.reset { + b.Remaining = rl.requests - 1 + b.lastReset = time.Now() + } + if b.Remaining < 1 { + b.reset = time.Now().Add(rl.reset) + } + return nil + } + + if headers == nil { + return nil + } + + remaining := headers.Get("X-RateLimit-Remaining") + reset := headers.Get("X-RateLimit-Reset") + global := headers.Get("X-RateLimit-Global") + resetAfter := headers.Get("X-RateLimit-Reset-After") + + // Update global and per bucket reset time if the proper headers are available + // If global is set, then it will block all buckets until after Retry-After + // If Retry-After without global is provided it will use that for the new reset + // time since it's more accurate than X-RateLimit-Reset. + // If Retry-After after is not proided, it will update the reset time from X-RateLimit-Reset + if resetAfter != "" { + parsedAfter, err := strconv.ParseFloat(resetAfter, 64) + if err != nil { + return err + } + + whole, frac := math.Modf(parsedAfter) + resetAt := time.Now().Add(time.Duration(whole) * time.Second).Add(time.Duration(frac*1000) * time.Millisecond) + + // Lock either this single bucket or all buckets + if global != "" { + atomic.StoreInt64(b.global, resetAt.UnixNano()) + } else { + b.reset = resetAt + } + } else if reset != "" { + // Calculate the reset time by using the date header returned from discord + discordTime, err := http.ParseTime(headers.Get("Date")) + if err != nil { + return err + } + + unix, err := strconv.ParseFloat(reset, 64) + if err != nil { + return err + } + + // Calculate the time until reset and add it to the current local time + // some extra time is added because without it i still encountered 429's. + // The added amount is the lowest amount that gave no 429's + // in 1k requests + whole, frac := math.Modf(unix) + delta := time.Unix(int64(whole), 0).Add(time.Duration(frac*1000)*time.Millisecond).Sub(discordTime) + time.Millisecond*250 + b.reset = time.Now().Add(delta) + } + + // Udpate remaining if header is present + if remaining != "" { + parsedRemaining, err := strconv.ParseInt(remaining, 10, 32) + if err != nil { + return err + } + b.Remaining = int(parsedRemaining) + } + + return nil +} diff --git a/vendor/github.com/bwmarrin/discordgo/restapi.go b/vendor/github.com/bwmarrin/discordgo/restapi.go new file mode 100644 index 00000000..41796fe2 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/restapi.go @@ -0,0 +1,2871 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains functions for interacting with the Discord REST/JSON API +// at the lowest level. + +package discordgo + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "image" + _ "image/jpeg" // For JPEG decoding + _ "image/png" // For PNG decoding + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +// All error constants +var ( + ErrJSONUnmarshal = errors.New("json unmarshal") + ErrStatusOffline = errors.New("You can't set your Status to offline") + ErrVerificationLevelBounds = errors.New("VerificationLevel out of bounds, should be between 0 and 3") + ErrPruneDaysBounds = errors.New("the number of days should be more than or equal to 1") + ErrGuildNoIcon = errors.New("guild does not have an icon set") + ErrGuildNoSplash = errors.New("guild does not have a splash set") + ErrUnauthorized = errors.New("HTTP request was unauthorized. This could be because the provided token was not a bot token. Please add \"Bot \" to the start of your token. https://discord.com/developers/docs/reference#authentication-example-bot-token-authorization-header") +) + +// Request is the same as RequestWithBucketID but the bucket id is the same as the urlStr +func (s *Session) Request(method, urlStr string, data interface{}) (response []byte, err error) { + return s.RequestWithBucketID(method, urlStr, data, strings.SplitN(urlStr, "?", 2)[0]) +} + +// RequestWithBucketID makes a (GET/POST/...) Requests to Discord REST API with JSON data. +func (s *Session) RequestWithBucketID(method, urlStr string, data interface{}, bucketID string) (response []byte, err error) { + var body []byte + if data != nil { + body, err = json.Marshal(data) + if err != nil { + return + } + } + + return s.request(method, urlStr, "application/json", body, bucketID, 0) +} + +// request makes a (GET/POST/...) Requests to Discord REST API. +// Sequence is the sequence number, if it fails with a 502 it will +// retry with sequence+1 until it either succeeds or sequence >= session.MaxRestRetries +func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID string, sequence int) (response []byte, err error) { + if bucketID == "" { + bucketID = strings.SplitN(urlStr, "?", 2)[0] + } + return s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucket(bucketID), sequence) +} + +// RequestWithLockedBucket makes a request using a bucket that's already been locked +func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b []byte, bucket *Bucket, sequence int) (response []byte, err error) { + if s.Debug { + log.Printf("API REQUEST %8s :: %s\n", method, urlStr) + log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b)) + } + + req, err := http.NewRequest(method, urlStr, bytes.NewBuffer(b)) + if err != nil { + bucket.Release(nil) + return + } + + // Not used on initial login.. + // TODO: Verify if a login, otherwise complain about no-token + if s.Token != "" { + req.Header.Set("authorization", s.Token) + } + + // Discord's API returns a 400 Bad Request is Content-Type is set, but the + // request body is empty. + if b != nil { + req.Header.Set("Content-Type", contentType) + } + + // TODO: Make a configurable static variable. + req.Header.Set("User-Agent", s.UserAgent) + + if s.Debug { + for k, v := range req.Header { + log.Printf("API REQUEST HEADER :: [%s] = %+v\n", k, v) + } + } + + resp, err := s.Client.Do(req) + if err != nil { + bucket.Release(nil) + return + } + defer func() { + err2 := resp.Body.Close() + if err2 != nil { + log.Println("error closing resp body") + } + }() + + err = bucket.Release(resp.Header) + if err != nil { + return + } + + response, err = ioutil.ReadAll(resp.Body) + if err != nil { + return + } + + if s.Debug { + + log.Printf("API RESPONSE STATUS :: %s\n", resp.Status) + for k, v := range resp.Header { + log.Printf("API RESPONSE HEADER :: [%s] = %+v\n", k, v) + } + log.Printf("API RESPONSE BODY :: [%s]\n\n\n", response) + } + + switch resp.StatusCode { + case http.StatusOK: + case http.StatusCreated: + case http.StatusNoContent: + case http.StatusBadGateway: + // Retry sending request if possible + if sequence < s.MaxRestRetries { + + s.log(LogInformational, "%s Failed (%s), Retrying...", urlStr, resp.Status) + response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence+1) + } else { + err = fmt.Errorf("Exceeded Max retries HTTP %s, %s", resp.Status, response) + } + case 429: // TOO MANY REQUESTS - Rate limiting + rl := TooManyRequests{} + err = json.Unmarshal(response, &rl) + if err != nil { + s.log(LogError, "rate limit unmarshal error, %s", err) + return + } + s.log(LogInformational, "Rate Limiting %s, retry in %v", urlStr, rl.RetryAfter) + s.handleEvent(rateLimitEventType, &RateLimit{TooManyRequests: &rl, URL: urlStr}) + + time.Sleep(rl.RetryAfter) + // we can make the above smarter + // this method can cause longer delays than required + + response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence) + case http.StatusUnauthorized: + if strings.Index(s.Token, "Bot ") != 0 { + s.log(LogInformational, ErrUnauthorized.Error()) + err = ErrUnauthorized + } + fallthrough + default: // Error condition + err = newRestError(req, resp, response) + } + + return +} + +func unmarshal(data []byte, v interface{}) error { + err := json.Unmarshal(data, v) + if err != nil { + return fmt.Errorf("%w: %s", ErrJSONUnmarshal, err) + } + + return nil +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Discord Users +// ------------------------------------------------------------------------------------------------ + +// User returns the user details of the given userID +// userID : A user ID or "@me" which is a shortcut of current user ID +func (s *Session) User(userID string) (st *User, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointUser(userID), nil, EndpointUsers) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// UserAvatar is deprecated. Please use UserAvatarDecode +// userID : A user ID or "@me" which is a shortcut of current user ID +func (s *Session) UserAvatar(userID string) (img image.Image, err error) { + u, err := s.User(userID) + if err != nil { + return + } + img, err = s.UserAvatarDecode(u) + return +} + +// UserAvatarDecode returns an image.Image of a user's Avatar +// user : The user which avatar should be retrieved +func (s *Session) UserAvatarDecode(u *User) (img image.Image, err error) { + body, err := s.RequestWithBucketID("GET", EndpointUserAvatar(u.ID, u.Avatar), nil, EndpointUserAvatar("", "")) + if err != nil { + return + } + + img, _, err = image.Decode(bytes.NewReader(body)) + return +} + +// UserUpdate updates current user settings. +func (s *Session) UserUpdate(username, avatar string) (st *User, err error) { + + // NOTE: Avatar must be either the hash/id of existing Avatar or + // data:image/png;base64,BASE64_STRING_OF_NEW_AVATAR_PNG + // to set a new avatar. + // If left blank, avatar will be set to null/blank + + data := struct { + Username string `json:"username,omitempty"` + Avatar string `json:"avatar,omitempty"` + }{username, avatar} + + body, err := s.RequestWithBucketID("PATCH", EndpointUser("@me"), data, EndpointUsers) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// UserConnections returns the user's connections +func (s *Session) UserConnections() (conn []*UserConnection, err error) { + response, err := s.RequestWithBucketID("GET", EndpointUserConnections("@me"), nil, EndpointUserConnections("@me")) + if err != nil { + return nil, err + } + + err = unmarshal(response, &conn) + if err != nil { + return + } + + return +} + +// UserChannelCreate creates a new User (Private) Channel with another User +// recipientID : A user ID for the user to which this channel is opened with. +func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) { + + data := struct { + RecipientID string `json:"recipient_id"` + }{recipientID} + + body, err := s.RequestWithBucketID("POST", EndpointUserChannels("@me"), data, EndpointUserChannels("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// UserGuilds returns an array of UserGuild structures for all guilds. +// limit : The number guilds that can be returned. (max 100) +// beforeID : If provided all guilds returned will be before given ID. +// afterID : If provided all guilds returned will be after given ID. +func (s *Session) UserGuilds(limit int, beforeID, afterID string) (st []*UserGuild, err error) { + + v := url.Values{} + + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + if afterID != "" { + v.Set("after", afterID) + } + if beforeID != "" { + v.Set("before", beforeID) + } + + uri := EndpointUserGuilds("@me") + + if len(v) > 0 { + uri += "?" + v.Encode() + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointUserGuilds("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// UserChannelPermissions returns the permission of a user in a channel. +// userID : The ID of the user to calculate permissions for. +// channelID : The ID of the channel to calculate permission for. +// +// NOTE: This function is now deprecated and will be removed in the future. +// Please see the same function inside state.go +func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions int64, err error) { + // Try to just get permissions from state. + apermissions, err = s.State.UserChannelPermissions(userID, channelID) + if err == nil { + return + } + + // Otherwise try get as much data from state as possible, falling back to the network. + channel, err := s.State.Channel(channelID) + if err != nil || channel == nil { + channel, err = s.Channel(channelID) + if err != nil { + return + } + } + + guild, err := s.State.Guild(channel.GuildID) + if err != nil || guild == nil { + guild, err = s.Guild(channel.GuildID) + if err != nil { + return + } + } + + if userID == guild.OwnerID { + apermissions = PermissionAll + return + } + + member, err := s.State.Member(guild.ID, userID) + if err != nil || member == nil { + member, err = s.GuildMember(guild.ID, userID) + if err != nil { + return + } + } + + return memberPermissions(guild, channel, userID, member.Roles), nil +} + +// Calculates the permissions for a member. +// https://support.discord.com/hc/en-us/articles/206141927-How-is-the-permission-hierarchy-structured- +func memberPermissions(guild *Guild, channel *Channel, userID string, roles []string) (apermissions int64) { + if userID == guild.OwnerID { + apermissions = PermissionAll + return + } + + for _, role := range guild.Roles { + if role.ID == guild.ID { + apermissions |= role.Permissions + break + } + } + + for _, role := range guild.Roles { + for _, roleID := range roles { + if role.ID == roleID { + apermissions |= role.Permissions + break + } + } + } + + if apermissions&PermissionAdministrator == PermissionAdministrator { + apermissions |= PermissionAll + } + + // Apply @everyone overrides from the channel. + for _, overwrite := range channel.PermissionOverwrites { + if guild.ID == overwrite.ID { + apermissions &= ^overwrite.Deny + apermissions |= overwrite.Allow + break + } + } + + var denies, allows int64 + // Member overwrites can override role overrides, so do two passes + for _, overwrite := range channel.PermissionOverwrites { + for _, roleID := range roles { + if overwrite.Type == PermissionOverwriteTypeRole && roleID == overwrite.ID { + denies |= overwrite.Deny + allows |= overwrite.Allow + break + } + } + } + + apermissions &= ^denies + apermissions |= allows + + for _, overwrite := range channel.PermissionOverwrites { + if overwrite.Type == PermissionOverwriteTypeMember && overwrite.ID == userID { + apermissions &= ^overwrite.Deny + apermissions |= overwrite.Allow + break + } + } + + if apermissions&PermissionAdministrator == PermissionAdministrator { + apermissions |= PermissionAllChannel + } + + return apermissions +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Discord Guilds +// ------------------------------------------------------------------------------------------------ + +// Guild returns a Guild structure of a specific Guild. +// guildID : The ID of a Guild +func (s *Session) Guild(guildID string) (st *Guild, err error) { + body, err := s.RequestWithBucketID("GET", EndpointGuild(guildID), nil, EndpointGuild(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildPreview returns a GuildPreview structure of a specific public Guild. +// guildID : The ID of a Guild +func (s *Session) GuildPreview(guildID string) (st *GuildPreview, err error) { + body, err := s.RequestWithBucketID("GET", EndpointGuildPreview(guildID), nil, EndpointGuildPreview(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildCreate creates a new Guild +// name : A name for the Guild (2-100 characters) +func (s *Session) GuildCreate(name string) (st *Guild, err error) { + + data := struct { + Name string `json:"name"` + }{name} + + body, err := s.RequestWithBucketID("POST", EndpointGuildCreate, data, EndpointGuildCreate) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildEdit edits a new Guild +// guildID : The ID of a Guild +// g : A GuildParams struct with the values Name, Region and VerificationLevel defined. +func (s *Session) GuildEdit(guildID string, g GuildParams) (st *Guild, err error) { + + // Bounds checking for VerificationLevel, interval: [0, 4] + if g.VerificationLevel != nil { + val := *g.VerificationLevel + if val < 0 || val > 4 { + err = ErrVerificationLevelBounds + return + } + } + + //Bounds checking for regions + if g.Region != "" { + isValid := false + regions, _ := s.VoiceRegions() + for _, r := range regions { + if g.Region == r.ID { + isValid = true + } + } + if !isValid { + var valid []string + for _, r := range regions { + valid = append(valid, r.ID) + } + err = fmt.Errorf("Region not a valid region (%q)", valid) + return + } + } + + body, err := s.RequestWithBucketID("PATCH", EndpointGuild(guildID), g, EndpointGuild(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildDelete deletes a Guild. +// guildID : The ID of a Guild +func (s *Session) GuildDelete(guildID string) (st *Guild, err error) { + + body, err := s.RequestWithBucketID("DELETE", EndpointGuild(guildID), nil, EndpointGuild(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildLeave leaves a Guild. +// guildID : The ID of a Guild +func (s *Session) GuildLeave(guildID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointUserGuild("@me", guildID), nil, EndpointUserGuild("", guildID)) + return +} + +// GuildBans returns an array of GuildBan structures for all bans of a +// given guild. +// guildID : The ID of a Guild. +func (s *Session) GuildBans(guildID string) (st []*GuildBan, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildBans(guildID), nil, EndpointGuildBans(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildBanCreate bans the given user from the given guild. +// guildID : The ID of a Guild. +// userID : The ID of a User +// days : The number of days of previous comments to delete. +func (s *Session) GuildBanCreate(guildID, userID string, days int) (err error) { + return s.GuildBanCreateWithReason(guildID, userID, "", days) +} + +// GuildBan finds ban by given guild and user id and returns GuildBan structure +func (s *Session) GuildBan(guildID, userID string) (st *GuildBan, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildBan(guildID, userID), nil, EndpointGuildBan(guildID, userID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildBanCreateWithReason bans the given user from the given guild also providing a reaso. +// guildID : The ID of a Guild. +// userID : The ID of a User +// reason : The reason for this ban +// days : The number of days of previous comments to delete. +func (s *Session) GuildBanCreateWithReason(guildID, userID, reason string, days int) (err error) { + + uri := EndpointGuildBan(guildID, userID) + + queryParams := url.Values{} + if days > 0 { + queryParams.Set("delete_message_days", strconv.Itoa(days)) + } + if reason != "" { + queryParams.Set("reason", reason) + } + + if len(queryParams) > 0 { + uri += "?" + queryParams.Encode() + } + + _, err = s.RequestWithBucketID("PUT", uri, nil, EndpointGuildBan(guildID, "")) + return +} + +// GuildBanDelete removes the given user from the guild bans +// guildID : The ID of a Guild. +// userID : The ID of a User +func (s *Session) GuildBanDelete(guildID, userID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointGuildBan(guildID, userID), nil, EndpointGuildBan(guildID, "")) + return +} + +// GuildMembers returns a list of members for a guild. +// guildID : The ID of a Guild. +// after : The id of the member to return members after +// limit : max number of members to return (max 1000) +func (s *Session) GuildMembers(guildID string, after string, limit int) (st []*Member, err error) { + + uri := EndpointGuildMembers(guildID) + + v := url.Values{} + + if after != "" { + v.Set("after", after) + } + + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + + if len(v) > 0 { + uri += "?" + v.Encode() + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildMembers(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildMember returns a member of a guild. +// guildID : The ID of a Guild. +// userID : The ID of a User +func (s *Session) GuildMember(guildID, userID string) (st *Member, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildMember(guildID, userID), nil, EndpointGuildMember(guildID, "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + // The returned object doesn't have the GuildID attribute so we will set it here. + st.GuildID = guildID + return +} + +// GuildMemberAdd force joins a user to the guild. +// accessToken : Valid access_token for the user. +// guildID : The ID of a Guild. +// userID : The ID of a User. +// nick : Value to set users nickname to +// roles : A list of role ID's to set on the member. +// mute : If the user is muted. +// deaf : If the user is deafened. +func (s *Session) GuildMemberAdd(accessToken, guildID, userID, nick string, roles []string, mute, deaf bool) (err error) { + + data := struct { + AccessToken string `json:"access_token"` + Nick string `json:"nick,omitempty"` + Roles []string `json:"roles,omitempty"` + Mute bool `json:"mute,omitempty"` + Deaf bool `json:"deaf,omitempty"` + }{accessToken, nick, roles, mute, deaf} + + _, err = s.RequestWithBucketID("PUT", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) + if err != nil { + return err + } + + return err +} + +// GuildMemberDelete removes the given user from the given guild. +// guildID : The ID of a Guild. +// userID : The ID of a User +func (s *Session) GuildMemberDelete(guildID, userID string) (err error) { + + return s.GuildMemberDeleteWithReason(guildID, userID, "") +} + +// GuildMemberDeleteWithReason removes the given user from the given guild. +// guildID : The ID of a Guild. +// userID : The ID of a User +// reason : The reason for the kick +func (s *Session) GuildMemberDeleteWithReason(guildID, userID, reason string) (err error) { + + uri := EndpointGuildMember(guildID, userID) + if reason != "" { + uri += "?reason=" + url.QueryEscape(reason) + } + + _, err = s.RequestWithBucketID("DELETE", uri, nil, EndpointGuildMember(guildID, "")) + return +} + +// GuildMemberEdit edits the roles of a member. +// guildID : The ID of a Guild. +// userID : The ID of a User. +// roles : A list of role ID's to set on the member. +func (s *Session) GuildMemberEdit(guildID, userID string, roles []string) (err error) { + + data := struct { + Roles []string `json:"roles"` + }{roles} + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) + return +} + +// GuildMemberMove moves a guild member from one voice channel to another/none +// guildID : The ID of a Guild. +// userID : The ID of a User. +// channelID : The ID of a channel to move user to or nil to remove from voice channel +// NOTE : I am not entirely set on the name of this function and it may change +// prior to the final 1.0.0 release of Discordgo +func (s *Session) GuildMemberMove(guildID string, userID string, channelID *string) (err error) { + data := struct { + ChannelID *string `json:"channel_id"` + }{channelID} + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) + return +} + +// GuildMemberNickname updates the nickname of a guild member +// guildID : The ID of a guild +// userID : The ID of a user +// userID : The ID of a user or "@me" which is a shortcut of the current user ID +// nickname : The nickname of the member, "" will reset their nickname +func (s *Session) GuildMemberNickname(guildID, userID, nickname string) (err error) { + + data := struct { + Nick string `json:"nick"` + }{nickname} + + if userID == "@me" { + userID += "/nick" + } + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) + return +} + +// GuildMemberMute server mutes a guild member +// guildID : The ID of a Guild. +// userID : The ID of a User. +// mute : boolean value for if the user should be muted +func (s *Session) GuildMemberMute(guildID string, userID string, mute bool) (err error) { + data := struct { + Mute bool `json:"mute"` + }{mute} + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) + return +} + +// GuildMemberTimeout times out a guild member +// guildID : The ID of a Guild. +// userID : The ID of a User. +// until : The timestamp for how long a member should be timed out. +// Set to nil to remove timeout. +func (s *Session) GuildMemberTimeout(guildID string, userID string, until *time.Time) (err error) { + data := struct { + CommunicationDisabledUntil *time.Time `json:"communication_disabled_until"` + }{until} + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) + return +} + +// GuildMemberDeafen server deafens a guild member +// guildID : The ID of a Guild. +// userID : The ID of a User. +// deaf : boolean value for if the user should be deafened +func (s *Session) GuildMemberDeafen(guildID string, userID string, deaf bool) (err error) { + data := struct { + Deaf bool `json:"deaf"` + }{deaf} + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) + return +} + +// GuildMemberRoleAdd adds the specified role to a given member +// guildID : The ID of a Guild. +// userID : The ID of a User. +// roleID : The ID of a Role to be assigned to the user. +func (s *Session) GuildMemberRoleAdd(guildID, userID, roleID string) (err error) { + + _, err = s.RequestWithBucketID("PUT", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, "", "")) + + return +} + +// GuildMemberRoleRemove removes the specified role to a given member +// guildID : The ID of a Guild. +// userID : The ID of a User. +// roleID : The ID of a Role to be removed from the user. +func (s *Session) GuildMemberRoleRemove(guildID, userID, roleID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, "", "")) + + return +} + +// GuildChannels returns an array of Channel structures for all channels of a +// given guild. +// guildID : The ID of a Guild. +func (s *Session) GuildChannels(guildID string) (st []*Channel, err error) { + + body, err := s.request("GET", EndpointGuildChannels(guildID), "", nil, EndpointGuildChannels(guildID), 0) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildChannelCreateData is provided to GuildChannelCreateComplex +type GuildChannelCreateData struct { + Name string `json:"name"` + Type ChannelType `json:"type"` + Topic string `json:"topic,omitempty"` + Bitrate int `json:"bitrate,omitempty"` + UserLimit int `json:"user_limit,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` + Position int `json:"position,omitempty"` + PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID string `json:"parent_id,omitempty"` + NSFW bool `json:"nsfw,omitempty"` +} + +// GuildChannelCreateComplex creates a new channel in the given guild +// guildID : The ID of a Guild +// data : A data struct describing the new Channel, Name and Type are mandatory, other fields depending on the type +func (s *Session) GuildChannelCreateComplex(guildID string, data GuildChannelCreateData) (st *Channel, err error) { + body, err := s.RequestWithBucketID("POST", EndpointGuildChannels(guildID), data, EndpointGuildChannels(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildChannelCreate creates a new channel in the given guild +// guildID : The ID of a Guild. +// name : Name of the channel (2-100 chars length) +// ctype : Type of the channel +func (s *Session) GuildChannelCreate(guildID, name string, ctype ChannelType) (st *Channel, err error) { + return s.GuildChannelCreateComplex(guildID, GuildChannelCreateData{ + Name: name, + Type: ctype, + }) +} + +// GuildChannelsReorder updates the order of channels in a guild +// guildID : The ID of a Guild. +// channels : Updated channels. +func (s *Session) GuildChannelsReorder(guildID string, channels []*Channel) (err error) { + + data := make([]struct { + ID string `json:"id"` + Position int `json:"position"` + }, len(channels)) + + for i, c := range channels { + data[i].ID = c.ID + data[i].Position = c.Position + } + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildChannels(guildID), data, EndpointGuildChannels(guildID)) + return +} + +// GuildInvites returns an array of Invite structures for the given guild +// guildID : The ID of a Guild. +func (s *Session) GuildInvites(guildID string) (st []*Invite, err error) { + body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInvites(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildRoles returns all roles for a given guild. +// guildID : The ID of a Guild. +func (s *Session) GuildRoles(guildID string) (st []*Role, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildRoles(guildID), nil, EndpointGuildRoles(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return // TODO return pointer +} + +// GuildRoleCreate returns a new Guild Role. +// guildID: The ID of a Guild. +func (s *Session) GuildRoleCreate(guildID string) (st *Role, err error) { + + body, err := s.RequestWithBucketID("POST", EndpointGuildRoles(guildID), nil, EndpointGuildRoles(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildRoleEdit updates an existing Guild Role with new values +// guildID : The ID of a Guild. +// roleID : The ID of a Role. +// name : The name of the Role. +// color : The color of the role (decimal, not hex). +// hoist : Whether to display the role's users separately. +// perm : The permissions for the role. +// mention : Whether this role is mentionable +func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist bool, perm int64, mention bool) (st *Role, err error) { + + // Prevent sending a color int that is too big. + if color > 0xFFFFFF { + err = fmt.Errorf("color value cannot be larger than 0xFFFFFF") + return nil, err + } + + data := struct { + Name string `json:"name"` // The role's name (overwrites existing) + Color int `json:"color"` // The color the role should have (as a decimal, not hex) + Hoist bool `json:"hoist"` // Whether to display the role's users separately + Permissions int64 `json:"permissions,string"` // The overall permissions number of the role (overwrites existing) + Mentionable bool `json:"mentionable"` // Whether this role is mentionable + }{name, color, hoist, perm, mention} + + body, err := s.RequestWithBucketID("PATCH", EndpointGuildRole(guildID, roleID), data, EndpointGuildRole(guildID, "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildRoleReorder reoders guild roles +// guildID : The ID of a Guild. +// roles : A list of ordered roles. +func (s *Session) GuildRoleReorder(guildID string, roles []*Role) (st []*Role, err error) { + + body, err := s.RequestWithBucketID("PATCH", EndpointGuildRoles(guildID), roles, EndpointGuildRoles(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildRoleDelete deletes an existing role. +// guildID : The ID of a Guild. +// roleID : The ID of a Role. +func (s *Session) GuildRoleDelete(guildID, roleID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointGuildRole(guildID, roleID), nil, EndpointGuildRole(guildID, "")) + + return +} + +// GuildPruneCount Returns the number of members that would be removed in a prune operation. +// Requires 'KICK_MEMBER' permission. +// guildID : The ID of a Guild. +// days : The number of days to count prune for (1 or more). +func (s *Session) GuildPruneCount(guildID string, days uint32) (count uint32, err error) { + count = 0 + + if days <= 0 { + err = ErrPruneDaysBounds + return + } + + p := struct { + Pruned uint32 `json:"pruned"` + }{} + + uri := EndpointGuildPrune(guildID) + "?days=" + strconv.FormatUint(uint64(days), 10) + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildPrune(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &p) + if err != nil { + return + } + + count = p.Pruned + + return +} + +// GuildPrune Begin as prune operation. Requires the 'KICK_MEMBERS' permission. +// Returns an object with one 'pruned' key indicating the number of members that were removed in the prune operation. +// guildID : The ID of a Guild. +// days : The number of days to count prune for (1 or more). +func (s *Session) GuildPrune(guildID string, days uint32) (count uint32, err error) { + + count = 0 + + if days <= 0 { + err = ErrPruneDaysBounds + return + } + + data := struct { + days uint32 + }{days} + + p := struct { + Pruned uint32 `json:"pruned"` + }{} + + body, err := s.RequestWithBucketID("POST", EndpointGuildPrune(guildID), data, EndpointGuildPrune(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &p) + if err != nil { + return + } + + count = p.Pruned + + return +} + +// GuildIntegrations returns an array of Integrations for a guild. +// guildID : The ID of a Guild. +func (s *Session) GuildIntegrations(guildID string) (st []*Integration, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildIntegrations(guildID), nil, EndpointGuildIntegrations(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildIntegrationCreate creates a Guild Integration. +// guildID : The ID of a Guild. +// integrationType : The Integration type. +// integrationID : The ID of an integration. +func (s *Session) GuildIntegrationCreate(guildID, integrationType, integrationID string) (err error) { + + data := struct { + Type string `json:"type"` + ID string `json:"id"` + }{integrationType, integrationID} + + _, err = s.RequestWithBucketID("POST", EndpointGuildIntegrations(guildID), data, EndpointGuildIntegrations(guildID)) + return +} + +// GuildIntegrationEdit edits a Guild Integration. +// guildID : The ID of a Guild. +// integrationType : The Integration type. +// integrationID : The ID of an integration. +// expireBehavior : The behavior when an integration subscription lapses (see the integration object documentation). +// expireGracePeriod : Period (in seconds) where the integration will ignore lapsed subscriptions. +// enableEmoticons : Whether emoticons should be synced for this integration (twitch only currently). +func (s *Session) GuildIntegrationEdit(guildID, integrationID string, expireBehavior, expireGracePeriod int, enableEmoticons bool) (err error) { + + data := struct { + ExpireBehavior int `json:"expire_behavior"` + ExpireGracePeriod int `json:"expire_grace_period"` + EnableEmoticons bool `json:"enable_emoticons"` + }{expireBehavior, expireGracePeriod, enableEmoticons} + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildIntegration(guildID, integrationID), data, EndpointGuildIntegration(guildID, "")) + return +} + +// GuildIntegrationDelete removes the given integration from the Guild. +// guildID : The ID of a Guild. +// integrationID : The ID of an integration. +func (s *Session) GuildIntegrationDelete(guildID, integrationID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointGuildIntegration(guildID, integrationID), nil, EndpointGuildIntegration(guildID, "")) + return +} + +// GuildIcon returns an image.Image of a guild icon. +// guildID : The ID of a Guild. +func (s *Session) GuildIcon(guildID string) (img image.Image, err error) { + g, err := s.Guild(guildID) + if err != nil { + return + } + + if g.Icon == "" { + err = ErrGuildNoIcon + return + } + + body, err := s.RequestWithBucketID("GET", EndpointGuildIcon(guildID, g.Icon), nil, EndpointGuildIcon(guildID, "")) + if err != nil { + return + } + + img, _, err = image.Decode(bytes.NewReader(body)) + return +} + +// GuildSplash returns an image.Image of a guild splash image. +// guildID : The ID of a Guild. +func (s *Session) GuildSplash(guildID string) (img image.Image, err error) { + g, err := s.Guild(guildID) + if err != nil { + return + } + + if g.Splash == "" { + err = ErrGuildNoSplash + return + } + + body, err := s.RequestWithBucketID("GET", EndpointGuildSplash(guildID, g.Splash), nil, EndpointGuildSplash(guildID, "")) + if err != nil { + return + } + + img, _, err = image.Decode(bytes.NewReader(body)) + return +} + +// GuildEmbed returns the embed for a Guild. +// guildID : The ID of a Guild. +func (s *Session) GuildEmbed(guildID string) (st *GuildEmbed, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildEmbed(guildID), nil, EndpointGuildEmbed(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildEmbedEdit returns the embed for a Guild. +// guildID : The ID of a Guild. +func (s *Session) GuildEmbedEdit(guildID string, enabled bool, channelID string) (err error) { + + data := GuildEmbed{enabled, channelID} + + _, err = s.RequestWithBucketID("PATCH", EndpointGuildEmbed(guildID), data, EndpointGuildEmbed(guildID)) + return +} + +// GuildAuditLog returns the audit log for a Guild. +// guildID : The ID of a Guild. +// userID : If provided the log will be filtered for the given ID. +// beforeID : If provided all log entries returned will be before the given ID. +// actionType : If provided the log will be filtered for the given Action Type. +// limit : The number messages that can be returned. (default 50, min 1, max 100) +func (s *Session) GuildAuditLog(guildID, userID, beforeID string, actionType, limit int) (st *GuildAuditLog, err error) { + + uri := EndpointGuildAuditLogs(guildID) + + v := url.Values{} + if userID != "" { + v.Set("user_id", userID) + } + if beforeID != "" { + v.Set("before", beforeID) + } + if actionType > 0 { + v.Set("action_type", strconv.Itoa(actionType)) + } + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildAuditLogs(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildEmojis returns all emoji +// guildID : The ID of a Guild. +func (s *Session) GuildEmojis(guildID string) (emoji []*Emoji, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildEmojis(guildID), nil, EndpointGuildEmojis(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &emoji) + return +} + +// GuildEmojiCreate creates a new emoji +// guildID : The ID of a Guild. +// name : The Name of the Emoji. +// image : The base64 encoded emoji image, has to be smaller than 256KB. +// roles : The roles for which this emoji will be whitelisted, can be nil. +func (s *Session) GuildEmojiCreate(guildID, name, image string, roles []string) (emoji *Emoji, err error) { + + data := struct { + Name string `json:"name"` + Image string `json:"image"` + Roles []string `json:"roles,omitempty"` + }{name, image, roles} + + body, err := s.RequestWithBucketID("POST", EndpointGuildEmojis(guildID), data, EndpointGuildEmojis(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &emoji) + return +} + +// GuildEmojiEdit modifies an emoji +// guildID : The ID of a Guild. +// emojiID : The ID of an Emoji. +// name : The Name of the Emoji. +// roles : The roles for which this emoji will be whitelisted, can be nil. +func (s *Session) GuildEmojiEdit(guildID, emojiID, name string, roles []string) (emoji *Emoji, err error) { + + data := struct { + Name string `json:"name"` + Roles []string `json:"roles,omitempty"` + }{name, roles} + + body, err := s.RequestWithBucketID("PATCH", EndpointGuildEmoji(guildID, emojiID), data, EndpointGuildEmojis(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &emoji) + return +} + +// GuildEmojiDelete deletes an Emoji. +// guildID : The ID of a Guild. +// emojiID : The ID of an Emoji. +func (s *Session) GuildEmojiDelete(guildID, emojiID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointGuildEmoji(guildID, emojiID), nil, EndpointGuildEmojis(guildID)) + return +} + +// GuildTemplate returns a GuildTemplate for the given code +// templateCode: The Code of a GuildTemplate +func (s *Session) GuildTemplate(templateCode string) (st *GuildTemplate, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildTemplate(templateCode), nil, EndpointGuildTemplate(templateCode)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildCreateWithTemplate creates a guild based on a GuildTemplate +// templateCode: The Code of a GuildTemplate +// name: The name of the guild (2-100) characters +// icon: base64 encoded 128x128 image for the guild icon +func (s *Session) GuildCreateWithTemplate(templateCode, name, icon string) (st *Guild, err error) { + + data := struct { + Name string `json:"name"` + Icon string `json:"icon"` + }{name, icon} + + body, err := s.RequestWithBucketID("POST", EndpointGuildTemplate(templateCode), data, EndpointGuildTemplate(templateCode)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildTemplates returns all of GuildTemplates +// guildID: The ID of the guild +func (s *Session) GuildTemplates(guildID string) (st []*GuildTemplate, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildTemplates(guildID), nil, EndpointGuildTemplates(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildTemplateCreate creates a template for the guild +// guildID: The ID of the guild +// name: The name of the template (1-100 characters) +// description: The description for the template (0-120 characters) +func (s *Session) GuildTemplateCreate(guildID, name, description string) (st *GuildTemplate) { + + data := struct { + Name string `json:"name"` + Description string `json:"description"` + }{name, description} + + body, err := s.RequestWithBucketID("POST", EndpointGuildTemplates(guildID), data, EndpointGuildTemplates(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildTemplateSync syncs the template to the guild's current state +// guildID: The ID of the guild +// templateCode: The code of the template +func (s *Session) GuildTemplateSync(guildID, templateCode string) (err error) { + + _, err = s.RequestWithBucketID("PUT", EndpointGuildTemplateSync(guildID, templateCode), nil, EndpointGuildTemplateSync(guildID, "")) + return +} + +// GuildTemplateEdit modifies the template's metadata +// guildID: The ID of the guild +// templateCode: The code of the template +// name: The name of the template (1-100 characters) +// description: The description for the template (0-120 characters) +func (s *Session) GuildTemplateEdit(guildID, templateCode, name, description string) (st *GuildTemplate, err error) { + + data := struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + }{name, description} + + body, err := s.RequestWithBucketID("PATCH", EndpointGuildTemplateSync(guildID, templateCode), data, EndpointGuildTemplateSync(guildID, "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildTemplateDelete deletes the template +// guildID: The ID of the guild +// templateCode: The code of the template +func (s *Session) GuildTemplateDelete(guildID, templateCode string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointGuildTemplateSync(guildID, templateCode), nil, EndpointGuildTemplateSync(guildID, "")) + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Discord Channels +// ------------------------------------------------------------------------------------------------ + +// Channel returns a Channel structure of a specific Channel. +// channelID : The ID of the Channel you want returned. +func (s *Session) Channel(channelID string) (st *Channel, err error) { + body, err := s.RequestWithBucketID("GET", EndpointChannel(channelID), nil, EndpointChannel(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ChannelEdit edits the given channel +// channelID : The ID of a Channel +// name : The new name to assign the channel. +func (s *Session) ChannelEdit(channelID, name string) (*Channel, error) { + return s.ChannelEditComplex(channelID, &ChannelEdit{ + Name: name, + }) +} + +// ChannelEditComplex edits an existing channel, replacing the parameters entirely with ChannelEdit struct +// channelID : The ID of a Channel +// data : The channel struct to send +func (s *Session) ChannelEditComplex(channelID string, data *ChannelEdit) (st *Channel, err error) { + body, err := s.RequestWithBucketID("PATCH", EndpointChannel(channelID), data, EndpointChannel(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ChannelDelete deletes the given channel +// channelID : The ID of a Channel +func (s *Session) ChannelDelete(channelID string) (st *Channel, err error) { + + body, err := s.RequestWithBucketID("DELETE", EndpointChannel(channelID), nil, EndpointChannel(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ChannelTyping broadcasts to all members that authenticated user is typing in +// the given channel. +// channelID : The ID of a Channel +func (s *Session) ChannelTyping(channelID string) (err error) { + + _, err = s.RequestWithBucketID("POST", EndpointChannelTyping(channelID), nil, EndpointChannelTyping(channelID)) + return +} + +// ChannelMessages returns an array of Message structures for messages within +// a given channel. +// channelID : The ID of a Channel. +// limit : The number messages that can be returned. (max 100) +// beforeID : If provided all messages returned will be before given ID. +// afterID : If provided all messages returned will be after given ID. +// aroundID : If provided all messages returned will be around given ID. +func (s *Session) ChannelMessages(channelID string, limit int, beforeID, afterID, aroundID string) (st []*Message, err error) { + + uri := EndpointChannelMessages(channelID) + + v := url.Values{} + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + if afterID != "" { + v.Set("after", afterID) + } + if beforeID != "" { + v.Set("before", beforeID) + } + if aroundID != "" { + v.Set("around", aroundID) + } + if len(v) > 0 { + uri += "?" + v.Encode() + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointChannelMessages(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ChannelMessage gets a single message by ID from a given channel. +// channeld : The ID of a Channel +// messageID : the ID of a Message +func (s *Session) ChannelMessage(channelID, messageID string) (st *Message, err error) { + + response, err := s.RequestWithBucketID("GET", EndpointChannelMessage(channelID, messageID), nil, EndpointChannelMessage(channelID, "")) + if err != nil { + return + } + + err = unmarshal(response, &st) + return +} + +// ChannelMessageSend sends a message to the given channel. +// channelID : The ID of a Channel. +// content : The message to send. +func (s *Session) ChannelMessageSend(channelID string, content string) (*Message, error) { + return s.ChannelMessageSendComplex(channelID, &MessageSend{ + Content: content, + }) +} + +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +// ChannelMessageSendComplex sends a message to the given channel. +// channelID : The ID of a Channel. +// data : The message struct to send. +func (s *Session) ChannelMessageSendComplex(channelID string, data *MessageSend) (st *Message, err error) { + // TODO: Remove this when compatibility is not required. + if data.Embed != nil { + if data.Embeds == nil { + data.Embeds = []*MessageEmbed{data.Embed} + } else { + err = fmt.Errorf("cannot specify both Embed and Embeds") + return + } + } + + for _, embed := range data.Embeds { + if embed.Type == "" { + embed.Type = "rich" + } + } + endpoint := EndpointChannelMessages(channelID) + + // TODO: Remove this when compatibility is not required. + files := data.Files + if data.File != nil { + if files == nil { + files = []*File{data.File} + } else { + err = fmt.Errorf("cannot specify both File and Files") + return + } + } + + var response []byte + if len(files) > 0 { + contentType, body, encodeErr := MultipartBodyWithJSON(data, files) + if encodeErr != nil { + return st, encodeErr + } + + response, err = s.request("POST", endpoint, contentType, body, endpoint, 0) + } else { + response, err = s.RequestWithBucketID("POST", endpoint, data, endpoint) + } + if err != nil { + return + } + + err = unmarshal(response, &st) + return +} + +// ChannelMessageSendTTS sends a message to the given channel with Text to Speech. +// channelID : The ID of a Channel. +// content : The message to send. +func (s *Session) ChannelMessageSendTTS(channelID string, content string) (*Message, error) { + return s.ChannelMessageSendComplex(channelID, &MessageSend{ + Content: content, + TTS: true, + }) +} + +// ChannelMessageSendEmbed sends a message to the given channel with embedded data. +// channelID : The ID of a Channel. +// embed : The embed data to send. +func (s *Session) ChannelMessageSendEmbed(channelID string, embed *MessageEmbed) (*Message, error) { + return s.ChannelMessageSendEmbeds(channelID, []*MessageEmbed{embed}) +} + +// ChannelMessageSendEmbeds sends a message to the given channel with multiple embedded data. +// channelID : The ID of a Channel. +// embeds : The embeds data to send. +func (s *Session) ChannelMessageSendEmbeds(channelID string, embeds []*MessageEmbed) (*Message, error) { + return s.ChannelMessageSendComplex(channelID, &MessageSend{ + Embeds: embeds, + }) +} + +// ChannelMessageSendReply sends a message to the given channel with reference data. +// channelID : The ID of a Channel. +// content : The message to send. +// reference : The message reference to send. +func (s *Session) ChannelMessageSendReply(channelID string, content string, reference *MessageReference) (*Message, error) { + if reference == nil { + return nil, fmt.Errorf("reply attempted with nil message reference") + } + return s.ChannelMessageSendComplex(channelID, &MessageSend{ + Content: content, + Reference: reference, + }) +} + +// ChannelMessageEdit edits an existing message, replacing it entirely with +// the given content. +// channelID : The ID of a Channel +// messageID : The ID of a Message +// content : The contents of the message +func (s *Session) ChannelMessageEdit(channelID, messageID, content string) (*Message, error) { + return s.ChannelMessageEditComplex(NewMessageEdit(channelID, messageID).SetContent(content)) +} + +// ChannelMessageEditComplex edits an existing message, replacing it entirely with +// the given MessageEdit struct +func (s *Session) ChannelMessageEditComplex(m *MessageEdit) (st *Message, err error) { + // TODO: Remove this when compatibility is not required. + if m.Embed != nil { + if m.Embeds == nil { + m.Embeds = []*MessageEmbed{m.Embed} + } else { + err = fmt.Errorf("cannot specify both Embed and Embeds") + return + } + } + + for _, embed := range m.Embeds { + if embed.Type == "" { + embed.Type = "rich" + } + } + response, err := s.RequestWithBucketID("PATCH", EndpointChannelMessage(m.Channel, m.ID), m, EndpointChannelMessage(m.Channel, "")) + if err != nil { + return + } + + err = unmarshal(response, &st) + return +} + +// ChannelMessageEditEmbed edits an existing message with embedded data. +// channelID : The ID of a Channel +// messageID : The ID of a Message +// embed : The embed data to send +func (s *Session) ChannelMessageEditEmbed(channelID, messageID string, embed *MessageEmbed) (*Message, error) { + return s.ChannelMessageEditEmbeds(channelID, messageID, []*MessageEmbed{embed}) +} + +// ChannelMessageEditEmbeds edits an existing message with multiple embedded data. +// channelID : The ID of a Channel +// messageID : The ID of a Message +// embeds : The embeds data to send +func (s *Session) ChannelMessageEditEmbeds(channelID, messageID string, embeds []*MessageEmbed) (*Message, error) { + return s.ChannelMessageEditComplex(NewMessageEdit(channelID, messageID).SetEmbeds(embeds)) +} + +// ChannelMessageDelete deletes a message from the Channel. +func (s *Session) ChannelMessageDelete(channelID, messageID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointChannelMessage(channelID, messageID), nil, EndpointChannelMessage(channelID, "")) + return +} + +// ChannelMessagesBulkDelete bulk deletes the messages from the channel for the provided messageIDs. +// If only one messageID is in the slice call channelMessageDelete function. +// If the slice is empty do nothing. +// channelID : The ID of the channel for the messages to delete. +// messages : The IDs of the messages to be deleted. A slice of string IDs. A maximum of 100 messages. +func (s *Session) ChannelMessagesBulkDelete(channelID string, messages []string) (err error) { + + if len(messages) == 0 { + return + } + + if len(messages) == 1 { + err = s.ChannelMessageDelete(channelID, messages[0]) + return + } + + if len(messages) > 100 { + messages = messages[:100] + } + + data := struct { + Messages []string `json:"messages"` + }{messages} + + _, err = s.RequestWithBucketID("POST", EndpointChannelMessagesBulkDelete(channelID), data, EndpointChannelMessagesBulkDelete(channelID)) + return +} + +// ChannelMessagePin pins a message within a given channel. +// channelID: The ID of a channel. +// messageID: The ID of a message. +func (s *Session) ChannelMessagePin(channelID, messageID string) (err error) { + + _, err = s.RequestWithBucketID("PUT", EndpointChannelMessagePin(channelID, messageID), nil, EndpointChannelMessagePin(channelID, "")) + return +} + +// ChannelMessageUnpin unpins a message within a given channel. +// channelID: The ID of a channel. +// messageID: The ID of a message. +func (s *Session) ChannelMessageUnpin(channelID, messageID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointChannelMessagePin(channelID, messageID), nil, EndpointChannelMessagePin(channelID, "")) + return +} + +// ChannelMessagesPinned returns an array of Message structures for pinned messages +// within a given channel +// channelID : The ID of a Channel. +func (s *Session) ChannelMessagesPinned(channelID string) (st []*Message, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointChannelMessagesPins(channelID), nil, EndpointChannelMessagesPins(channelID)) + + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ChannelFileSend sends a file to the given channel. +// channelID : The ID of a Channel. +// name: The name of the file. +// io.Reader : A reader for the file contents. +func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (*Message, error) { + return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}}) +} + +// ChannelFileSendWithMessage sends a file to the given channel with an message. +// DEPRECATED. Use ChannelMessageSendComplex instead. +// channelID : The ID of a Channel. +// content: Optional Message content. +// name: The name of the file. +// io.Reader : A reader for the file contents. +func (s *Session) ChannelFileSendWithMessage(channelID, content string, name string, r io.Reader) (*Message, error) { + return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}, Content: content}) +} + +// ChannelInvites returns an array of Invite structures for the given channel +// channelID : The ID of a Channel +func (s *Session) ChannelInvites(channelID string) (st []*Invite, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointChannelInvites(channelID), nil, EndpointChannelInvites(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ChannelInviteCreate creates a new invite for the given channel. +// channelID : The ID of a Channel +// i : An Invite struct with the values MaxAge, MaxUses and Temporary defined. +func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, err error) { + + data := struct { + MaxAge int `json:"max_age"` + MaxUses int `json:"max_uses"` + Temporary bool `json:"temporary"` + Unique bool `json:"unique"` + }{i.MaxAge, i.MaxUses, i.Temporary, i.Unique} + + body, err := s.RequestWithBucketID("POST", EndpointChannelInvites(channelID), data, EndpointChannelInvites(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ChannelPermissionSet creates a Permission Override for the given channel. +// NOTE: This func name may changed. Using Set instead of Create because +// you can both create a new override or update an override with this function. +func (s *Session) ChannelPermissionSet(channelID, targetID string, targetType PermissionOverwriteType, allow, deny int64) (err error) { + + data := struct { + ID string `json:"id"` + Type PermissionOverwriteType `json:"type"` + Allow int64 `json:"allow,string"` + Deny int64 `json:"deny,string"` + }{targetID, targetType, allow, deny} + + _, err = s.RequestWithBucketID("PUT", EndpointChannelPermission(channelID, targetID), data, EndpointChannelPermission(channelID, "")) + return +} + +// ChannelPermissionDelete deletes a specific permission override for the given channel. +// NOTE: Name of this func may change. +func (s *Session) ChannelPermissionDelete(channelID, targetID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointChannelPermission(channelID, targetID), nil, EndpointChannelPermission(channelID, "")) + return +} + +// ChannelMessageCrosspost cross posts a message in a news channel to followers +// of the channel +// channelID : The ID of a Channel +// messageID : The ID of a Message +func (s *Session) ChannelMessageCrosspost(channelID, messageID string) (st *Message, err error) { + + endpoint := EndpointChannelMessageCrosspost(channelID, messageID) + + body, err := s.RequestWithBucketID("POST", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ChannelNewsFollow follows a news channel in the targetID +// channelID : The ID of a News Channel +// targetID : The ID of a Channel where the News Channel should post to +func (s *Session) ChannelNewsFollow(channelID, targetID string) (st *ChannelFollow, err error) { + + endpoint := EndpointChannelFollow(channelID) + + data := struct { + WebhookChannelID string `json:"webhook_channel_id"` + }{targetID} + + body, err := s.RequestWithBucketID("POST", endpoint, data, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Discord Invites +// ------------------------------------------------------------------------------------------------ + +// Invite returns an Invite structure of the given invite +// inviteID : The invite code +func (s *Session) Invite(inviteID string) (st *Invite, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID), nil, EndpointInvite("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// InviteWithCounts returns an Invite structure of the given invite including approximate member counts +// inviteID : The invite code +func (s *Session) InviteWithCounts(inviteID string) (st *Invite, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID)+"?with_counts=true", nil, EndpointInvite("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// InviteDelete deletes an existing invite +// inviteID : the code of an invite +func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { + + body, err := s.RequestWithBucketID("DELETE", EndpointInvite(inviteID), nil, EndpointInvite("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// InviteAccept accepts an Invite to a Guild or Channel +// inviteID : The invite code +func (s *Session) InviteAccept(inviteID string) (st *Invite, err error) { + + body, err := s.RequestWithBucketID("POST", EndpointInvite(inviteID), nil, EndpointInvite("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Discord Voice +// ------------------------------------------------------------------------------------------------ + +// VoiceRegions returns the voice server regions +func (s *Session) VoiceRegions() (st []*VoiceRegion, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointVoiceRegions, nil, EndpointVoiceRegions) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Discord Websockets +// ------------------------------------------------------------------------------------------------ + +// Gateway returns the websocket Gateway address +func (s *Session) Gateway() (gateway string, err error) { + + response, err := s.RequestWithBucketID("GET", EndpointGateway, nil, EndpointGateway) + if err != nil { + return + } + + temp := struct { + URL string `json:"url"` + }{} + + err = unmarshal(response, &temp) + if err != nil { + return + } + + gateway = temp.URL + + // Ensure the gateway always has a trailing slash. + // MacOS will fail to connect if we add query params without a trailing slash on the base domain. + if !strings.HasSuffix(gateway, "/") { + gateway += "/" + } + + return +} + +// GatewayBot returns the websocket Gateway address and the recommended number of shards +func (s *Session) GatewayBot() (st *GatewayBotResponse, err error) { + + response, err := s.RequestWithBucketID("GET", EndpointGatewayBot, nil, EndpointGatewayBot) + if err != nil { + return + } + + err = unmarshal(response, &st) + if err != nil { + return + } + + // Ensure the gateway always has a trailing slash. + // MacOS will fail to connect if we add query params without a trailing slash on the base domain. + if !strings.HasSuffix(st.URL, "/") { + st.URL += "/" + } + + return +} + +// Functions specific to Webhooks + +// WebhookCreate returns a new Webhook. +// channelID: The ID of a Channel. +// name : The name of the webhook. +// avatar : The avatar of the webhook. +func (s *Session) WebhookCreate(channelID, name, avatar string) (st *Webhook, err error) { + + data := struct { + Name string `json:"name"` + Avatar string `json:"avatar,omitempty"` + }{name, avatar} + + body, err := s.RequestWithBucketID("POST", EndpointChannelWebhooks(channelID), data, EndpointChannelWebhooks(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// ChannelWebhooks returns all webhooks for a given channel. +// channelID: The ID of a channel. +func (s *Session) ChannelWebhooks(channelID string) (st []*Webhook, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointChannelWebhooks(channelID), nil, EndpointChannelWebhooks(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildWebhooks returns all webhooks for a given guild. +// guildID: The ID of a Guild. +func (s *Session) GuildWebhooks(guildID string) (st []*Webhook, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildWebhooks(guildID), nil, EndpointGuildWebhooks(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// Webhook returns a webhook for a given ID +// webhookID: The ID of a webhook. +func (s *Session) Webhook(webhookID string) (st *Webhook, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointWebhook(webhookID), nil, EndpointWebhooks) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookWithToken returns a webhook for a given ID +// webhookID: The ID of a webhook. +// token : The auth token for the webhook. +func (s *Session) WebhookWithToken(webhookID, token string) (st *Webhook, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointWebhookToken(webhookID, token), nil, EndpointWebhookToken("", "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookEdit updates an existing Webhook. +// webhookID: The ID of a webhook. +// name : The name of the webhook. +// avatar : The avatar of the webhook. +func (s *Session) WebhookEdit(webhookID, name, avatar, channelID string) (st *Role, err error) { + + data := struct { + Name string `json:"name,omitempty"` + Avatar string `json:"avatar,omitempty"` + ChannelID string `json:"channel_id,omitempty"` + }{name, avatar, channelID} + + body, err := s.RequestWithBucketID("PATCH", EndpointWebhook(webhookID), data, EndpointWebhooks) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookEditWithToken updates an existing Webhook with an auth token. +// webhookID: The ID of a webhook. +// token : The auth token for the webhook. +// name : The name of the webhook. +// avatar : The avatar of the webhook. +func (s *Session) WebhookEditWithToken(webhookID, token, name, avatar string) (st *Role, err error) { + + data := struct { + Name string `json:"name,omitempty"` + Avatar string `json:"avatar,omitempty"` + }{name, avatar} + + body, err := s.RequestWithBucketID("PATCH", EndpointWebhookToken(webhookID, token), data, EndpointWebhookToken("", "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookDelete deletes a webhook for a given ID +// webhookID: The ID of a webhook. +func (s *Session) WebhookDelete(webhookID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointWebhook(webhookID), nil, EndpointWebhooks) + + return +} + +// WebhookDeleteWithToken deletes a webhook for a given ID with an auth token. +// webhookID: The ID of a webhook. +// token : The auth token for the webhook. +func (s *Session) WebhookDeleteWithToken(webhookID, token string) (st *Webhook, err error) { + + body, err := s.RequestWithBucketID("DELETE", EndpointWebhookToken(webhookID, token), nil, EndpointWebhookToken("", "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +func (s *Session) webhookExecute(webhookID, token string, wait bool, threadID string, data *WebhookParams) (st *Message, err error) { + uri := EndpointWebhookToken(webhookID, token) + + v := url.Values{} + if wait { + v.Set("wait", "true") + } + + if threadID != "" { + v.Set("thread_id", threadID) + } + if len(v) != 0 { + uri += "?" + v.Encode() + } + + var response []byte + if len(data.Files) > 0 { + contentType, body, encodeErr := MultipartBodyWithJSON(data, data.Files) + if encodeErr != nil { + return st, encodeErr + } + + response, err = s.request("POST", uri, contentType, body, uri, 0) + } else { + response, err = s.RequestWithBucketID("POST", uri, data, uri) + } + if !wait || err != nil { + return + } + + err = unmarshal(response, &st) + return +} + +// WebhookExecute executes a webhook. +// webhookID: The ID of a webhook. +// token : The auth token for the webhook +// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise) +func (s *Session) WebhookExecute(webhookID, token string, wait bool, data *WebhookParams) (st *Message, err error) { + return s.webhookExecute(webhookID, token, wait, "", data) +} + +// WebhookThreadExecute executes a webhook in a thread. +// webhookID: The ID of a webhook. +// token : The auth token for the webhook +// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise) +// threadID : Sends a message to the specified thread within a webhook's channel. The thread will automatically be unarchived. +func (s *Session) WebhookThreadExecute(webhookID, token string, wait bool, threadID string, data *WebhookParams) (st *Message, err error) { + return s.webhookExecute(webhookID, token, wait, threadID, data) +} + +// WebhookMessage gets a webhook message. +// webhookID : The ID of a webhook +// token : The auth token for the webhook +// messageID : The ID of message to get +func (s *Session) WebhookMessage(webhookID, token, messageID string) (message *Message, err error) { + uri := EndpointWebhookMessage(webhookID, token, messageID) + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointWebhookToken("", "")) + if err != nil { + return + } + + err = json.Unmarshal(body, &message) + + return +} + +// WebhookMessageEdit edits a webhook message and returns a new one. +// webhookID : The ID of a webhook +// token : The auth token for the webhook +// messageID : The ID of message to edit +func (s *Session) WebhookMessageEdit(webhookID, token, messageID string, data *WebhookEdit) (st *Message, err error) { + uri := EndpointWebhookMessage(webhookID, token, messageID) + + var response []byte + if len(data.Files) > 0 { + contentType, body, err := MultipartBodyWithJSON(data, data.Files) + if err != nil { + return nil, err + } + + response, err = s.request("PATCH", uri, contentType, body, uri, 0) + if err != nil { + return nil, err + } + } else { + response, err = s.RequestWithBucketID("PATCH", uri, data, EndpointWebhookToken("", "")) + + if err != nil { + return nil, err + } + } + + err = unmarshal(response, &st) + return +} + +// WebhookMessageDelete deletes a webhook message. +// webhookID : The ID of a webhook +// token : The auth token for the webhook +// messageID : The ID of a message to edit +func (s *Session) WebhookMessageDelete(webhookID, token, messageID string) (err error) { + uri := EndpointWebhookMessage(webhookID, token, messageID) + + _, err = s.RequestWithBucketID("DELETE", uri, nil, EndpointWebhookToken("", "")) + return +} + +// MessageReactionAdd creates an emoji reaction to a message. +// channelID : The channel ID. +// messageID : The message ID. +// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. +func (s *Session) MessageReactionAdd(channelID, messageID, emojiID string) error { + + // emoji such as #⃣ need to have # escaped + emojiID = strings.Replace(emojiID, "#", "%23", -1) + _, err := s.RequestWithBucketID("PUT", EndpointMessageReaction(channelID, messageID, emojiID, "@me"), nil, EndpointMessageReaction(channelID, "", "", "")) + + return err +} + +// MessageReactionRemove deletes an emoji reaction to a message. +// channelID : The channel ID. +// messageID : The message ID. +// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. +// userID : @me or ID of the user to delete the reaction for. +func (s *Session) MessageReactionRemove(channelID, messageID, emojiID, userID string) error { + + // emoji such as #⃣ need to have # escaped + emojiID = strings.Replace(emojiID, "#", "%23", -1) + _, err := s.RequestWithBucketID("DELETE", EndpointMessageReaction(channelID, messageID, emojiID, userID), nil, EndpointMessageReaction(channelID, "", "", "")) + + return err +} + +// MessageReactionsRemoveAll deletes all reactions from a message +// channelID : The channel ID +// messageID : The message ID. +func (s *Session) MessageReactionsRemoveAll(channelID, messageID string) error { + + _, err := s.RequestWithBucketID("DELETE", EndpointMessageReactionsAll(channelID, messageID), nil, EndpointMessageReactionsAll(channelID, messageID)) + + return err +} + +// MessageReactionsRemoveEmoji deletes all reactions of a certain emoji from a message +// channelID : The channel ID +// messageID : The message ID +// emojiID : The emoji ID +func (s *Session) MessageReactionsRemoveEmoji(channelID, messageID, emojiID string) error { + + // emoji such as #⃣ need to have # escaped + emojiID = strings.Replace(emojiID, "#", "%23", -1) + _, err := s.RequestWithBucketID("DELETE", EndpointMessageReactions(channelID, messageID, emojiID), nil, EndpointMessageReactions(channelID, messageID, emojiID)) + + return err +} + +// MessageReactions gets all the users reactions for a specific emoji. +// channelID : The channel ID. +// messageID : The message ID. +// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. +// limit : max number of users to return (max 100) +// beforeID : If provided all reactions returned will be before given ID. +// afterID : If provided all reactions returned will be after given ID. +func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit int, beforeID, afterID string) (st []*User, err error) { + // emoji such as #⃣ need to have # escaped + emojiID = strings.Replace(emojiID, "#", "%23", -1) + uri := EndpointMessageReactions(channelID, messageID, emojiID) + + v := url.Values{} + + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + + if afterID != "" { + v.Set("after", afterID) + } + if beforeID != "" { + v.Set("before", beforeID) + } + + if len(v) > 0 { + uri += "?" + v.Encode() + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointMessageReaction(channelID, "", "", "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to threads +// ------------------------------------------------------------------------------------------------ + +// MessageThreadStartComplex creates a new thread from an existing message. +// channelID : Channel to create thread in +// messageID : Message to start thread from +// data : Parameters of the thread +func (s *Session) MessageThreadStartComplex(channelID, messageID string, data *ThreadStart) (ch *Channel, err error) { + endpoint := EndpointChannelMessageThread(channelID, messageID) + var body []byte + body, err = s.RequestWithBucketID("POST", endpoint, data, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &ch) + return +} + +// MessageThreadStart creates a new thread from an existing message. +// channelID : Channel to create thread in +// messageID : Message to start thread from +// name : Name of the thread +// archiveDuration : Auto archive duration (in minutes) +func (s *Session) MessageThreadStart(channelID, messageID string, name string, archiveDuration int) (ch *Channel, err error) { + return s.MessageThreadStartComplex(channelID, messageID, &ThreadStart{ + Name: name, + AutoArchiveDuration: archiveDuration, + }) +} + +// ThreadStartComplex creates a new thread. +// channelID : Channel to create thread in +// data : Parameters of the thread +func (s *Session) ThreadStartComplex(channelID string, data *ThreadStart) (ch *Channel, err error) { + endpoint := EndpointChannelThreads(channelID) + var body []byte + body, err = s.RequestWithBucketID("POST", endpoint, data, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &ch) + return +} + +// ThreadStart creates a new thread. +// channelID : Channel to create thread in +// name : Name of the thread +// archiveDuration : Auto archive duration (in minutes) +func (s *Session) ThreadStart(channelID, name string, typ ChannelType, archiveDuration int) (ch *Channel, err error) { + return s.ThreadStartComplex(channelID, &ThreadStart{ + Name: name, + Type: typ, + AutoArchiveDuration: archiveDuration, + }) +} + +// ThreadJoin adds current user to a thread +func (s *Session) ThreadJoin(id string) error { + endpoint := EndpointThreadMember(id, "@me") + _, err := s.RequestWithBucketID("PUT", endpoint, nil, endpoint) + return err +} + +// ThreadLeave removes current user to a thread +func (s *Session) ThreadLeave(id string) error { + endpoint := EndpointThreadMember(id, "@me") + _, err := s.RequestWithBucketID("DELETE", endpoint, nil, endpoint) + return err +} + +// ThreadMemberAdd adds another member to a thread +func (s *Session) ThreadMemberAdd(threadID, memberID string) error { + endpoint := EndpointThreadMember(threadID, memberID) + _, err := s.RequestWithBucketID("PUT", endpoint, nil, endpoint) + return err +} + +// ThreadMemberRemove removes another member from a thread +func (s *Session) ThreadMemberRemove(threadID, memberID string) error { + endpoint := EndpointThreadMember(threadID, memberID) + _, err := s.RequestWithBucketID("DELETE", endpoint, nil, endpoint) + return err +} + +// ThreadMember returns thread member object for the specified member of a thread +func (s *Session) ThreadMember(threadID, memberID string) (member *ThreadMember, err error) { + endpoint := EndpointThreadMember(threadID, memberID) + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + + if err != nil { + return + } + + err = unmarshal(body, &member) + return +} + +// ThreadMembers returns all members of specified thread. +func (s *Session) ThreadMembers(threadID string) (members []*ThreadMember, err error) { + var body []byte + body, err = s.RequestWithBucketID("GET", EndpointThreadMembers(threadID), nil, EndpointThreadMembers(threadID)) + + if err != nil { + return + } + + err = unmarshal(body, &members) + return +} + +// ThreadsActive returns all active threads for specified channel. +func (s *Session) ThreadsActive(channelID string) (threads *ThreadsList, err error) { + var body []byte + body, err = s.RequestWithBucketID("GET", EndpointChannelActiveThreads(channelID), nil, EndpointChannelActiveThreads(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &threads) + return +} + +// GuildThreadsActive returns all active threads for specified guild. +func (s *Session) GuildThreadsActive(guildID string) (threads *ThreadsList, err error) { + var body []byte + body, err = s.RequestWithBucketID("GET", EndpointGuildActiveThreads(guildID), nil, EndpointGuildActiveThreads(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &threads) + return +} + +// ThreadsArchived returns archived threads for specified channel. +// before : If specified returns only threads before the timestamp +// limit : Optional maximum amount of threads to return. +func (s *Session) ThreadsArchived(channelID string, before *time.Time, limit int) (threads *ThreadsList, err error) { + endpoint := EndpointChannelPublicArchivedThreads(channelID) + v := url.Values{} + if before != nil { + v.Set("before", before.Format(time.RFC3339)) + } + + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + + if len(v) > 0 { + endpoint += "?" + v.Encode() + } + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &threads) + return +} + +// ThreadsPrivateArchived returns archived private threads for specified channel. +// before : If specified returns only threads before the timestamp +// limit : Optional maximum amount of threads to return. +func (s *Session) ThreadsPrivateArchived(channelID string, before *time.Time, limit int) (threads *ThreadsList, err error) { + endpoint := EndpointChannelPrivateArchivedThreads(channelID) + v := url.Values{} + if before != nil { + v.Set("before", before.Format(time.RFC3339)) + } + + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + + if len(v) > 0 { + endpoint += "?" + v.Encode() + } + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &threads) + return +} + +// ThreadsPrivateJoinedArchived returns archived joined private threads for specified channel. +// before : If specified returns only threads before the timestamp +// limit : Optional maximum amount of threads to return. +func (s *Session) ThreadsPrivateJoinedArchived(channelID string, before *time.Time, limit int) (threads *ThreadsList, err error) { + endpoint := EndpointChannelJoinedPrivateArchivedThreads(channelID) + v := url.Values{} + if before != nil { + v.Set("before", before.Format(time.RFC3339)) + } + + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + + if len(v) > 0 { + endpoint += "?" + v.Encode() + } + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &threads) + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to application (slash) commands +// ------------------------------------------------------------------------------------------------ + +// ApplicationCommandCreate creates a global application command and returns it. +// appID : The application ID. +// guildID : Guild ID to create guild-specific application command. If empty - creates global application command. +// cmd : New application command data. +func (s *Session) ApplicationCommandCreate(appID string, guildID string, cmd *ApplicationCommand) (ccmd *ApplicationCommand, err error) { + endpoint := EndpointApplicationGlobalCommands(appID) + if guildID != "" { + endpoint = EndpointApplicationGuildCommands(appID, guildID) + } + + body, err := s.RequestWithBucketID("POST", endpoint, *cmd, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &ccmd) + + return +} + +// ApplicationCommandEdit edits application command and returns new command data. +// appID : The application ID. +// cmdID : Application command ID to edit. +// guildID : Guild ID to edit guild-specific application command. If empty - edits global application command. +// cmd : Updated application command data. +func (s *Session) ApplicationCommandEdit(appID, guildID, cmdID string, cmd *ApplicationCommand) (updated *ApplicationCommand, err error) { + endpoint := EndpointApplicationGlobalCommand(appID, cmdID) + if guildID != "" { + endpoint = EndpointApplicationGuildCommand(appID, guildID, cmdID) + } + + body, err := s.RequestWithBucketID("PATCH", endpoint, *cmd, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &updated) + + return +} + +// ApplicationCommandBulkOverwrite Creates commands overwriting existing commands. Returns a list of commands. +// appID : The application ID. +// commands : The commands to create. +func (s *Session) ApplicationCommandBulkOverwrite(appID string, guildID string, commands []*ApplicationCommand) (createdCommands []*ApplicationCommand, err error) { + endpoint := EndpointApplicationGlobalCommands(appID) + if guildID != "" { + endpoint = EndpointApplicationGuildCommands(appID, guildID) + } + + body, err := s.RequestWithBucketID("PUT", endpoint, commands, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &createdCommands) + + return +} + +// ApplicationCommandDelete deletes application command by ID. +// appID : The application ID. +// cmdID : Application command ID to delete. +// guildID : Guild ID to delete guild-specific application command. If empty - deletes global application command. +func (s *Session) ApplicationCommandDelete(appID, guildID, cmdID string) error { + endpoint := EndpointApplicationGlobalCommand(appID, cmdID) + if guildID != "" { + endpoint = EndpointApplicationGuildCommand(appID, guildID, cmdID) + } + + _, err := s.RequestWithBucketID("DELETE", endpoint, nil, endpoint) + + return err +} + +// ApplicationCommand retrieves an application command by given ID. +// appID : The application ID. +// cmdID : Application command ID. +// guildID : Guild ID to retrieve guild-specific application command. If empty - retrieves global application command. +func (s *Session) ApplicationCommand(appID, guildID, cmdID string) (cmd *ApplicationCommand, err error) { + endpoint := EndpointApplicationGlobalCommand(appID, cmdID) + if guildID != "" { + endpoint = EndpointApplicationGuildCommand(appID, guildID, cmdID) + } + + body, err := s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &cmd) + + return +} + +// ApplicationCommands retrieves all commands in application. +// appID : The application ID. +// guildID : Guild ID to retrieve all guild-specific application commands. If empty - retrieves global application commands. +func (s *Session) ApplicationCommands(appID, guildID string) (cmd []*ApplicationCommand, err error) { + endpoint := EndpointApplicationGlobalCommands(appID) + if guildID != "" { + endpoint = EndpointApplicationGuildCommands(appID, guildID) + } + + body, err := s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &cmd) + + return +} + +// GuildApplicationCommandsPermissions returns permissions for application commands in a guild. +// appID : The application ID +// guildID : Guild ID to retrieve application commands permissions for. +func (s *Session) GuildApplicationCommandsPermissions(appID, guildID string) (permissions []*GuildApplicationCommandPermissions, err error) { + endpoint := EndpointApplicationCommandsGuildPermissions(appID, guildID) + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &permissions) + return +} + +// ApplicationCommandPermissions returns all permissions of an application command +// appID : The Application ID +// guildID : The guild ID containing the application command +// cmdID : The command ID to retrieve the permissions of +func (s *Session) ApplicationCommandPermissions(appID, guildID, cmdID string) (permissions *GuildApplicationCommandPermissions, err error) { + endpoint := EndpointApplicationCommandPermissions(appID, guildID, cmdID) + + var body []byte + body, err = s.RequestWithBucketID("GET", endpoint, nil, endpoint) + if err != nil { + return + } + + err = unmarshal(body, &permissions) + return +} + +// ApplicationCommandPermissionsEdit edits the permissions of an application command +// appID : The Application ID +// guildID : The guild ID containing the application command +// cmdID : The command ID to edit the permissions of +// permissions : An object containing a list of permissions for the application command +func (s *Session) ApplicationCommandPermissionsEdit(appID, guildID, cmdID string, permissions *ApplicationCommandPermissionsList) (err error) { + endpoint := EndpointApplicationCommandPermissions(appID, guildID, cmdID) + + _, err = s.RequestWithBucketID("PUT", endpoint, permissions, endpoint) + return +} + +// ApplicationCommandPermissionsBatchEdit edits the permissions of a batch of commands +// appID : The Application ID +// guildID : The guild ID to batch edit commands of +// permissions : A list of permissions paired with a command ID, guild ID, and application ID per application command +func (s *Session) ApplicationCommandPermissionsBatchEdit(appID, guildID string, permissions []*GuildApplicationCommandPermissions) (err error) { + endpoint := EndpointApplicationCommandsGuildPermissions(appID, guildID) + + _, err = s.RequestWithBucketID("PUT", endpoint, permissions, endpoint) + return +} + +// InteractionRespond creates the response to an interaction. +// appID : The application ID. +// interaction : Interaction instance. +// resp : Response message data. +func (s *Session) InteractionRespond(interaction *Interaction, resp *InteractionResponse) (err error) { + endpoint := EndpointInteractionResponse(interaction.ID, interaction.Token) + + if resp.Data != nil && len(resp.Data.Files) > 0 { + contentType, body, err := MultipartBodyWithJSON(resp, resp.Data.Files) + if err != nil { + return err + } + + _, err = s.request("POST", endpoint, contentType, body, endpoint, 0) + } else { + _, err = s.RequestWithBucketID("POST", endpoint, *resp, endpoint) + } + return err +} + +// InteractionResponse gets the response to an interaction. +// appID : The application ID. +// interaction : Interaction instance. +func (s *Session) InteractionResponse(appID string, interaction *Interaction) (*Message, error) { + return s.WebhookMessage(appID, interaction.Token, "@original") +} + +// InteractionResponseEdit edits the response to an interaction. +// appID : The application ID. +// interaction : Interaction instance. +// newresp : Updated response message data. +func (s *Session) InteractionResponseEdit(appID string, interaction *Interaction, newresp *WebhookEdit) (*Message, error) { + return s.WebhookMessageEdit(appID, interaction.Token, "@original", newresp) +} + +// InteractionResponseDelete deletes the response to an interaction. +// appID : The application ID. +// interaction : Interaction instance. +func (s *Session) InteractionResponseDelete(appID string, interaction *Interaction) error { + endpoint := EndpointInteractionResponseActions(appID, interaction.Token) + + _, err := s.RequestWithBucketID("DELETE", endpoint, nil, endpoint) + + return err +} + +// FollowupMessageCreate creates the followup message for an interaction. +// appID : The application ID. +// interaction : Interaction instance. +// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise) +// data : Data of the message to send. +func (s *Session) FollowupMessageCreate(appID string, interaction *Interaction, wait bool, data *WebhookParams) (*Message, error) { + return s.WebhookExecute(appID, interaction.Token, wait, data) +} + +// FollowupMessageEdit edits a followup message of an interaction. +// appID : The application ID. +// interaction : Interaction instance. +// messageID : The followup message ID. +// data : Data to update the message +func (s *Session) FollowupMessageEdit(appID string, interaction *Interaction, messageID string, data *WebhookEdit) (*Message, error) { + return s.WebhookMessageEdit(appID, interaction.Token, messageID, data) +} + +// FollowupMessageDelete deletes a followup message of an interaction. +// appID : The application ID. +// interaction : Interaction instance. +// messageID : The followup message ID. +func (s *Session) FollowupMessageDelete(appID string, interaction *Interaction, messageID string) error { + return s.WebhookMessageDelete(appID, interaction.Token, messageID) +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to guilds scheduled events +// ------------------------------------------------------------------------------------------------ + +// GuildScheduledEvents returns an array of GuildScheduledEvent for a guild +// guildID : The ID of a Guild +// userCount : Whether to include the user count in the response +func (s *Session) GuildScheduledEvents(guildID string, userCount bool) (st []*GuildScheduledEvent, err error) { + uri := EndpointGuildScheduledEvents(guildID) + if userCount { + uri += "?with_user_count=true" + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildScheduledEvents(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildScheduledEvent returns a specific GuildScheduledEvent in a guild +// guildID : The ID of a Guild +// eventID : The ID of the event +// userCount : Whether to include the user count in the response +func (s *Session) GuildScheduledEvent(guildID, eventID string, userCount bool) (st *GuildScheduledEvent, err error) { + uri := EndpointGuildScheduledEvent(guildID, eventID) + if userCount { + uri += "?with_user_count=true" + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildScheduledEvent(guildID, eventID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildScheduledEventCreate creates a GuildScheduledEvent for a guild and returns it +// guildID : The ID of a Guild +// eventID : The ID of the event +func (s *Session) GuildScheduledEventCreate(guildID string, event *GuildScheduledEventParams) (st *GuildScheduledEvent, err error) { + body, err := s.RequestWithBucketID("POST", EndpointGuildScheduledEvents(guildID), event, EndpointGuildScheduledEvents(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildScheduledEventEdit updates a specific event for a guild and returns it. +// guildID : The ID of a Guild +// eventID : The ID of the event +func (s *Session) GuildScheduledEventEdit(guildID, eventID string, event *GuildScheduledEventParams) (st *GuildScheduledEvent, err error) { + body, err := s.RequestWithBucketID("PATCH", EndpointGuildScheduledEvent(guildID, eventID), event, EndpointGuildScheduledEvent(guildID, eventID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// GuildScheduledEventDelete deletes a specific GuildScheduledEvent in a guild +// guildID : The ID of a Guild +// eventID : The ID of the event +func (s *Session) GuildScheduledEventDelete(guildID, eventID string) (err error) { + _, err = s.RequestWithBucketID("DELETE", EndpointGuildScheduledEvent(guildID, eventID), nil, EndpointGuildScheduledEvent(guildID, eventID)) + return +} + +// GuildScheduledEventUsers returns an array of GuildScheduledEventUser for a particular event in a guild +// guildID : The ID of a Guild +// eventID : The ID of the event +// limit : The maximum number of users to return (Max 100) +// withMember : Whether to include the member object in the response +// beforeID : If is not empty all returned users entries will be before the given ID +// afterID : If is not empty all returned users entries will be after the given ID +func (s *Session) GuildScheduledEventUsers(guildID, eventID string, limit int, withMember bool, beforeID, afterID string) (st []*GuildScheduledEventUser, err error) { + uri := EndpointGuildScheduledEventUsers(guildID, eventID) + + queryParams := url.Values{} + if withMember { + queryParams.Set("with_member", "true") + } + if limit > 0 { + queryParams.Set("limit", strconv.Itoa(limit)) + } + if beforeID != "" { + queryParams.Set("before", beforeID) + } + if afterID != "" { + queryParams.Set("after", afterID) + } + + if len(queryParams) > 0 { + uri += "?" + queryParams.Encode() + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildScheduledEventUsers(guildID, eventID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} diff --git a/vendor/github.com/bwmarrin/discordgo/state.go b/vendor/github.com/bwmarrin/discordgo/state.go new file mode 100644 index 00000000..e75be895 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/state.go @@ -0,0 +1,1277 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains code related to state tracking. If enabled, state +// tracking will capture the initial READY packet and many other websocket +// events and maintain an in-memory state of of guilds, channels, users, and +// so forth. This information can be accessed through the Session.State struct. + +package discordgo + +import ( + "errors" + "sort" + "sync" +) + +// ErrNilState is returned when the state is nil. +var ErrNilState = errors.New("state not instantiated, please use discordgo.New() or assign Session.State") + +// ErrStateNotFound is returned when the state cache +// requested is not found +var ErrStateNotFound = errors.New("state cache not found") + +// ErrMessageIncompletePermissions is returned when the message +// requested for permissions does not contain enough data to +// generate the permissions. +var ErrMessageIncompletePermissions = errors.New("message incomplete, unable to determine permissions") + +// A State contains the current known state. +// As discord sends this in a READY blob, it seems reasonable to simply +// use that struct as the data store. +type State struct { + sync.RWMutex + Ready + + // MaxMessageCount represents how many messages per channel the state will store. + MaxMessageCount int + TrackChannels bool + TrackThreads bool + TrackEmojis bool + TrackMembers bool + TrackThreadMembers bool + TrackRoles bool + TrackVoice bool + TrackPresences bool + + guildMap map[string]*Guild + channelMap map[string]*Channel + memberMap map[string]map[string]*Member +} + +// NewState creates an empty state. +func NewState() *State { + return &State{ + Ready: Ready{ + PrivateChannels: []*Channel{}, + Guilds: []*Guild{}, + }, + TrackChannels: true, + TrackThreads: true, + TrackEmojis: true, + TrackMembers: true, + TrackThreadMembers: true, + TrackRoles: true, + TrackVoice: true, + TrackPresences: true, + guildMap: make(map[string]*Guild), + channelMap: make(map[string]*Channel), + memberMap: make(map[string]map[string]*Member), + } +} + +func (s *State) createMemberMap(guild *Guild) { + members := make(map[string]*Member) + for _, m := range guild.Members { + members[m.User.ID] = m + } + s.memberMap[guild.ID] = members +} + +// GuildAdd adds a guild to the current world state, or +// updates it if it already exists. +func (s *State) GuildAdd(guild *Guild) error { + if s == nil { + return ErrNilState + } + + s.Lock() + defer s.Unlock() + + // Update the channels to point to the right guild, adding them to the channelMap as we go + for _, c := range guild.Channels { + s.channelMap[c.ID] = c + } + + // Add all the threads to the state in case of thread sync list. + for _, t := range guild.Threads { + s.channelMap[t.ID] = t + } + + // If this guild contains a new member slice, we must regenerate the member map so the pointers stay valid + if guild.Members != nil { + s.createMemberMap(guild) + } else if _, ok := s.memberMap[guild.ID]; !ok { + // Even if we have no new member slice, we still initialize the member map for this guild if it doesn't exist + s.memberMap[guild.ID] = make(map[string]*Member) + } + + if g, ok := s.guildMap[guild.ID]; ok { + // We are about to replace `g` in the state with `guild`, but first we need to + // make sure we preserve any fields that the `guild` doesn't contain from `g`. + if guild.MemberCount == 0 { + guild.MemberCount = g.MemberCount + } + if guild.Roles == nil { + guild.Roles = g.Roles + } + if guild.Emojis == nil { + guild.Emojis = g.Emojis + } + if guild.Members == nil { + guild.Members = g.Members + } + if guild.Presences == nil { + guild.Presences = g.Presences + } + if guild.Channels == nil { + guild.Channels = g.Channels + } + if guild.Threads == nil { + guild.Threads = g.Threads + } + if guild.VoiceStates == nil { + guild.VoiceStates = g.VoiceStates + } + *g = *guild + return nil + } + + s.Guilds = append(s.Guilds, guild) + s.guildMap[guild.ID] = guild + + return nil +} + +// GuildRemove removes a guild from current world state. +func (s *State) GuildRemove(guild *Guild) error { + if s == nil { + return ErrNilState + } + + _, err := s.Guild(guild.ID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + delete(s.guildMap, guild.ID) + + for i, g := range s.Guilds { + if g.ID == guild.ID { + s.Guilds = append(s.Guilds[:i], s.Guilds[i+1:]...) + return nil + } + } + + return nil +} + +// Guild gets a guild by ID. +// Useful for querying if @me is in a guild: +// _, err := discordgo.Session.State.Guild(guildID) +// isInGuild := err == nil +func (s *State) Guild(guildID string) (*Guild, error) { + if s == nil { + return nil, ErrNilState + } + + s.RLock() + defer s.RUnlock() + + if g, ok := s.guildMap[guildID]; ok { + return g, nil + } + + return nil, ErrStateNotFound +} + +func (s *State) presenceAdd(guildID string, presence *Presence) error { + guild, ok := s.guildMap[guildID] + if !ok { + return ErrStateNotFound + } + + for i, p := range guild.Presences { + if p.User.ID == presence.User.ID { + //guild.Presences[i] = presence + + //Update status + guild.Presences[i].Activities = presence.Activities + if presence.Status != "" { + guild.Presences[i].Status = presence.Status + } + + //Update the optionally sent user information + //ID Is a mandatory field so you should not need to check if it is empty + guild.Presences[i].User.ID = presence.User.ID + + if presence.User.Avatar != "" { + guild.Presences[i].User.Avatar = presence.User.Avatar + } + if presence.User.Discriminator != "" { + guild.Presences[i].User.Discriminator = presence.User.Discriminator + } + if presence.User.Email != "" { + guild.Presences[i].User.Email = presence.User.Email + } + if presence.User.Token != "" { + guild.Presences[i].User.Token = presence.User.Token + } + if presence.User.Username != "" { + guild.Presences[i].User.Username = presence.User.Username + } + + return nil + } + } + + guild.Presences = append(guild.Presences, presence) + return nil +} + +// PresenceAdd adds a presence to the current world state, or +// updates it if it already exists. +func (s *State) PresenceAdd(guildID string, presence *Presence) error { + if s == nil { + return ErrNilState + } + + s.Lock() + defer s.Unlock() + + return s.presenceAdd(guildID, presence) +} + +// PresenceRemove removes a presence from the current world state. +func (s *State) PresenceRemove(guildID string, presence *Presence) error { + if s == nil { + return ErrNilState + } + + guild, err := s.Guild(guildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + for i, p := range guild.Presences { + if p.User.ID == presence.User.ID { + guild.Presences = append(guild.Presences[:i], guild.Presences[i+1:]...) + return nil + } + } + + return ErrStateNotFound +} + +// Presence gets a presence by ID from a guild. +func (s *State) Presence(guildID, userID string) (*Presence, error) { + if s == nil { + return nil, ErrNilState + } + + guild, err := s.Guild(guildID) + if err != nil { + return nil, err + } + + for _, p := range guild.Presences { + if p.User.ID == userID { + return p, nil + } + } + + return nil, ErrStateNotFound +} + +// TODO: Consider moving Guild state update methods onto *Guild. + +func (s *State) memberAdd(member *Member) error { + guild, ok := s.guildMap[member.GuildID] + if !ok { + return ErrStateNotFound + } + + members, ok := s.memberMap[member.GuildID] + if !ok { + return ErrStateNotFound + } + + m, ok := members[member.User.ID] + if !ok { + members[member.User.ID] = member + guild.Members = append(guild.Members, member) + } else { + // We are about to replace `m` in the state with `member`, but first we need to + // make sure we preserve any fields that the `member` doesn't contain from `m`. + if member.JoinedAt.IsZero() { + member.JoinedAt = m.JoinedAt + } + *m = *member + } + return nil +} + +// MemberAdd adds a member to the current world state, or +// updates it if it already exists. +func (s *State) MemberAdd(member *Member) error { + if s == nil { + return ErrNilState + } + + s.Lock() + defer s.Unlock() + + return s.memberAdd(member) +} + +// MemberRemove removes a member from current world state. +func (s *State) MemberRemove(member *Member) error { + if s == nil { + return ErrNilState + } + + guild, err := s.Guild(member.GuildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + members, ok := s.memberMap[member.GuildID] + if !ok { + return ErrStateNotFound + } + + _, ok = members[member.User.ID] + if !ok { + return ErrStateNotFound + } + delete(members, member.User.ID) + + for i, m := range guild.Members { + if m.User.ID == member.User.ID { + guild.Members = append(guild.Members[:i], guild.Members[i+1:]...) + return nil + } + } + + return ErrStateNotFound +} + +// Member gets a member by ID from a guild. +func (s *State) Member(guildID, userID string) (*Member, error) { + if s == nil { + return nil, ErrNilState + } + + s.RLock() + defer s.RUnlock() + + members, ok := s.memberMap[guildID] + if !ok { + return nil, ErrStateNotFound + } + + m, ok := members[userID] + if ok { + return m, nil + } + + return nil, ErrStateNotFound +} + +// RoleAdd adds a role to the current world state, or +// updates it if it already exists. +func (s *State) RoleAdd(guildID string, role *Role) error { + if s == nil { + return ErrNilState + } + + guild, err := s.Guild(guildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + for i, r := range guild.Roles { + if r.ID == role.ID { + guild.Roles[i] = role + return nil + } + } + + guild.Roles = append(guild.Roles, role) + return nil +} + +// RoleRemove removes a role from current world state by ID. +func (s *State) RoleRemove(guildID, roleID string) error { + if s == nil { + return ErrNilState + } + + guild, err := s.Guild(guildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + for i, r := range guild.Roles { + if r.ID == roleID { + guild.Roles = append(guild.Roles[:i], guild.Roles[i+1:]...) + return nil + } + } + + return ErrStateNotFound +} + +// Role gets a role by ID from a guild. +func (s *State) Role(guildID, roleID string) (*Role, error) { + if s == nil { + return nil, ErrNilState + } + + guild, err := s.Guild(guildID) + if err != nil { + return nil, err + } + + s.RLock() + defer s.RUnlock() + + for _, r := range guild.Roles { + if r.ID == roleID { + return r, nil + } + } + + return nil, ErrStateNotFound +} + +// ChannelAdd adds a channel to the current world state, or +// updates it if it already exists. +// Channels may exist either as PrivateChannels or inside +// a guild. +func (s *State) ChannelAdd(channel *Channel) error { + if s == nil { + return ErrNilState + } + + s.Lock() + defer s.Unlock() + + // If the channel exists, replace it + if c, ok := s.channelMap[channel.ID]; ok { + if channel.Messages == nil { + channel.Messages = c.Messages + } + if channel.PermissionOverwrites == nil { + channel.PermissionOverwrites = c.PermissionOverwrites + } + if channel.ThreadMetadata == nil { + channel.ThreadMetadata = c.ThreadMetadata + } + + *c = *channel + return nil + } + + if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM { + s.PrivateChannels = append(s.PrivateChannels, channel) + s.channelMap[channel.ID] = channel + return nil + } + + guild, ok := s.guildMap[channel.GuildID] + if !ok { + return ErrStateNotFound + } + + if channel.IsThread() { + guild.Threads = append(guild.Threads, channel) + } else { + guild.Channels = append(guild.Channels, channel) + } + + s.channelMap[channel.ID] = channel + + return nil +} + +// ChannelRemove removes a channel from current world state. +func (s *State) ChannelRemove(channel *Channel) error { + if s == nil { + return ErrNilState + } + + _, err := s.Channel(channel.ID) + if err != nil { + return err + } + + if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM { + s.Lock() + defer s.Unlock() + + for i, c := range s.PrivateChannels { + if c.ID == channel.ID { + s.PrivateChannels = append(s.PrivateChannels[:i], s.PrivateChannels[i+1:]...) + break + } + } + delete(s.channelMap, channel.ID) + return nil + } + + guild, err := s.Guild(channel.GuildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + if channel.IsThread() { + for i, t := range guild.Threads { + if t.ID == channel.ID { + guild.Threads = append(guild.Threads[:i], guild.Threads[i+1:]...) + break + } + } + } else { + for i, c := range guild.Channels { + if c.ID == channel.ID { + guild.Channels = append(guild.Channels[:i], guild.Channels[i+1:]...) + break + } + } + } + + delete(s.channelMap, channel.ID) + + return nil +} + +// ThreadListSync syncs guild threads with provided ones. +func (s *State) ThreadListSync(tls *ThreadListSync) error { + guild, err := s.Guild(tls.GuildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + // This algorithm filters out archived or + // threads which are children of channels in channelIDs + // and then it adds all synced threads to guild threads and cache + index := 0 +outer: + for _, t := range guild.Threads { + if !t.ThreadMetadata.Archived && tls.ChannelIDs != nil { + for _, v := range tls.ChannelIDs { + if t.ParentID == v { + delete(s.channelMap, t.ID) + continue outer + } + } + guild.Threads[index] = t + index++ + } else { + delete(s.channelMap, t.ID) + } + } + guild.Threads = guild.Threads[:index] + for _, t := range tls.Threads { + s.channelMap[t.ID] = t + guild.Threads = append(guild.Threads, t) + } + + for _, m := range tls.Members { + if c, ok := s.channelMap[m.ID]; ok { + c.Member = m + } + } + + return nil +} + +// ThreadMembersUpdate updates thread members list +func (s *State) ThreadMembersUpdate(tmu *ThreadMembersUpdate) error { + thread, err := s.Channel(tmu.ID) + if err != nil { + return err + } + s.Lock() + defer s.Unlock() + + for idx, member := range thread.Members { + for _, removedMember := range tmu.RemovedMembers { + if member.ID == removedMember { + thread.Members = append(thread.Members[:idx], thread.Members[idx+1:]...) + break + } + } + } + + for _, addedMember := range tmu.AddedMembers { + thread.Members = append(thread.Members, addedMember.ThreadMember) + if addedMember.Member != nil { + err = s.memberAdd(addedMember.Member) + if err != nil { + return err + } + } + if addedMember.Presence != nil { + err = s.presenceAdd(tmu.GuildID, addedMember.Presence) + if err != nil { + return err + } + } + } + thread.MemberCount = tmu.MemberCount + + return nil +} + +// ThreadMemberUpdate sets or updates member data for the current user. +func (s *State) ThreadMemberUpdate(mu *ThreadMemberUpdate) error { + thread, err := s.Channel(mu.ID) + if err != nil { + return err + } + + thread.Member = mu.ThreadMember + return nil +} + +// GuildChannel gets a channel by ID from a guild. +// This method is Deprecated, use Channel(channelID) +func (s *State) GuildChannel(guildID, channelID string) (*Channel, error) { + return s.Channel(channelID) +} + +// PrivateChannel gets a private channel by ID. +// This method is Deprecated, use Channel(channelID) +func (s *State) PrivateChannel(channelID string) (*Channel, error) { + return s.Channel(channelID) +} + +// Channel gets a channel by ID, it will look in all guilds and private channels. +func (s *State) Channel(channelID string) (*Channel, error) { + if s == nil { + return nil, ErrNilState + } + + s.RLock() + defer s.RUnlock() + + if c, ok := s.channelMap[channelID]; ok { + return c, nil + } + + return nil, ErrStateNotFound +} + +// Emoji returns an emoji for a guild and emoji id. +func (s *State) Emoji(guildID, emojiID string) (*Emoji, error) { + if s == nil { + return nil, ErrNilState + } + + guild, err := s.Guild(guildID) + if err != nil { + return nil, err + } + + s.RLock() + defer s.RUnlock() + + for _, e := range guild.Emojis { + if e.ID == emojiID { + return e, nil + } + } + + return nil, ErrStateNotFound +} + +// EmojiAdd adds an emoji to the current world state. +func (s *State) EmojiAdd(guildID string, emoji *Emoji) error { + if s == nil { + return ErrNilState + } + + guild, err := s.Guild(guildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + for i, e := range guild.Emojis { + if e.ID == emoji.ID { + guild.Emojis[i] = emoji + return nil + } + } + + guild.Emojis = append(guild.Emojis, emoji) + return nil +} + +// EmojisAdd adds multiple emojis to the world state. +func (s *State) EmojisAdd(guildID string, emojis []*Emoji) error { + for _, e := range emojis { + if err := s.EmojiAdd(guildID, e); err != nil { + return err + } + } + return nil +} + +// MessageAdd adds a message to the current world state, or updates it if it exists. +// If the channel cannot be found, the message is discarded. +// Messages are kept in state up to s.MaxMessageCount per channel. +func (s *State) MessageAdd(message *Message) error { + if s == nil { + return ErrNilState + } + + c, err := s.Channel(message.ChannelID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + // If the message exists, merge in the new message contents. + for _, m := range c.Messages { + if m.ID == message.ID { + if message.Content != "" { + m.Content = message.Content + } + if message.EditedTimestamp != nil { + m.EditedTimestamp = message.EditedTimestamp + } + if message.Mentions != nil { + m.Mentions = message.Mentions + } + if message.Embeds != nil { + m.Embeds = message.Embeds + } + if message.Attachments != nil { + m.Attachments = message.Attachments + } + if !message.Timestamp.IsZero() { + m.Timestamp = message.Timestamp + } + if message.Author != nil { + m.Author = message.Author + } + if message.Components != nil { + m.Components = message.Components + } + + return nil + } + } + + c.Messages = append(c.Messages, message) + + if len(c.Messages) > s.MaxMessageCount { + c.Messages = c.Messages[len(c.Messages)-s.MaxMessageCount:] + } + + return nil +} + +// MessageRemove removes a message from the world state. +func (s *State) MessageRemove(message *Message) error { + if s == nil { + return ErrNilState + } + + return s.messageRemoveByID(message.ChannelID, message.ID) +} + +// messageRemoveByID removes a message by channelID and messageID from the world state. +func (s *State) messageRemoveByID(channelID, messageID string) error { + c, err := s.Channel(channelID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + for i, m := range c.Messages { + if m.ID == messageID { + c.Messages = append(c.Messages[:i], c.Messages[i+1:]...) + + return nil + } + } + + return ErrStateNotFound +} + +func (s *State) voiceStateUpdate(update *VoiceStateUpdate) error { + guild, err := s.Guild(update.GuildID) + if err != nil { + return err + } + + s.Lock() + defer s.Unlock() + + // Handle Leaving Channel + if update.ChannelID == "" { + for i, state := range guild.VoiceStates { + if state.UserID == update.UserID { + guild.VoiceStates = append(guild.VoiceStates[:i], guild.VoiceStates[i+1:]...) + return nil + } + } + } else { + for i, state := range guild.VoiceStates { + if state.UserID == update.UserID { + guild.VoiceStates[i] = update.VoiceState + return nil + } + } + + guild.VoiceStates = append(guild.VoiceStates, update.VoiceState) + } + + return nil +} + +// VoiceState gets a VoiceState by guild and user ID. +func (s *State) VoiceState(guildID, userID string) (*VoiceState, error) { + if s == nil { + return nil, ErrNilState + } + + guild, err := s.Guild(guildID) + if err != nil { + return nil, err + } + + for _, state := range guild.VoiceStates { + if state.UserID == userID { + return state, nil + } + } + + return nil, ErrStateNotFound +} + +// Message gets a message by channel and message ID. +func (s *State) Message(channelID, messageID string) (*Message, error) { + if s == nil { + return nil, ErrNilState + } + + c, err := s.Channel(channelID) + if err != nil { + return nil, err + } + + s.RLock() + defer s.RUnlock() + + for _, m := range c.Messages { + if m.ID == messageID { + return m, nil + } + } + + return nil, ErrStateNotFound +} + +// OnReady takes a Ready event and updates all internal state. +func (s *State) onReady(se *Session, r *Ready) (err error) { + if s == nil { + return ErrNilState + } + + s.Lock() + defer s.Unlock() + + // We must track at least the current user for Voice, even + // if state is disabled, store the bare essentials. + if !se.StateEnabled { + ready := Ready{ + Version: r.Version, + SessionID: r.SessionID, + User: r.User, + } + + s.Ready = ready + + return nil + } + + s.Ready = *r + + for _, g := range s.Guilds { + s.guildMap[g.ID] = g + s.createMemberMap(g) + + for _, c := range g.Channels { + s.channelMap[c.ID] = c + } + } + + for _, c := range s.PrivateChannels { + s.channelMap[c.ID] = c + } + + return nil +} + +// OnInterface handles all events related to states. +func (s *State) OnInterface(se *Session, i interface{}) (err error) { + if s == nil { + return ErrNilState + } + + r, ok := i.(*Ready) + if ok { + return s.onReady(se, r) + } + + if !se.StateEnabled { + return nil + } + + switch t := i.(type) { + case *GuildCreate: + err = s.GuildAdd(t.Guild) + case *GuildUpdate: + err = s.GuildAdd(t.Guild) + case *GuildDelete: + var old *Guild + old, err = s.Guild(t.ID) + if err == nil { + oldCopy := *old + t.BeforeDelete = &oldCopy + } + + err = s.GuildRemove(t.Guild) + case *GuildMemberAdd: + // Updates the MemberCount of the guild. + guild, err := s.Guild(t.Member.GuildID) + if err != nil { + return err + } + guild.MemberCount++ + + // Caches member if tracking is enabled. + if s.TrackMembers { + err = s.MemberAdd(t.Member) + } + case *GuildMemberUpdate: + if s.TrackMembers { + err = s.MemberAdd(t.Member) + } + case *GuildMemberRemove: + // Updates the MemberCount of the guild. + guild, err := s.Guild(t.Member.GuildID) + if err != nil { + return err + } + guild.MemberCount-- + + // Removes member from the cache if tracking is enabled. + if s.TrackMembers { + err = s.MemberRemove(t.Member) + } + case *GuildMembersChunk: + if s.TrackMembers { + for i := range t.Members { + t.Members[i].GuildID = t.GuildID + err = s.MemberAdd(t.Members[i]) + } + } + + if s.TrackPresences { + for _, p := range t.Presences { + err = s.PresenceAdd(t.GuildID, p) + } + } + case *GuildRoleCreate: + if s.TrackRoles { + err = s.RoleAdd(t.GuildID, t.Role) + } + case *GuildRoleUpdate: + if s.TrackRoles { + err = s.RoleAdd(t.GuildID, t.Role) + } + case *GuildRoleDelete: + if s.TrackRoles { + err = s.RoleRemove(t.GuildID, t.RoleID) + } + case *GuildEmojisUpdate: + if s.TrackEmojis { + err = s.EmojisAdd(t.GuildID, t.Emojis) + } + case *ChannelCreate: + if s.TrackChannels { + err = s.ChannelAdd(t.Channel) + } + case *ChannelUpdate: + if s.TrackChannels { + err = s.ChannelAdd(t.Channel) + } + case *ChannelDelete: + if s.TrackChannels { + err = s.ChannelRemove(t.Channel) + } + case *ThreadCreate: + if s.TrackThreads { + err = s.ChannelAdd(t.Channel) + } + case *ThreadUpdate: + if s.TrackThreads { + old, err := s.Channel(t.ID) + if err == nil { + oldCopy := *old + t.BeforeUpdate = &oldCopy + } + err = s.ChannelAdd(t.Channel) + } + case *ThreadDelete: + if s.TrackThreads { + err = s.ChannelRemove(t.Channel) + } + case *ThreadMemberUpdate: + if s.TrackThreads { + err = s.ThreadMemberUpdate(t) + } + case *ThreadMembersUpdate: + if s.TrackThreadMembers { + err = s.ThreadMembersUpdate(t) + } + case *ThreadListSync: + if s.TrackThreads { + err = s.ThreadListSync(t) + } + case *MessageCreate: + if s.MaxMessageCount != 0 { + err = s.MessageAdd(t.Message) + } + case *MessageUpdate: + if s.MaxMessageCount != 0 { + var old *Message + old, err = s.Message(t.ChannelID, t.ID) + if err == nil { + oldCopy := *old + t.BeforeUpdate = &oldCopy + } + + err = s.MessageAdd(t.Message) + } + case *MessageDelete: + if s.MaxMessageCount != 0 { + var old *Message + old, err = s.Message(t.ChannelID, t.ID) + if err == nil { + oldCopy := *old + t.BeforeDelete = &oldCopy + } + + err = s.MessageRemove(t.Message) + } + case *MessageDeleteBulk: + if s.MaxMessageCount != 0 { + for _, mID := range t.Messages { + s.messageRemoveByID(t.ChannelID, mID) + } + } + case *VoiceStateUpdate: + if s.TrackVoice { + var old *VoiceState + old, err = s.VoiceState(t.GuildID, t.UserID) + if err == nil { + oldCopy := *old + t.BeforeUpdate = &oldCopy + } + + err = s.voiceStateUpdate(t) + } + case *PresenceUpdate: + if s.TrackPresences { + s.PresenceAdd(t.GuildID, &t.Presence) + } + if s.TrackMembers { + if t.Status == StatusOffline { + return + } + + var m *Member + m, err = s.Member(t.GuildID, t.User.ID) + + if err != nil { + // Member not found; this is a user coming online + m = &Member{ + GuildID: t.GuildID, + User: t.User, + } + } else { + if t.User.Username != "" { + m.User.Username = t.User.Username + } + } + + err = s.MemberAdd(m) + } + + } + + return +} + +// UserChannelPermissions returns the permission of a user in a channel. +// userID : The ID of the user to calculate permissions for. +// channelID : The ID of the channel to calculate permission for. +func (s *State) UserChannelPermissions(userID, channelID string) (apermissions int64, err error) { + if s == nil { + return 0, ErrNilState + } + + channel, err := s.Channel(channelID) + if err != nil { + return + } + + guild, err := s.Guild(channel.GuildID) + if err != nil { + return + } + + member, err := s.Member(guild.ID, userID) + if err != nil { + return + } + + return memberPermissions(guild, channel, userID, member.Roles), nil +} + +// MessagePermissions returns the permissions of the author of the message +// in the channel in which it was sent. +func (s *State) MessagePermissions(message *Message) (apermissions int64, err error) { + if s == nil { + return 0, ErrNilState + } + + if message.Author == nil || message.Member == nil { + return 0, ErrMessageIncompletePermissions + } + + channel, err := s.Channel(message.ChannelID) + if err != nil { + return + } + + guild, err := s.Guild(channel.GuildID) + if err != nil { + return + } + + return memberPermissions(guild, channel, message.Author.ID, message.Member.Roles), nil +} + +// UserColor returns the color of a user in a channel. +// While colors are defined at a Guild level, determining for a channel is more useful in message handlers. +// 0 is returned in cases of error, which is the color of @everyone. +// userID : The ID of the user to calculate the color for. +// channelID : The ID of the channel to calculate the color for. +func (s *State) UserColor(userID, channelID string) int { + if s == nil { + return 0 + } + + channel, err := s.Channel(channelID) + if err != nil { + return 0 + } + + guild, err := s.Guild(channel.GuildID) + if err != nil { + return 0 + } + + member, err := s.Member(guild.ID, userID) + if err != nil { + return 0 + } + + return firstRoleColorColor(guild, member.Roles) +} + +// MessageColor returns the color of the author's name as displayed +// in the client associated with this message. +func (s *State) MessageColor(message *Message) int { + if s == nil { + return 0 + } + + if message.Member == nil || message.Member.Roles == nil { + return 0 + } + + channel, err := s.Channel(message.ChannelID) + if err != nil { + return 0 + } + + guild, err := s.Guild(channel.GuildID) + if err != nil { + return 0 + } + + return firstRoleColorColor(guild, message.Member.Roles) +} + +func firstRoleColorColor(guild *Guild, memberRoles []string) int { + roles := Roles(guild.Roles) + sort.Sort(roles) + + for _, role := range roles { + for _, roleID := range memberRoles { + if role.ID == roleID { + if role.Color != 0 { + return role.Color + } + } + } + } + + for _, role := range roles { + if role.ID == guild.ID { + return role.Color + } + } + + return 0 +} diff --git a/vendor/github.com/bwmarrin/discordgo/structs.go b/vendor/github.com/bwmarrin/discordgo/structs.go new file mode 100644 index 00000000..3a92c9fd --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/structs.go @@ -0,0 +1,2038 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains all structures for the discordgo package. These +// may be moved about later into separate files but I find it easier to have +// them all located together. + +package discordgo + +import ( + "encoding/json" + "fmt" + "math" + "net/http" + "regexp" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" +) + +// A Session represents a connection to the Discord API. +type Session struct { + sync.RWMutex + + // General configurable settings. + + // Authentication token for this session + // TODO: Remove Below, Deprecated, Use Identify struct + Token string + + MFA bool + + // Debug for printing JSON request/responses + Debug bool // Deprecated, will be removed. + LogLevel int + + // Should the session reconnect the websocket on errors. + ShouldReconnectOnError bool + + // Identify is sent during initial handshake with the discord gateway. + // https://discord.com/developers/docs/topics/gateway#identify + Identify Identify + + // TODO: Remove Below, Deprecated, Use Identify struct + // Should the session request compressed websocket data. + Compress bool + + // Sharding + ShardID int + ShardCount int + + // Should state tracking be enabled. + // State tracking is the best way for getting the the users + // active guilds and the members of the guilds. + StateEnabled bool + + // Whether or not to call event handlers synchronously. + // e.g false = launch event handlers in their own goroutines. + SyncEvents bool + + // Exposed but should not be modified by User. + + // Whether the Data Websocket is ready + DataReady bool // NOTE: Maye be deprecated soon + + // Max number of REST API retries + MaxRestRetries int + + // Status stores the currect status of the websocket connection + // this is being tested, may stay, may go away. + status int32 + + // Whether the Voice Websocket is ready + VoiceReady bool // NOTE: Deprecated. + + // Whether the UDP Connection is ready + UDPReady bool // NOTE: Deprecated + + // Stores a mapping of guild id's to VoiceConnections + VoiceConnections map[string]*VoiceConnection + + // Managed state object, updated internally with events when + // StateEnabled is true. + State *State + + // The http client used for REST requests + Client *http.Client + + // The user agent used for REST APIs + UserAgent string + + // Stores the last HeartbeatAck that was received (in UTC) + LastHeartbeatAck time.Time + + // Stores the last Heartbeat sent (in UTC) + LastHeartbeatSent time.Time + + // used to deal with rate limits + Ratelimiter *RateLimiter + + // Event handlers + handlersMu sync.RWMutex + handlers map[string][]*eventHandlerInstance + onceHandlers map[string][]*eventHandlerInstance + + // The websocket connection. + wsConn *websocket.Conn + + // When nil, the session is not listening. + listening chan interface{} + + // sequence tracks the current gateway api websocket sequence number + sequence *int64 + + // stores sessions current Discord Gateway + gateway string + + // stores session ID of current Gateway connection + sessionID string + + // used to make sure gateway websocket writes do not happen concurrently + wsMutex sync.Mutex +} + +// Application stores values for a Discord Application +type Application struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + Icon string `json:"icon,omitempty"` + Description string `json:"description,omitempty"` + RPCOrigins []string `json:"rpc_origins,omitempty"` + BotPublic bool `json:"bot_public,omitempty"` + BotRequireCodeGrant bool `json:"bot_require_code_grant,omitempty"` + TermsOfServiceURL string `json:"terms_of_service_url"` + PrivacyProxyURL string `json:"privacy_policy_url"` + Owner *User `json:"owner"` + Summary string `json:"summary"` + VerifyKey string `json:"verify_key"` + Team *Team `json:"team"` + GuildID string `json:"guild_id"` + PrimarySKUID string `json:"primary_sku_id"` + Slug string `json:"slug"` + CoverImage string `json:"cover_image"` + Flags int `json:"flags,omitempty"` +} + +// UserConnection is a Connection returned from the UserConnections endpoint +type UserConnection struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Revoked bool `json:"revoked"` + Integrations []*Integration `json:"integrations"` +} + +// Integration stores integration information +type Integration struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Enabled bool `json:"enabled"` + Syncing bool `json:"syncing"` + RoleID string `json:"role_id"` + EnableEmoticons bool `json:"enable_emoticons"` + ExpireBehavior ExpireBehavior `json:"expire_behavior"` + ExpireGracePeriod int `json:"expire_grace_period"` + User *User `json:"user"` + Account IntegrationAccount `json:"account"` + SyncedAt time.Time `json:"synced_at"` +} + +// ExpireBehavior of Integration +// https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors +type ExpireBehavior int + +// Block of valid ExpireBehaviors +const ( + ExpireBehaviorRemoveRole ExpireBehavior = 0 + ExpireBehaviorKick ExpireBehavior = 1 +) + +// IntegrationAccount is integration account information +// sent by the UserConnections endpoint +type IntegrationAccount struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// A VoiceRegion stores data for a specific voice region server. +type VoiceRegion struct { + ID string `json:"id"` + Name string `json:"name"` + Hostname string `json:"sample_hostname"` + Port int `json:"sample_port"` +} + +// A VoiceICE stores data for voice ICE servers. +type VoiceICE struct { + TTL string `json:"ttl"` + Servers []*ICEServer `json:"servers"` +} + +// A ICEServer stores data for a specific voice ICE server. +type ICEServer struct { + URL string `json:"url"` + Username string `json:"username"` + Credential string `json:"credential"` +} + +// InviteTargetType indicates the type of target of an invite +// https://discord.com/developers/docs/resources/invite#invite-object-invite-target-types +type InviteTargetType uint8 + +// Invite target types +const ( + InviteTargetStream InviteTargetType = 1 + InviteTargetEmbeddedAppliction InviteTargetType = 2 +) + +// A Invite stores all data related to a specific Discord Guild or Channel invite. +type Invite struct { + Guild *Guild `json:"guild"` + Channel *Channel `json:"channel"` + Inviter *User `json:"inviter"` + Code string `json:"code"` + CreatedAt time.Time `json:"created_at"` + MaxAge int `json:"max_age"` + Uses int `json:"uses"` + MaxUses int `json:"max_uses"` + Revoked bool `json:"revoked"` + Temporary bool `json:"temporary"` + Unique bool `json:"unique"` + TargetUser *User `json:"target_user"` + TargetType InviteTargetType `json:"target_type"` + TargetApplication *Application `json:"target_application"` + + // will only be filled when using InviteWithCounts + ApproximatePresenceCount int `json:"approximate_presence_count"` + ApproximateMemberCount int `json:"approximate_member_count"` +} + +// ChannelType is the type of a Channel +type ChannelType int + +// Block contains known ChannelType values +const ( + ChannelTypeGuildText ChannelType = 0 + ChannelTypeDM ChannelType = 1 + ChannelTypeGuildVoice ChannelType = 2 + ChannelTypeGroupDM ChannelType = 3 + ChannelTypeGuildCategory ChannelType = 4 + ChannelTypeGuildNews ChannelType = 5 + ChannelTypeGuildStore ChannelType = 6 + ChannelTypeGuildNewsThread ChannelType = 10 + ChannelTypeGuildPublicThread ChannelType = 11 + ChannelTypeGuildPrivateThread ChannelType = 12 +) + +// A Channel holds all data related to an individual Discord channel. +type Channel struct { + // The ID of the channel. + ID string `json:"id"` + + // The ID of the guild to which the channel belongs, if it is in a guild. + // Else, this ID is empty (e.g. DM channels). + GuildID string `json:"guild_id"` + + // The name of the channel. + Name string `json:"name"` + + // The topic of the channel. + Topic string `json:"topic"` + + // The type of the channel. + Type ChannelType `json:"type"` + + // The ID of the last message sent in the channel. This is not + // guaranteed to be an ID of a valid message. + LastMessageID string `json:"last_message_id"` + + // The timestamp of the last pinned message in the channel. + // nil if the channel has no pinned messages. + LastPinTimestamp *time.Time `json:"last_pin_timestamp"` + + // An approximate count of messages in a thread, stops counting at 50 + MessageCount int `json:"message_count"` + // An approximate count of users in a thread, stops counting at 50 + MemberCount int `json:"member_count"` + + // Whether the channel is marked as NSFW. + NSFW bool `json:"nsfw"` + + // Icon of the group DM channel. + Icon string `json:"icon"` + + // The position of the channel, used for sorting in client. + Position int `json:"position"` + + // The bitrate of the channel, if it is a voice channel. + Bitrate int `json:"bitrate"` + + // The recipients of the channel. This is only populated in DM channels. + Recipients []*User `json:"recipients"` + + // The messages in the channel. This is only present in state-cached channels, + // and State.MaxMessageCount must be non-zero. + Messages []*Message `json:"-"` + + // A list of permission overwrites present for the channel. + PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"` + + // The user limit of the voice channel. + UserLimit int `json:"user_limit"` + + // The ID of the parent channel, if the channel is under a category. For threads - id of the channel thread was created in. + ParentID string `json:"parent_id"` + + // Amount of seconds a user has to wait before sending another message or creating another thread (0-21600) + // bots, as well as users with the permission manage_messages or manage_channel, are unaffected + RateLimitPerUser int `json:"rate_limit_per_user"` + + // ID of the creator of the group DM or thread + OwnerID string `json:"owner_id"` + + // ApplicationID of the DM creator Zeroed if guild channel or not a bot user + ApplicationID string `json:"application_id"` + + // Thread-specific fields not needed by other channels + ThreadMetadata *ThreadMetadata `json:"thread_metadata,omitempty"` + // Thread member object for the current user, if they have joined the thread, only included on certain API endpoints + Member *ThreadMember `json:"thread_member"` + + // All thread members. State channels only. + Members []*ThreadMember `json:"-"` +} + +// Mention returns a string which mentions the channel +func (c *Channel) Mention() string { + return fmt.Sprintf("<#%s>", c.ID) +} + +// IsThread is a helper function to determine if channel is a thread or not +func (c *Channel) IsThread() bool { + return c.Type == ChannelTypeGuildPublicThread || c.Type == ChannelTypeGuildPrivateThread || c.Type == ChannelTypeGuildNewsThread +} + +// A ChannelEdit holds Channel Field data for a channel edit. +type ChannelEdit struct { + Name string `json:"name,omitempty"` + Topic string `json:"topic,omitempty"` + NSFW bool `json:"nsfw,omitempty"` + Position int `json:"position"` + Bitrate int `json:"bitrate,omitempty"` + UserLimit int `json:"user_limit,omitempty"` + PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` + ParentID string `json:"parent_id,omitempty"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` + + // NOTE: threads only + + Archived bool `json:"archived,omitempty"` + AutoArchiveDuration int `json:"auto_archive_duration,omitempty"` + Locked bool `json:"locked,bool"` + Invitable bool `json:"invitable,omitempty"` +} + +// A ChannelFollow holds data returned after following a news channel +type ChannelFollow struct { + ChannelID string `json:"channel_id"` + WebhookID string `json:"webhook_id"` +} + +// PermissionOverwriteType represents the type of resource on which +// a permission overwrite acts. +type PermissionOverwriteType int + +// The possible permission overwrite types. +const ( + PermissionOverwriteTypeRole PermissionOverwriteType = 0 + PermissionOverwriteTypeMember PermissionOverwriteType = 1 +) + +// A PermissionOverwrite holds permission overwrite data for a Channel +type PermissionOverwrite struct { + ID string `json:"id"` + Type PermissionOverwriteType `json:"type"` + Deny int64 `json:"deny,string"` + Allow int64 `json:"allow,string"` +} + +// ThreadStart stores all parameters you can use with MessageThreadStartComplex or ThreadStartComplex +type ThreadStart struct { + Name string `json:"name"` + AutoArchiveDuration int `json:"auto_archive_duration,omitempty"` + Type ChannelType `json:"type,omitempty"` + Invitable bool `json:"invitable"` + RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` +} + +// ThreadMetadata contains a number of thread-specific channel fields that are not needed by other channel types. +type ThreadMetadata struct { + // Whether the thread is archived + Archived bool `json:"archived"` + // Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 + AutoArchiveDuration int `json:"auto_archive_duration"` + // Timestamp when the thread's archive status was last changed, used for calculating recent activity + ArchiveTimestamp time.Time `json:"archive_timestamp"` + // Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it + Locked bool `json:"locked"` + // Whether non-moderators can add other non-moderators to a thread; only available on private threads + Invitable bool `json:"invitable"` +} + +// ThreadMember is used to indicate whether a user has joined a thread or not. +// NOTE: ID and UserID are empty (omitted) on the member sent within each thread in the GUILD_CREATE event. +type ThreadMember struct { + // The id of the thread + ID string `json:"id,omitempty"` + // The id of the user + UserID string `json:"user_id,omitempty"` + // The time the current user last joined the thread + JoinTimestamp time.Time `json:"join_timestamp"` + // Any user-thread settings, currently only used for notifications + Flags int +} + +// ThreadsList represents a list of threads alongisde with thread member objects for the current user. +type ThreadsList struct { + Threads []*Channel `json:"threads"` + Members []*ThreadMember `json:"members"` + HasMore bool `json:"has_more"` +} + +// AddedThreadMember holds information about the user who was added to the thread +type AddedThreadMember struct { + *ThreadMember + Member *Member `json:"member"` + Presence *Presence `json:"presence"` +} + +// Emoji struct holds data related to Emoji's +type Emoji struct { + ID string `json:"id"` + Name string `json:"name"` + Roles []string `json:"roles"` + User *User `json:"user"` + RequireColons bool `json:"require_colons"` + Managed bool `json:"managed"` + Animated bool `json:"animated"` + Available bool `json:"available"` +} + +// EmojiRegex is the regex used to find and identify emojis in messages +var ( + EmojiRegex = regexp.MustCompile(`<(a|):[A-z0-9_~]+:[0-9]{18}>`) +) + +// MessageFormat returns a correctly formatted Emoji for use in Message content and embeds +func (e *Emoji) MessageFormat() string { + if e.ID != "" && e.Name != "" { + if e.Animated { + return "" + } + + return "<:" + e.APIName() + ">" + } + + return e.APIName() +} + +// APIName returns an correctly formatted API name for use in the MessageReactions endpoints. +func (e *Emoji) APIName() string { + if e.ID != "" && e.Name != "" { + return e.Name + ":" + e.ID + } + if e.Name != "" { + return e.Name + } + return e.ID +} + +// StickerFormat is the file format of the Sticker. +type StickerFormat int + +// Defines all known Sticker types. +const ( + StickerFormatTypePNG StickerFormat = 1 + StickerFormatTypeAPNG StickerFormat = 2 + StickerFormatTypeLottie StickerFormat = 3 +) + +// StickerType is the type of sticker. +type StickerType int + +// Defines Sticker types. +const ( + StickerTypeStandard StickerType = 1 + StickerTypeGuild StickerType = 2 +) + +// Sticker represents a sticker object that can be sent in a Message. +type Sticker struct { + ID string `json:"id"` + PackID string `json:"pack_id"` + Name string `json:"name"` + Description string `json:"description"` + Tags string `json:"tags"` + Type StickerType `json:"type"` + FormatType StickerFormat `json:"format_type"` + Available bool `json:"available"` + GuildID string `json:"guild_id"` + User *User `json:"user"` + SortValue int `json:"sort_value"` +} + +// StickerPack represents a pack of standard stickers. +type StickerPack struct { + ID string `json:"id"` + Stickers []*Sticker `json:"stickers"` + Name string `json:"name"` + SKUID string `json:"sku_id"` + CoverStickerID string `json:"cover_sticker_id"` + Description string `json:"description"` + BannerAssetID string `json:"banner_asset_id"` +} + +// VerificationLevel type definition +type VerificationLevel int + +// Constants for VerificationLevel levels from 0 to 4 inclusive +const ( + VerificationLevelNone VerificationLevel = 0 + VerificationLevelLow VerificationLevel = 1 + VerificationLevelMedium VerificationLevel = 2 + VerificationLevelHigh VerificationLevel = 3 + VerificationLevelVeryHigh VerificationLevel = 4 +) + +// ExplicitContentFilterLevel type definition +type ExplicitContentFilterLevel int + +// Constants for ExplicitContentFilterLevel levels from 0 to 2 inclusive +const ( + ExplicitContentFilterDisabled ExplicitContentFilterLevel = 0 + ExplicitContentFilterMembersWithoutRoles ExplicitContentFilterLevel = 1 + ExplicitContentFilterAllMembers ExplicitContentFilterLevel = 2 +) + +// MfaLevel type definition +type MfaLevel int + +// Constants for MfaLevel levels from 0 to 1 inclusive +const ( + MfaLevelNone MfaLevel = 0 + MfaLevelElevated MfaLevel = 1 +) + +// PremiumTier type definition +type PremiumTier int + +// Constants for PremiumTier levels from 0 to 3 inclusive +const ( + PremiumTierNone PremiumTier = 0 + PremiumTier1 PremiumTier = 1 + PremiumTier2 PremiumTier = 2 + PremiumTier3 PremiumTier = 3 +) + +// A Guild holds all data related to a specific Discord Guild. Guilds are also +// sometimes referred to as Servers in the Discord client. +type Guild struct { + // The ID of the guild. + ID string `json:"id"` + + // The name of the guild. (2–100 characters) + Name string `json:"name"` + + // The hash of the guild's icon. Use Session.GuildIcon + // to retrieve the icon itself. + Icon string `json:"icon"` + + // The voice region of the guild. + Region string `json:"region"` + + // The ID of the AFK voice channel. + AfkChannelID string `json:"afk_channel_id"` + + // The user ID of the owner of the guild. + OwnerID string `json:"owner_id"` + + // If we are the owner of the guild + Owner bool `json:"owner"` + + // The time at which the current user joined the guild. + // This field is only present in GUILD_CREATE events and websocket + // update events, and thus is only present in state-cached guilds. + JoinedAt time.Time `json:"joined_at"` + + // The hash of the guild's discovery splash. + DiscoverySplash string `json:"discovery_splash"` + + // The hash of the guild's splash. + Splash string `json:"splash"` + + // The timeout, in seconds, before a user is considered AFK in voice. + AfkTimeout int `json:"afk_timeout"` + + // The number of members in the guild. + // This field is only present in GUILD_CREATE events and websocket + // update events, and thus is only present in state-cached guilds. + MemberCount int `json:"member_count"` + + // The verification level required for the guild. + VerificationLevel VerificationLevel `json:"verification_level"` + + // Whether the guild is considered large. This is + // determined by a member threshold in the identify packet, + // and is currently hard-coded at 250 members in the library. + Large bool `json:"large"` + + // The default message notification setting for the guild. + DefaultMessageNotifications MessageNotifications `json:"default_message_notifications"` + + // A list of roles in the guild. + Roles []*Role `json:"roles"` + + // A list of the custom emojis present in the guild. + Emojis []*Emoji `json:"emojis"` + + // A list of the custom stickers present in the guild. + Stickers []*Sticker `json:"stickers"` + + // A list of the members in the guild. + // This field is only present in GUILD_CREATE events and websocket + // update events, and thus is only present in state-cached guilds. + Members []*Member `json:"members"` + + // A list of partial presence objects for members in the guild. + // This field is only present in GUILD_CREATE events and websocket + // update events, and thus is only present in state-cached guilds. + Presences []*Presence `json:"presences"` + + // The maximum number of presences for the guild (the default value, currently 25000, is in effect when null is returned) + MaxPresences int `json:"max_presences"` + + // The maximum number of members for the guild + MaxMembers int `json:"max_members"` + + // A list of channels in the guild. + // This field is only present in GUILD_CREATE events and websocket + // update events, and thus is only present in state-cached guilds. + Channels []*Channel `json:"channels"` + + // A list of all active threads in the guild that current user has permission to view + // This field is only present in GUILD_CREATE events and websocket + // update events and thus is only present in state-cached guilds. + Threads []*Channel `json:"threads"` + + // A list of voice states for the guild. + // This field is only present in GUILD_CREATE events and websocket + // update events, and thus is only present in state-cached guilds. + VoiceStates []*VoiceState `json:"voice_states"` + + // Whether this guild is currently unavailable (most likely due to outage). + // This field is only present in GUILD_CREATE events and websocket + // update events, and thus is only present in state-cached guilds. + Unavailable bool `json:"unavailable"` + + // The explicit content filter level + ExplicitContentFilter ExplicitContentFilterLevel `json:"explicit_content_filter"` + + // The list of enabled guild features + Features []string `json:"features"` + + // Required MFA level for the guild + MfaLevel MfaLevel `json:"mfa_level"` + + // The application id of the guild if bot created. + ApplicationID string `json:"application_id"` + + // Whether or not the Server Widget is enabled + WidgetEnabled bool `json:"widget_enabled"` + + // The Channel ID for the Server Widget + WidgetChannelID string `json:"widget_channel_id"` + + // The Channel ID to which system messages are sent (eg join and leave messages) + SystemChannelID string `json:"system_channel_id"` + + // The System channel flags + SystemChannelFlags SystemChannelFlag `json:"system_channel_flags"` + + // The ID of the rules channel ID, used for rules. + RulesChannelID string `json:"rules_channel_id"` + + // the vanity url code for the guild + VanityURLCode string `json:"vanity_url_code"` + + // the description for the guild + Description string `json:"description"` + + // The hash of the guild's banner + Banner string `json:"banner"` + + // The premium tier of the guild + PremiumTier PremiumTier `json:"premium_tier"` + + // The total number of users currently boosting this server + PremiumSubscriptionCount int `json:"premium_subscription_count"` + + // The preferred locale of a guild with the "PUBLIC" feature; used in server discovery and notices from Discord; defaults to "en-US" + PreferredLocale string `json:"preferred_locale"` + + // The id of the channel where admins and moderators of guilds with the "PUBLIC" feature receive notices from Discord + PublicUpdatesChannelID string `json:"public_updates_channel_id"` + + // The maximum amount of users in a video channel + MaxVideoChannelUsers int `json:"max_video_channel_users"` + + // Approximate number of members in this guild, returned from the GET /guild/ endpoint when with_counts is true + ApproximateMemberCount int `json:"approximate_member_count"` + + // Approximate number of non-offline members in this guild, returned from the GET /guild/ endpoint when with_counts is true + ApproximatePresenceCount int `json:"approximate_presence_count"` + + // Permissions of our user + Permissions int64 `json:"permissions,string"` +} + +// A GuildPreview holds data related to a specific public Discord Guild, even if the user is not in the guild. +type GuildPreview struct { + // The ID of the guild. + ID string `json:"id"` + + // The name of the guild. (2–100 characters) + Name string `json:"name"` + + // The hash of the guild's icon. Use Session.GuildIcon + // to retrieve the icon itself. + Icon string `json:"icon"` + + // The hash of the guild's splash. + Splash string `json:"splash"` + + // The hash of the guild's discovery splash. + DiscoverySplash string `json:"discovery_splash"` + + // A list of the custom emojis present in the guild. + Emojis []*Emoji `json:"emojis"` + + // The list of enabled guild features + Features []string `json:"features"` + + // Approximate number of members in this guild, returned from the GET /guild/ endpoint when with_counts is true + ApproximateMemberCount int `json:"approximate_member_count"` + + // Approximate number of non-offline members in this guild, returned from the GET /guild/ endpoint when with_counts is true + ApproximatePresenceCount int `json:"approximate_presence_count"` + + // the description for the guild + Description string `json:"description"` +} + +// GuildScheduledEvent is a representation of a scheduled event in a guild. Only for retrieval of the data. +// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event +type GuildScheduledEvent struct { + // The ID of the scheduled event + ID string `json:"id"` + // The guild id which the scheduled event belongs to + GuildID string `json:"guild_id"` + // The channel id in which the scheduled event will be hosted, or null if scheduled entity type is EXTERNAL + ChannelID string `json:"channel_id"` + // The id of the user that created the scheduled event + CreatorID string `json:"creator_id"` + // The name of the scheduled event (1-100 characters) + Name string `json:"name"` + // The description of the scheduled event (1-1000 characters) + Description string `json:"description"` + // The time the scheduled event will start + ScheduledStartTime time.Time `json:"scheduled_start_time"` + // The time the scheduled event will end, required only when entity_type is EXTERNAL + ScheduledEndTime *time.Time `json:"scheduled_end_time"` + // The privacy level of the scheduled event + PrivacyLevel GuildScheduledEventPrivacyLevel `json:"privacy_level"` + // The status of the scheduled event + Status GuildScheduledEventStatus `json:"status"` + // Type of the entity where event would be hosted + // See field requirements + // https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-field-requirements-by-entity-type + EntityType GuildScheduledEventEntityType `json:"entity_type"` + // The id of an entity associated with a guild scheduled event + EntityID string `json:"entity_id"` + // Additional metadata for the guild scheduled event + EntityMetadata GuildScheduledEventEntityMetadata `json:"entity_metadata"` + // The user that created the scheduled event + Creator *User `json:"creator"` + // The number of users subscribed to the scheduled event + UserCount int `json:"user_count"` + // The cover image hash of the scheduled event + // see https://discord.com/developers/docs/reference#image-formatting for more + // information about image formatting + Image string `json:"image"` +} + +// GuildScheduledEventParams are the parameters allowed for creating or updating a scheduled event +// https://discord.com/developers/docs/resources/guild-scheduled-event#create-guild-scheduled-event +type GuildScheduledEventParams struct { + // The channel id in which the scheduled event will be hosted, or null if scheduled entity type is EXTERNAL + ChannelID string `json:"channel_id,omitempty"` + // The name of the scheduled event (1-100 characters) + Name string `json:"name,omitempty"` + // The description of the scheduled event (1-1000 characters) + Description string `json:"description,omitempty"` + // The time the scheduled event will start + ScheduledStartTime *time.Time `json:"scheduled_start_time,omitempty"` + // The time the scheduled event will end, required only when entity_type is EXTERNAL + ScheduledEndTime *time.Time `json:"scheduled_end_time,omitempty"` + // The privacy level of the scheduled event + PrivacyLevel GuildScheduledEventPrivacyLevel `json:"privacy_level,omitempty"` + // The status of the scheduled event + Status GuildScheduledEventStatus `json:"status,omitempty"` + // Type of the entity where event would be hosted + // See field requirements + // https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-field-requirements-by-entity-type + EntityType GuildScheduledEventEntityType `json:"entity_type,omitempty"` + // Additional metadata for the guild scheduled event + EntityMetadata *GuildScheduledEventEntityMetadata `json:"entity_metadata,omitempty"` + // The cover image hash of the scheduled event + // see https://discord.com/developers/docs/reference#image-formatting for more + // information about image formatting + Image string `json:"image,omitempty"` +} + +// MarshalJSON is a helper function to marshal GuildScheduledEventParams +func (p GuildScheduledEventParams) MarshalJSON() ([]byte, error) { + type guildScheduledEventParams GuildScheduledEventParams + + if p.EntityType == GuildScheduledEventEntityTypeExternal && p.ChannelID == "" { + return json.Marshal(struct { + guildScheduledEventParams + ChannelID json.RawMessage `json:"channel_id"` + }{ + guildScheduledEventParams: guildScheduledEventParams(p), + ChannelID: json.RawMessage("null"), + }) + } + + return json.Marshal(guildScheduledEventParams(p)) +} + +// GuildScheduledEventEntityMetadata holds additional metadata for guild scheduled event. +type GuildScheduledEventEntityMetadata struct { + // location of the event (1-100 characters) + // required for events with 'entity_type': EXTERNAL + Location string `json:"location"` +} + +// GuildScheduledEventPrivacyLevel is the privacy level of a scheduled event. +// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-privacy-level +type GuildScheduledEventPrivacyLevel int + +const ( + // GuildScheduledEventPrivacyLevelGuildOnly makes the scheduled + // event is only accessible to guild members + GuildScheduledEventPrivacyLevelGuildOnly GuildScheduledEventPrivacyLevel = 2 +) + +// GuildScheduledEventStatus is the status of a scheduled event +// Valid Guild Scheduled Event Status Transitions : +// SCHEDULED --> ACTIVE --> COMPLETED +// SCHEDULED --> CANCELED +// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-status +type GuildScheduledEventStatus int + +const ( + // GuildScheduledEventStatusScheduled represents the current event is in scheduled state + GuildScheduledEventStatusScheduled = 1 + // GuildScheduledEventStatusActive represents the current event is in active state + GuildScheduledEventStatusActive = 2 + // GuildScheduledEventStatusCompleted represents the current event is in completed state + GuildScheduledEventStatusCompleted = 3 + // GuildScheduledEventStatusCanceled represents the current event is in canceled state + GuildScheduledEventStatusCanceled = 4 +) + +// GuildScheduledEventEntityType is the type of entity associated with a guild scheduled event. +// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-object-guild-scheduled-event-entity-types +type GuildScheduledEventEntityType int + +const ( + // GuildScheduledEventEntityTypeStageInstance represents a stage channel + GuildScheduledEventEntityTypeStageInstance = 1 + // GuildScheduledEventEntityTypeVoice represents a voice channel + GuildScheduledEventEntityTypeVoice = 2 + // GuildScheduledEventEntityTypeExternal represents an external event + GuildScheduledEventEntityTypeExternal = 3 +) + +// GuildScheduledEventUser is a user subscribed to a scheduled event. +// https://discord.com/developers/docs/resources/guild-scheduled-event#guild-scheduled-event-user-object +type GuildScheduledEventUser struct { + GuildScheduledEventID string `json:"guild_scheduled_event_id"` + User *User `json:"user"` + Member *Member `json:"member"` +} + +// A GuildTemplate represents +type GuildTemplate struct { + // The unique code for the guild template + Code string `json:"code"` + + // The name of the template + Name string `json:"name"` + + // The description for the template + Description string `json:"description"` + + // The number of times this template has been used + UsageCount string `json:"usage_count"` + + // The ID of the user who created the template + CreatorID string `json:"creator_id"` + + // The user who created the template + Creator *User `json:"creator"` + + // The timestamp of when the template was created + CreatedAt time.Time `json:"created_at"` + + // The timestamp of when the template was last synced + UpdatedAt time.Time `json:"updated_at"` + + // The ID of the guild the template was based on + SourceGuildID string `json:"source_guild_id"` + + // The guild 'snapshot' this template contains + SerializedSourceGuild *Guild `json:"serialized_source_guild"` + + // Whether the template has unsynced changes + IsDirty bool `json:"is_dirty"` +} + +// MessageNotifications is the notification level for a guild +// https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level +type MessageNotifications int + +// Block containing known MessageNotifications values +const ( + MessageNotificationsAllMessages MessageNotifications = 0 + MessageNotificationsOnlyMentions MessageNotifications = 1 +) + +// SystemChannelFlag is the type of flags in the system channel (see SystemChannelFlag* consts) +// https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags +type SystemChannelFlag int + +// Block containing known SystemChannelFlag values +const ( + SystemChannelFlagsSuppressJoin SystemChannelFlag = 1 << 0 + SystemChannelFlagsSuppressPremium SystemChannelFlag = 1 << 1 +) + +// IconURL returns a URL to the guild's icon. +func (g *Guild) IconURL() string { + if g.Icon == "" { + return "" + } + + if strings.HasPrefix(g.Icon, "a_") { + return EndpointGuildIconAnimated(g.ID, g.Icon) + } + + return EndpointGuildIcon(g.ID, g.Icon) +} + +// BannerURL returns a URL to the guild's banner. +func (g *Guild) BannerURL() string { + if g.Banner == "" { + return "" + } + return EndpointGuildBanner(g.ID, g.Banner) +} + +// A UserGuild holds a brief version of a Guild +type UserGuild struct { + ID string `json:"id"` + Name string `json:"name"` + Icon string `json:"icon"` + Owner bool `json:"owner"` + Permissions int64 `json:"permissions,string"` +} + +// A GuildParams stores all the data needed to update discord guild settings +type GuildParams struct { + Name string `json:"name,omitempty"` + Region string `json:"region,omitempty"` + VerificationLevel *VerificationLevel `json:"verification_level,omitempty"` + DefaultMessageNotifications int `json:"default_message_notifications,omitempty"` // TODO: Separate type? + AfkChannelID string `json:"afk_channel_id,omitempty"` + AfkTimeout int `json:"afk_timeout,omitempty"` + Icon string `json:"icon,omitempty"` + OwnerID string `json:"owner_id,omitempty"` + Splash string `json:"splash,omitempty"` + Banner string `json:"banner,omitempty"` +} + +// A Role stores information about Discord guild member roles. +type Role struct { + // The ID of the role. + ID string `json:"id"` + + // The name of the role. + Name string `json:"name"` + + // Whether this role is managed by an integration, and + // thus cannot be manually added to, or taken from, members. + Managed bool `json:"managed"` + + // Whether this role is mentionable. + Mentionable bool `json:"mentionable"` + + // Whether this role is hoisted (shows up separately in member list). + Hoist bool `json:"hoist"` + + // The hex color of this role. + Color int `json:"color"` + + // The position of this role in the guild's role hierarchy. + Position int `json:"position"` + + // The permissions of the role on the guild (doesn't include channel overrides). + // This is a combination of bit masks; the presence of a certain permission can + // be checked by performing a bitwise AND between this int and the permission. + Permissions int64 `json:"permissions,string"` +} + +// Mention returns a string which mentions the role +func (r *Role) Mention() string { + return fmt.Sprintf("<@&%s>", r.ID) +} + +// Roles are a collection of Role +type Roles []*Role + +func (r Roles) Len() int { + return len(r) +} + +func (r Roles) Less(i, j int) bool { + return r[i].Position > r[j].Position +} + +func (r Roles) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +// A VoiceState stores the voice states of Guilds +type VoiceState struct { + UserID string `json:"user_id"` + SessionID string `json:"session_id"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id"` + Suppress bool `json:"suppress"` + SelfMute bool `json:"self_mute"` + SelfDeaf bool `json:"self_deaf"` + Mute bool `json:"mute"` + Deaf bool `json:"deaf"` +} + +// A Presence stores the online, offline, or idle and game status of Guild members. +type Presence struct { + User *User `json:"user"` + Status Status `json:"status"` + Activities []*Activity `json:"activities"` + Since *int `json:"since"` +} + +// A TimeStamps struct contains start and end times used in the rich presence "playing .." Game +type TimeStamps struct { + EndTimestamp int64 `json:"end,omitempty"` + StartTimestamp int64 `json:"start,omitempty"` +} + +// UnmarshalJSON unmarshals JSON into TimeStamps struct +func (t *TimeStamps) UnmarshalJSON(b []byte) error { + temp := struct { + End float64 `json:"end,omitempty"` + Start float64 `json:"start,omitempty"` + }{} + err := json.Unmarshal(b, &temp) + if err != nil { + return err + } + t.EndTimestamp = int64(temp.End) + t.StartTimestamp = int64(temp.Start) + return nil +} + +// An Assets struct contains assets and labels used in the rich presence "playing .." Game +type Assets struct { + LargeImageID string `json:"large_image,omitempty"` + SmallImageID string `json:"small_image,omitempty"` + LargeText string `json:"large_text,omitempty"` + SmallText string `json:"small_text,omitempty"` +} + +// A Member stores user information for Guild members. A guild +// member represents a certain user's presence in a guild. +type Member struct { + // The guild ID on which the member exists. + GuildID string `json:"guild_id"` + + // The time at which the member joined the guild. + JoinedAt time.Time `json:"joined_at"` + + // The nickname of the member, if they have one. + Nick string `json:"nick"` + + // Whether the member is deafened at a guild level. + Deaf bool `json:"deaf"` + + // Whether the member is muted at a guild level. + Mute bool `json:"mute"` + + // The hash of the avatar for the guild member, if any. + Avatar string `json:"avatar"` + + // The underlying user on which the member is based. + User *User `json:"user"` + + // A list of IDs of the roles which are possessed by the member. + Roles []string `json:"roles"` + + // When the user used their Nitro boost on the server + PremiumSince *time.Time `json:"premium_since"` + + // Is true while the member hasn't accepted the membership screen. + Pending bool `json:"pending"` + + // Total permissions of the member in the channel, including overrides, returned when in the interaction object. + Permissions int64 `json:"permissions,string"` + + // The time at which the member's timeout will expire. + // Time in the past or nil if the user is not timed out. + CommunicationDisabledUntil *time.Time `json:"communication_disabled_until"` +} + +// Mention creates a member mention +func (m *Member) Mention() string { + return "<@!" + m.User.ID + ">" +} + +// AvatarURL returns the URL of the member's avatar +// size: The size of the user's avatar as a power of two +// if size is an empty string, no size parameter will +// be added to the URL. +func (m *Member) AvatarURL(size string) string { + if m.Avatar == "" { + return m.User.AvatarURL(size) + } + // The default/empty avatar case should be handled by the above condition + return avatarURL(m.Avatar, "", EndpointGuildMemberAvatar(m.GuildID, m.User.ID, m.Avatar), + EndpointGuildMemberAvatarAnimated(m.GuildID, m.User.ID, m.Avatar), size) + +} + +// A Settings stores data for a specific users Discord client settings. +type Settings struct { + RenderEmbeds bool `json:"render_embeds"` + InlineEmbedMedia bool `json:"inline_embed_media"` + InlineAttachmentMedia bool `json:"inline_attachment_media"` + EnableTTSCommand bool `json:"enable_tts_command"` + MessageDisplayCompact bool `json:"message_display_compact"` + ShowCurrentGame bool `json:"show_current_game"` + ConvertEmoticons bool `json:"convert_emoticons"` + Locale string `json:"locale"` + Theme string `json:"theme"` + GuildPositions []string `json:"guild_positions"` + RestrictedGuilds []string `json:"restricted_guilds"` + FriendSourceFlags *FriendSourceFlags `json:"friend_source_flags"` + Status Status `json:"status"` + DetectPlatformAccounts bool `json:"detect_platform_accounts"` + DeveloperMode bool `json:"developer_mode"` +} + +// Status type definition +type Status string + +// Constants for Status with the different current available status +const ( + StatusOnline Status = "online" + StatusIdle Status = "idle" + StatusDoNotDisturb Status = "dnd" + StatusInvisible Status = "invisible" + StatusOffline Status = "offline" +) + +// FriendSourceFlags stores ... TODO :) +type FriendSourceFlags struct { + All bool `json:"all"` + MutualGuilds bool `json:"mutual_guilds"` + MutualFriends bool `json:"mutual_friends"` +} + +// A Relationship between the logged in user and Relationship.User +type Relationship struct { + User *User `json:"user"` + Type int `json:"type"` // 1 = friend, 2 = blocked, 3 = incoming friend req, 4 = sent friend req + ID string `json:"id"` +} + +// A TooManyRequests struct holds information received from Discord +// when receiving a HTTP 429 response. +type TooManyRequests struct { + Bucket string `json:"bucket"` + Message string `json:"message"` + RetryAfter time.Duration `json:"retry_after"` +} + +// UnmarshalJSON helps support translation of a milliseconds-based float +// into a time.Duration on TooManyRequests. +func (t *TooManyRequests) UnmarshalJSON(b []byte) error { + u := struct { + Bucket string `json:"bucket"` + Message string `json:"message"` + RetryAfter float64 `json:"retry_after"` + }{} + err := json.Unmarshal(b, &u) + if err != nil { + return err + } + + t.Bucket = u.Bucket + t.Message = u.Message + whole, frac := math.Modf(u.RetryAfter) + t.RetryAfter = time.Duration(whole)*time.Second + time.Duration(frac*1000)*time.Millisecond + return nil +} + +// A ReadState stores data on the read state of channels. +type ReadState struct { + MentionCount int `json:"mention_count"` + LastMessageID string `json:"last_message_id"` + ID string `json:"id"` +} + +// An Ack is used to ack messages +type Ack struct { + Token string `json:"token"` +} + +// A GuildRole stores data for guild roles. +type GuildRole struct { + Role *Role `json:"role"` + GuildID string `json:"guild_id"` +} + +// A GuildBan stores data for a guild ban. +type GuildBan struct { + Reason string `json:"reason"` + User *User `json:"user"` +} + +// A GuildEmbed stores data for a guild embed. +type GuildEmbed struct { + Enabled bool `json:"enabled"` + ChannelID string `json:"channel_id"` +} + +// A GuildAuditLog stores data for a guild audit log. +// https://discord.com/developers/docs/resources/audit-log#audit-log-object-audit-log-structure +type GuildAuditLog struct { + Webhooks []*Webhook `json:"webhooks,omitempty"` + Users []*User `json:"users,omitempty"` + AuditLogEntries []*AuditLogEntry `json:"audit_log_entries"` + Integrations []*Integration `json:"integrations"` +} + +// AuditLogEntry for a GuildAuditLog +// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-entry-structure +type AuditLogEntry struct { + TargetID string `json:"target_id"` + Changes []*AuditLogChange `json:"changes"` + UserID string `json:"user_id"` + ID string `json:"id"` + ActionType *AuditLogAction `json:"action_type"` + Options *AuditLogOptions `json:"options"` + Reason string `json:"reason"` +} + +// AuditLogChange for an AuditLogEntry +type AuditLogChange struct { + NewValue interface{} `json:"new_value"` + OldValue interface{} `json:"old_value"` + Key *AuditLogChangeKey `json:"key"` +} + +// AuditLogChangeKey value for AuditLogChange +// https://discord.com/developers/docs/resources/audit-log#audit-log-change-object-audit-log-change-key +type AuditLogChangeKey string + +// Block of valid AuditLogChangeKey +const ( + // AuditLogChangeKeyAfkChannelID is sent when afk channel changed (snowflake) - guild + AuditLogChangeKeyAfkChannelID AuditLogChangeKey = "afk_channel_id" + // AuditLogChangeKeyAfkTimeout is sent when afk timeout duration changed (int) - guild + AuditLogChangeKeyAfkTimeout AuditLogChangeKey = "afk_timeout" + // AuditLogChangeKeyAllow is sent when a permission on a text or voice channel was allowed for a role (string) - role + AuditLogChangeKeyAllow AuditLogChangeKey = "allow" + // AudirChangeKeyApplicationID is sent when application id of the added or removed webhook or bot (snowflake) - channel + AuditLogChangeKeyApplicationID AuditLogChangeKey = "application_id" + // AuditLogChangeKeyArchived is sent when thread was archived/unarchived (bool) - thread + AuditLogChangeKeyArchived AuditLogChangeKey = "archived" + // AuditLogChangeKeyAsset is sent when asset is changed (string) - sticker + AuditLogChangeKeyAsset AuditLogChangeKey = "asset" + // AuditLogChangeKeyAutoArchiveDuration is sent when auto archive duration changed (int) - thread + AuditLogChangeKeyAutoArchiveDuration AuditLogChangeKey = "auto_archive_duration" + // AuditLogChangeKeyAvailable is sent when availability of sticker changed (bool) - sticker + AuditLogChangeKeyAvailable AuditLogChangeKey = "available" + // AuditLogChangeKeyAvatarHash is sent when user avatar changed (string) - user + AuditLogChangeKeyAvatarHash AuditLogChangeKey = "avatar_hash" + // AuditLogChangeKeyBannerHash is sent when guild banner changed (string) - guild + AuditLogChangeKeyBannerHash AuditLogChangeKey = "banner_hash" + // AuditLogChangeKeyBitrate is sent when voice channel bitrate changed (int) - channel + AuditLogChangeKeyBitrate AuditLogChangeKey = "bitrate" + // AuditLogChangeKeyChannelID is sent when channel for invite code or guild scheduled event changed (snowflake) - invite or guild scheduled event + AuditLogChangeKeyChannelID AuditLogChangeKey = "channel_id" + // AuditLogChangeKeyCode is sent when invite code changed (string) - invite + AuditLogChangeKeyCode AuditLogChangeKey = "code" + // AuditLogChangeKeyColor is sent when role color changed (int) - role + AuditLogChangeKeyColor AuditLogChangeKey = "color" + // AuditLogChangeKeyCommunicationDisabledUntil is sent when member timeout state changed (ISO8601 timestamp) - member + AuditLogChangeKeyCommunicationDisabledUntil AuditLogChangeKey = "communication_disabled_until" + // AuditLogChangeKeyDeaf is sent when user server deafened/undeafened (bool) - member + AuditLogChangeKeyDeaf AuditLogChangeKey = "deaf" + // AuditLogChangeKeyDefaultAutoArchiveDuration is sent when default auto archive duration for newly created threads changed (int) - channel + AuditLogChangeKeyDefaultAutoArchiveDuration AuditLogChangeKey = "default_auto_archive_duration" + // AuditLogChangeKeyDefaultMessageNotification is sent when default message notification level changed (int) - guild + AuditLogChangeKeyDefaultMessageNotification AuditLogChangeKey = "default_message_notifications" + // AuditLogChangeKeyDeny is sent when a permission on a text or voice channel was denied for a role (string) - role + AuditLogChangeKeyDeny AuditLogChangeKey = "deny" + // AuditLogChangeKeyDescription is sent when description changed (string) - guild, sticker, or guild scheduled event + AuditLogChangeKeyDescription AuditLogChangeKey = "description" + // AuditLogChangeKeyDiscoverySplashHash is sent when discovery splash changed (string) - guild + AuditLogChangeKeyDiscoverySplashHash AuditLogChangeKey = "discovery_splash_hash" + // AuditLogChangeKeyEnableEmoticons is sent when integration emoticons enabled/disabled (bool) - integration + AuditLogChangeKeyEnableEmoticons AuditLogChangeKey = "enable_emoticons" + // AuditLogChangeKeyEntityType is sent when entity type of guild scheduled event was changed (int) - guild scheduled event + AuditLogChangeKeyEntityType AuditLogChangeKey = "entity_type" + // AuditLogChangeKeyExpireBehavior is sent when integration expiring subscriber behavior changed (int) - integration + AuditLogChangeKeyExpireBehavior AuditLogChangeKey = "expire_behavior" + // AuditLogChangeKeyExpireGracePeriod is sent when integration expire grace period changed (int) - integration + AuditLogChangeKeyExpireGracePeriod AuditLogChangeKey = "expire_grace_period" + // AuditLogChangeKeyExplicitContentFilter is sent when change in whose messages are scanned and deleted for explicit content in the server is made (int) - guild + AuditLogChangeKeyExplicitContentFilter AuditLogChangeKey = "explicit_content_filter" + // AuditLogChangeKeyFormatType is sent when format type of sticker changed (int - sticker format type) - sticker + AuditLogChangeKeyFormatType AuditLogChangeKey = "format_type" + // AuditLogChangeKeyGuildID is sent when guild sticker is in changed (snowflake) - sticker + AuditLogChangeKeyGuildID AuditLogChangeKey = "guild_id" + // AuditLogChangeKeyHoist is sent when role is now displayed/no longer displayed separate from online users (bool) - role + AuditLogChangeKeyHoist AuditLogChangeKey = "hoist" + // AuditLogChangeKeyIconHash is sent when icon changed (string) - guild or role + AuditLogChangeKeyIconHash AuditLogChangeKey = "icon_hash" + // AuditLogChangeKeyID is sent when the id of the changed entity - sometimes used in conjunction with other keys (snowflake) - any + AuditLogChangeKeyID AuditLogChangeKey = "id" + // AuditLogChangeKeyInvitable is sent when private thread is now invitable/uninvitable (bool) - thread + AuditLogChangeKeyInvitable AuditLogChangeKey = "invitable" + // AuditLogChangeKeyInviterID is sent when person who created invite code changed (snowflake) - invite + AuditLogChangeKeyInviterID AuditLogChangeKey = "inviter_id" + // AuditLogChangeKeyLocation is sent when channel id for guild scheduled event changed (string) - guild scheduled event + AuditLogChangeKeyLocation AuditLogChangeKey = "location" + // AuditLogChangeKeyLocked is sent when thread was locked/unlocked (bool) - thread + AuditLogChangeKeyLocked AuditLogChangeKey = "locked" + // AuditLogChangeKeyMaxAge is sent when invite code expiration time changed (int) - invite + AuditLogChangeKeyMaxAge AuditLogChangeKey = "max_age" + // AuditLogChangeKeyMaxUses is sent when max number of times invite code can be used changed (int) - invite + AuditLogChangeKeyMaxUses AuditLogChangeKey = "max_uses" + // AuditLogChangeKeyMentionable is sent when role is now mentionable/unmentionable (bool) - role + AuditLogChangeKeyMentionable AuditLogChangeKey = "mentionable" + // AuditLogChangeKeyMfaLevel is sent when two-factor auth requirement changed (int - mfa level) - guild + AuditLogChangeKeyMfaLevel AuditLogChangeKey = "mfa_level" + // AuditLogChangeKeyMute is sent when user server muted/unmuted (bool) - member + AuditLogChangeKeyMute AuditLogChangeKey = "mute" + // AuditLogChangeKeyName is sent when name changed (string) - any + AuditLogChangeKeyName AuditLogChangeKey = "name" + // AuditLogChangeKeyNick is sent when user nickname changed (string) - member + AuditLogChangeKeyNick AuditLogChangeKey = "nick" + // AuditLogChangeKeyNSFW is sent when channel nsfw restriction changed (bool) - channel + AuditLogChangeKeyNSFW AuditLogChangeKey = "nsfw" + // AuditLogChangeKeyOwnerID is sent when owner changed (snowflake) - guild + AuditLogChangeKeyOwnerID AuditLogChangeKey = "owner_id" + // AuditLogChangeKeyPermissionOverwrite is sent when permissions on a channel changed (array of channel overwrite objects) - channel + AuditLogChangeKeyPermissionOverwrite AuditLogChangeKey = "permission_overwrites" + // AuditLogChangeKeyPermissions is sent when permissions for a role changed (string) - role + AuditLogChangeKeyPermissions AuditLogChangeKey = "permissions" + // AuditLogChangeKeyPosition is sent when text or voice channel position changed (int) - channel + AuditLogChangeKeyPosition AuditLogChangeKey = "position" + // AuditLogChangeKeyPreferredLocale is sent when preferred locale changed (string) - guild + AuditLogChangeKeyPreferredLocale AuditLogChangeKey = "preferred_locale" + // AuditLogChangeKeyPrivacylevel is sent when privacy level of the stage instance changed (integer - privacy level) - stage instance or guild scheduled event + AuditLogChangeKeyPrivacylevel AuditLogChangeKey = "privacy_level" + // AuditLogChangeKeyPruneDeleteDays is sent when number of days after which inactive and role-unassigned members are kicked changed (int) - guild + AuditLogChangeKeyPruneDeleteDays AuditLogChangeKey = "prune_delete_days" + // AuditLogChangeKeyPulibUpdatesChannelID is sent when id of the public updates channel changed (snowflake) - guild + AuditLogChangeKeyPulibUpdatesChannelID AuditLogChangeKey = "public_updates_channel_id" + // AuditLogChangeKeyRateLimitPerUser is sent when amount of seconds a user has to wait before sending another message changed (int) - channel + AuditLogChangeKeyRateLimitPerUser AuditLogChangeKey = "rate_limit_per_user" + // AuditLogChangeKeyRegion is sent when region changed (string) - guild + AuditLogChangeKeyRegion AuditLogChangeKey = "region" + // AuditLogChangeKeyRulesChannelID is sent when id of the rules channel changed (snowflake) - guild + AuditLogChangeKeyRulesChannelID AuditLogChangeKey = "rules_channel_id" + // AuditLogChangeKeySplashHash is sent when invite splash page artwork changed (string) - guild + AuditLogChangeKeySplashHash AuditLogChangeKey = "splash_hash" + // AuditLogChangeKeyStatus is sent when status of guild scheduled event was changed (int - guild scheduled event status) - guild scheduled event + AuditLogChangeKeyStatus AuditLogChangeKey = "status" + // AuditLogChangeKeySystemChannelID is sent when id of the system channel changed (snowflake) - guild + AuditLogChangeKeySystemChannelID AuditLogChangeKey = "system_channel_id" + // AuditLogChangeKeyTags is sent when related emoji of sticker changed (string) - sticker + AuditLogChangeKeyTags AuditLogChangeKey = "tags" + // AuditLogChangeKeyTemporary is sent when invite code is now temporary or never expires (bool) - invite + AuditLogChangeKeyTemporary AuditLogChangeKey = "temporary" + // TODO: remove when compatibility is not required + AuditLogChangeKeyTempoary = AuditLogChangeKeyTemporary + // AuditLogChangeKeyTopic is sent when text channel topic or stage instance topic changed (string) - channel or stage instance + AuditLogChangeKeyTopic AuditLogChangeKey = "topic" + // AuditLogChangeKeyType is sent when type of entity created (int or string) - any + AuditLogChangeKeyType AuditLogChangeKey = "type" + // AuditLogChangeKeyUnicodeEmoji is sent when role unicode emoji changed (string) - role + AuditLogChangeKeyUnicodeEmoji AuditLogChangeKey = "unicode_emoji" + // AuditLogChangeKeyUserLimit is sent when new user limit in a voice channel set (int) - voice channel + AuditLogChangeKeyUserLimit AuditLogChangeKey = "user_limit" + // AuditLogChangeKeyUses is sent when number of times invite code used changed (int) - invite + AuditLogChangeKeyUses AuditLogChangeKey = "uses" + // AuditLogChangeKeyVanityURLCode is sent when guild invite vanity url changed (string) - guild + AuditLogChangeKeyVanityURLCode AuditLogChangeKey = "vanity_url_code" + // AuditLogChangeKeyVerificationLevel is sent when required verification level changed (int - verification level) - guild + AuditLogChangeKeyVerificationLevel AuditLogChangeKey = "verification_level" + // AuditLogChangeKeyWidgetChannelID is sent when channel id of the server widget changed (snowflake) - guild + AuditLogChangeKeyWidgetChannelID AuditLogChangeKey = "widget_channel_id" + // AuditLogChangeKeyWidgetEnabled is sent when server widget enabled/disabled (bool) - guild + AuditLogChangeKeyWidgetEnabled AuditLogChangeKey = "widget_enabled" + // AuditLogChangeKeyRoleAdd is sent when new role added (array of partial role objects) - guild + AuditLogChangeKeyRoleAdd AuditLogChangeKey = "$add" + // AuditLogChangeKeyRoleRemove is sent when role removed (array of partial role objects) - guild + AuditLogChangeKeyRoleRemove AuditLogChangeKey = "$remove" +) + +// AuditLogOptions optional data for the AuditLog +// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info +type AuditLogOptions struct { + DeleteMemberDays string `json:"delete_member_days"` + MembersRemoved string `json:"members_removed"` + ChannelID string `json:"channel_id"` + MessageID string `json:"message_id"` + Count string `json:"count"` + ID string `json:"id"` + Type *AuditLogOptionsType `json:"type"` + RoleName string `json:"role_name"` +} + +// AuditLogOptionsType of the AuditLogOption +// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info +type AuditLogOptionsType string + +// Valid Types for AuditLogOptionsType +const ( + AuditLogOptionsTypeMember AuditLogOptionsType = "member" + AuditLogOptionsTypeRole AuditLogOptionsType = "role" +) + +// AuditLogAction is the Action of the AuditLog (see AuditLogAction* consts) +// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events +type AuditLogAction int + +// Block contains Discord Audit Log Action Types +const ( + AuditLogActionGuildUpdate AuditLogAction = 1 + + AuditLogActionChannelCreate AuditLogAction = 10 + AuditLogActionChannelUpdate AuditLogAction = 11 + AuditLogActionChannelDelete AuditLogAction = 12 + AuditLogActionChannelOverwriteCreate AuditLogAction = 13 + AuditLogActionChannelOverwriteUpdate AuditLogAction = 14 + AuditLogActionChannelOverwriteDelete AuditLogAction = 15 + + AuditLogActionMemberKick AuditLogAction = 20 + AuditLogActionMemberPrune AuditLogAction = 21 + AuditLogActionMemberBanAdd AuditLogAction = 22 + AuditLogActionMemberBanRemove AuditLogAction = 23 + AuditLogActionMemberUpdate AuditLogAction = 24 + AuditLogActionMemberRoleUpdate AuditLogAction = 25 + AuditLogActionMemberMove AuditLogAction = 26 + AuditLogActionMemberDisconnect AuditLogAction = 27 + AuditLogActionBotAdd AuditLogAction = 28 + + AuditLogActionRoleCreate AuditLogAction = 30 + AuditLogActionRoleUpdate AuditLogAction = 31 + AuditLogActionRoleDelete AuditLogAction = 32 + + AuditLogActionInviteCreate AuditLogAction = 40 + AuditLogActionInviteUpdate AuditLogAction = 41 + AuditLogActionInviteDelete AuditLogAction = 42 + + AuditLogActionWebhookCreate AuditLogAction = 50 + AuditLogActionWebhookUpdate AuditLogAction = 51 + AuditLogActionWebhookDelete AuditLogAction = 52 + + AuditLogActionEmojiCreate AuditLogAction = 60 + AuditLogActionEmojiUpdate AuditLogAction = 61 + AuditLogActionEmojiDelete AuditLogAction = 62 + + AuditLogActionMessageDelete AuditLogAction = 72 + AuditLogActionMessageBulkDelete AuditLogAction = 73 + AuditLogActionMessagePin AuditLogAction = 74 + AuditLogActionMessageUnpin AuditLogAction = 75 + + AuditLogActionIntegrationCreate AuditLogAction = 80 + AuditLogActionIntegrationUpdate AuditLogAction = 81 + AuditLogActionIntegrationDelete AuditLogAction = 82 + AuditLogActionStageInstanceCreate AuditLogAction = 83 + AuditLogActionStageInstanceUpdate AuditLogAction = 84 + AuditLogActionStageInstanceDelete AuditLogAction = 85 + + AuditLogActionStickerCreate AuditLogAction = 90 + AuditLogActionStickerUpdate AuditLogAction = 91 + AuditLogActionStickerDelete AuditLogAction = 92 + + AuditLogGuildScheduledEventCreate AuditLogAction = 100 + AuditLogGuildScheduledEventUpdare AuditLogAction = 101 + AuditLogGuildScheduledEventDelete AuditLogAction = 102 + + AuditLogActionThreadCreate AuditLogAction = 110 + AuditLogActionThreadUpdate AuditLogAction = 111 + AuditLogActionThreadDelete AuditLogAction = 112 +) + +// A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings. +type UserGuildSettingsChannelOverride struct { + Muted bool `json:"muted"` + MessageNotifications int `json:"message_notifications"` + ChannelID string `json:"channel_id"` +} + +// A UserGuildSettings stores data for a users guild settings. +type UserGuildSettings struct { + SupressEveryone bool `json:"suppress_everyone"` + Muted bool `json:"muted"` + MobilePush bool `json:"mobile_push"` + MessageNotifications int `json:"message_notifications"` + GuildID string `json:"guild_id"` + ChannelOverrides []*UserGuildSettingsChannelOverride `json:"channel_overrides"` +} + +// A UserGuildSettingsEdit stores data for editing UserGuildSettings +type UserGuildSettingsEdit struct { + SupressEveryone bool `json:"suppress_everyone"` + Muted bool `json:"muted"` + MobilePush bool `json:"mobile_push"` + MessageNotifications int `json:"message_notifications"` + ChannelOverrides map[string]*UserGuildSettingsChannelOverride `json:"channel_overrides"` +} + +// An APIErrorMessage is an api error message returned from discord +type APIErrorMessage struct { + Code int `json:"code"` + Message string `json:"message"` +} + +// MessageReaction stores the data for a message reaction. +type MessageReaction struct { + UserID string `json:"user_id"` + MessageID string `json:"message_id"` + Emoji Emoji `json:"emoji"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id,omitempty"` +} + +// GatewayBotResponse stores the data for the gateway/bot response +type GatewayBotResponse struct { + URL string `json:"url"` + Shards int `json:"shards"` + SessionStartLimit SessionInformation `json:"session_start_limit"` +} + +// SessionInformation provides the information for max concurrency sharding +type SessionInformation struct { + Total int `json:"total,omitempty"` + Remaining int `json:"remaining,omitempty"` + ResetAfter int `json:"reset_after,omitempty"` + MaxConcurrency int `json:"max_concurrency,omitempty"` +} + +// GatewayStatusUpdate is sent by the client to indicate a presence or status update +// https://discord.com/developers/docs/topics/gateway#update-status-gateway-status-update-structure +type GatewayStatusUpdate struct { + Since int `json:"since"` + Game Activity `json:"game"` + Status string `json:"status"` + AFK bool `json:"afk"` +} + +// Activity defines the Activity sent with GatewayStatusUpdate +// https://discord.com/developers/docs/topics/gateway#activity-object +type Activity struct { + Name string `json:"name"` + Type ActivityType `json:"type"` + URL string `json:"url,omitempty"` + CreatedAt time.Time `json:"created_at"` + ApplicationID string `json:"application_id,omitempty"` + State string `json:"state,omitempty"` + Details string `json:"details,omitempty"` + Timestamps TimeStamps `json:"timestamps,omitempty"` + Emoji Emoji `json:"emoji,omitempty"` + Party Party `json:"party,omitempty"` + Assets Assets `json:"assets,omitempty"` + Secrets Secrets `json:"secrets,omitempty"` + Instance bool `json:"instance,omitempty"` + Flags int `json:"flags,omitempty"` +} + +// UnmarshalJSON is a custom unmarshaljson to make CreatedAt a time.Time instead of an int +func (activity *Activity) UnmarshalJSON(b []byte) error { + temp := struct { + Name string `json:"name"` + Type ActivityType `json:"type"` + URL string `json:"url,omitempty"` + CreatedAt int64 `json:"created_at"` + ApplicationID string `json:"application_id,omitempty"` + State string `json:"state,omitempty"` + Details string `json:"details,omitempty"` + Timestamps TimeStamps `json:"timestamps,omitempty"` + Emoji Emoji `json:"emoji,omitempty"` + Party Party `json:"party,omitempty"` + Assets Assets `json:"assets,omitempty"` + Secrets Secrets `json:"secrets,omitempty"` + Instance bool `json:"instance,omitempty"` + Flags int `json:"flags,omitempty"` + }{} + err := json.Unmarshal(b, &temp) + if err != nil { + return err + } + activity.CreatedAt = time.Unix(0, temp.CreatedAt*1000000) + activity.ApplicationID = temp.ApplicationID + activity.Assets = temp.Assets + activity.Details = temp.Details + activity.Emoji = temp.Emoji + activity.Flags = temp.Flags + activity.Instance = temp.Instance + activity.Name = temp.Name + activity.Party = temp.Party + activity.Secrets = temp.Secrets + activity.State = temp.State + activity.Timestamps = temp.Timestamps + activity.Type = temp.Type + activity.URL = temp.URL + return nil +} + +// Party defines the Party field in the Activity struct +// https://discord.com/developers/docs/topics/gateway#activity-object +type Party struct { + ID string `json:"id,omitempty"` + Size []int `json:"size,omitempty"` +} + +// Secrets defines the Secrets field for the Activity struct +// https://discord.com/developers/docs/topics/gateway#activity-object +type Secrets struct { + Join string `json:"join,omitempty"` + Spectate string `json:"spectate,omitempty"` + Match string `json:"match,omitempty"` +} + +// ActivityType is the type of Activity (see ActivityType* consts) in the Activity struct +// https://discord.com/developers/docs/topics/gateway#activity-object-activity-types +type ActivityType int + +// Valid ActivityType values +const ( + ActivityTypeGame ActivityType = 0 + ActivityTypeStreaming ActivityType = 1 + ActivityTypeListening ActivityType = 2 + ActivityTypeWatching ActivityType = 3 + ActivityTypeCustom ActivityType = 4 + ActivityTypeCompeting ActivityType = 5 +) + +// Identify is sent during initial handshake with the discord gateway. +// https://discord.com/developers/docs/topics/gateway#identify +type Identify struct { + Token string `json:"token"` + Properties IdentifyProperties `json:"properties"` + Compress bool `json:"compress"` + LargeThreshold int `json:"large_threshold"` + Shard *[2]int `json:"shard,omitempty"` + Presence GatewayStatusUpdate `json:"presence,omitempty"` + GuildSubscriptions bool `json:"guild_subscriptions"` + Intents Intent `json:"intents"` +} + +// IdentifyProperties contains the "properties" portion of an Identify packet +// https://discord.com/developers/docs/topics/gateway#identify-identify-connection-properties +type IdentifyProperties struct { + OS string `json:"$os"` + Browser string `json:"$browser"` + Device string `json:"$device"` + Referer string `json:"$referer"` + ReferringDomain string `json:"$referring_domain"` +} + +// Constants for the different bit offsets of text channel permissions +const ( + // Deprecated: PermissionReadMessages has been replaced with PermissionViewChannel for text and voice channels + PermissionReadMessages = 0x0000000000000400 + PermissionSendMessages = 0x0000000000000800 + PermissionSendTTSMessages = 0x0000000000001000 + PermissionManageMessages = 0x0000000000002000 + PermissionEmbedLinks = 0x0000000000004000 + PermissionAttachFiles = 0x0000000000008000 + PermissionReadMessageHistory = 0x0000000000010000 + PermissionMentionEveryone = 0x0000000000020000 + PermissionUseExternalEmojis = 0x0000000000040000 + PermissionUseSlashCommands = 0x0000000080000000 + PermissionManageThreads = 0x0000000400000000 + PermissionCreatePublicThreads = 0x0000000800000000 + PermissionCreatePrivateThreads = 0x0000001000000000 + PermissionSendMessagesInThreads = 0x0000004000000000 +) + +// Constants for the different bit offsets of voice permissions +const ( + PermissionVoicePrioritySpeaker = 0x0000000000000100 + PermissionVoiceStreamVideo = 0x0000000000000200 + PermissionVoiceConnect = 0x0000000000100000 + PermissionVoiceSpeak = 0x0000000000200000 + PermissionVoiceMuteMembers = 0x0000000000400000 + PermissionVoiceDeafenMembers = 0x0000000000800000 + PermissionVoiceMoveMembers = 0x0000000001000000 + PermissionVoiceUseVAD = 0x0000000002000000 + PermissionVoiceRequestToSpeak = 0x0000000100000000 +) + +// Constants for general management. +const ( + PermissionChangeNickname = 0x0000000004000000 + PermissionManageNicknames = 0x0000000008000000 + PermissionManageRoles = 0x0000000010000000 + PermissionManageWebhooks = 0x0000000020000000 + PermissionManageEmojis = 0x0000000040000000 +) + +// Constants for the different bit offsets of general permissions +const ( + PermissionCreateInstantInvite = 0x0000000000000001 + PermissionKickMembers = 0x0000000000000002 + PermissionBanMembers = 0x0000000000000004 + PermissionAdministrator = 0x0000000000000008 + PermissionManageChannels = 0x0000000000000010 + PermissionManageServer = 0x0000000000000020 + PermissionAddReactions = 0x0000000000000040 + PermissionViewAuditLogs = 0x0000000000000080 + PermissionViewChannel = 0x0000000000000400 + PermissionViewGuildInsights = 0x0000000000080000 + PermissionModerateMembers = 0x0000010000000000 + + PermissionAllText = PermissionViewChannel | + PermissionSendMessages | + PermissionSendTTSMessages | + PermissionManageMessages | + PermissionEmbedLinks | + PermissionAttachFiles | + PermissionReadMessageHistory | + PermissionMentionEveryone + PermissionAllVoice = PermissionViewChannel | + PermissionVoiceConnect | + PermissionVoiceSpeak | + PermissionVoiceMuteMembers | + PermissionVoiceDeafenMembers | + PermissionVoiceMoveMembers | + PermissionVoiceUseVAD | + PermissionVoicePrioritySpeaker + PermissionAllChannel = PermissionAllText | + PermissionAllVoice | + PermissionCreateInstantInvite | + PermissionManageRoles | + PermissionManageChannels | + PermissionAddReactions | + PermissionViewAuditLogs + PermissionAll = PermissionAllChannel | + PermissionKickMembers | + PermissionBanMembers | + PermissionManageServer | + PermissionAdministrator | + PermissionManageWebhooks | + PermissionManageEmojis +) + +// Block contains Discord JSON Error Response codes +const ( + ErrCodeGeneralError = 0 + + ErrCodeUnknownAccount = 10001 + ErrCodeUnknownApplication = 10002 + ErrCodeUnknownChannel = 10003 + ErrCodeUnknownGuild = 10004 + ErrCodeUnknownIntegration = 10005 + ErrCodeUnknownInvite = 10006 + ErrCodeUnknownMember = 10007 + ErrCodeUnknownMessage = 10008 + ErrCodeUnknownOverwrite = 10009 + ErrCodeUnknownProvider = 10010 + ErrCodeUnknownRole = 10011 + ErrCodeUnknownToken = 10012 + ErrCodeUnknownUser = 10013 + ErrCodeUnknownEmoji = 10014 + ErrCodeUnknownWebhook = 10015 + ErrCodeUnknownWebhookService = 10016 + ErrCodeUnknownSession = 10020 + ErrCodeUnknownBan = 10026 + ErrCodeUnknownSKU = 10027 + ErrCodeUnknownStoreListing = 10028 + ErrCodeUnknownEntitlement = 10029 + ErrCodeUnknownBuild = 10030 + ErrCodeUnknownLobby = 10031 + ErrCodeUnknownBranch = 10032 + ErrCodeUnknownStoreDirectoryLayout = 10033 + ErrCodeUnknownRedistributable = 10036 + ErrCodeUnknownGiftCode = 10038 + ErrCodeUnknownStream = 10049 + ErrCodeUnknownPremiumServerSubscribeCooldown = 10050 + ErrCodeUnknownGuildTemplate = 10057 + ErrCodeUnknownDiscoveryCategory = 10059 + ErrCodeUnknownSticker = 10060 + ErrCodeUnknownInteraction = 10062 + ErrCodeUnknownApplicationCommand = 10063 + ErrCodeUnknownApplicationCommandPermissions = 10066 + ErrCodeUnknownStageInstance = 10067 + ErrCodeUnknownGuildMemberVerificationForm = 10068 + ErrCodeUnknownGuildWelcomeScreen = 10069 + ErrCodeUnknownGuildScheduledEvent = 10070 + ErrCodeUnknownGuildScheduledEventUser = 10071 + + ErrCodeBotsCannotUseEndpoint = 20001 + ErrCodeOnlyBotsCanUseEndpoint = 20002 + ErrCodeExplicitContentCannotBeSentToTheDesiredRecipients = 20009 + ErrCodeYouAreNotAuthorizedToPerformThisActionOnThisApplication = 20012 + ErrCodeThisActionCannotBePerformedDueToSlowmodeRateLimit = 20016 + ErrCodeOnlyTheOwnerOfThisAccountCanPerformThisAction = 20018 + ErrCodeMessageCannotBeEditedDueToAnnouncementRateLimits = 20022 + ErrCodeChannelHasHitWriteRateLimit = 20028 + ErrCodeTheWriteActionYouArePerformingOnTheServerHasHitTheWriteRateLimit = 20029 + ErrCodeStageTopicContainsNotAllowedWordsForPublicStages = 20031 + ErrCodeGuildPremiumSubscriptionLevelTooLow = 20035 + + ErrCodeMaximumGuildsReached = 30001 + ErrCodeMaximumPinsReached = 30003 + ErrCodeMaximumNumberOfRecipientsReached = 30004 + ErrCodeMaximumGuildRolesReached = 30005 + ErrCodeMaximumNumberOfWebhooksReached = 30007 + ErrCodeMaximumNumberOfEmojisReached = 30008 + ErrCodeTooManyReactions = 30010 + ErrCodeMaximumNumberOfGuildChannelsReached = 30013 + ErrCodeMaximumNumberOfAttachmentsInAMessageReached = 30015 + ErrCodeMaximumNumberOfInvitesReached = 30016 + ErrCodeMaximumNumberOfAnimatedEmojisReached = 30018 + ErrCodeMaximumNumberOfServerMembersReached = 30019 + ErrCodeMaximumNumberOfGuildDiscoverySubcategoriesReached = 30030 + ErrCodeGuildAlreadyHasATemplate = 30031 + ErrCodeMaximumNumberOfThreadParticipantsReached = 30033 + ErrCodeMaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035 + ErrCodeMaximumNumberOfBansFetchesHasBeenReached = 30037 + ErrCodeMaximumNumberOfUncompletedGuildScheduledEventsReached = 30038 + ErrCodeMaximumNumberOfStickersReached = 30039 + ErrCodeMaximumNumberOfPruneRequestsHasBeenReached = 30040 + ErrCodeMaximumNumberOfGuildWidgetSettingsUpdatesHasBeenReached = 30042 + ErrCodeMaximumNumberOfEditsToMessagesOlderThanOneHourReached = 30046 + + ErrCodeUnauthorized = 40001 + ErrCodeActionRequiredVerifiedAccount = 40002 + ErrCodeOpeningDirectMessagesTooFast = 40003 + ErrCodeSendMessagesHasBeenTemporarilyDisabled = 40004 + ErrCodeRequestEntityTooLarge = 40005 + ErrCodeFeatureTemporarilyDisabledServerSide = 40006 + ErrCodeUserIsBannedFromThisGuild = 40007 + ErrCodeTargetIsNotConnectedToVoice = 40032 + ErrCodeMessageAlreadyCrossposted = 40033 + ErrCodeAnApplicationWithThatNameAlreadyExists = 40041 + ErrCodeInteractionHasAlreadyBeenAcknowledged = 40060 + + ErrCodeMissingAccess = 50001 + ErrCodeInvalidAccountType = 50002 + ErrCodeCannotExecuteActionOnDMChannel = 50003 + ErrCodeEmbedDisabled = 50004 + ErrCodeGuildWidgetDisabled = 50004 + ErrCodeCannotEditFromAnotherUser = 50005 + ErrCodeCannotSendEmptyMessage = 50006 + ErrCodeCannotSendMessagesToThisUser = 50007 + ErrCodeCannotSendMessagesInVoiceChannel = 50008 + ErrCodeChannelVerificationLevelTooHigh = 50009 + ErrCodeOAuth2ApplicationDoesNotHaveBot = 50010 + ErrCodeOAuth2ApplicationLimitReached = 50011 + ErrCodeInvalidOAuthState = 50012 + ErrCodeMissingPermissions = 50013 + ErrCodeInvalidAuthenticationToken = 50014 + ErrCodeTooFewOrTooManyMessagesToDelete = 50016 + ErrCodeCanOnlyPinMessageToOriginatingChannel = 50019 + ErrCodeInviteCodeWasEitherInvalidOrTaken = 50020 + ErrCodeCannotExecuteActionOnSystemMessage = 50021 + ErrCodeCannotExecuteActionOnThisChannelType = 50024 + ErrCodeInvalidOAuth2AccessTokenProvided = 50025 + ErrCodeMissingRequiredOAuth2Scope = 50026 + ErrCodeInvalidWebhookTokenProvided = 50027 + ErrCodeInvalidRole = 50028 + ErrCodeInvalidRecipients = 50033 + ErrCodeMessageProvidedTooOldForBulkDelete = 50034 + ErrCodeInvalidFormBody = 50035 + ErrCodeInviteAcceptedToGuildApplicationsBotNotIn = 50036 + ErrCodeInvalidAPIVersionProvided = 50041 + ErrCodeFileUploadedExceedsTheMaximumSize = 50045 + ErrCodeInvalidFileUploaded = 50046 + ErrCodeInvalidGuild = 50055 + ErrCodeInvalidMessageType = 50068 + ErrCodeCannotDeleteAChannelRequiredForCommunityGuilds = 50074 + ErrCodeInvalidStickerSent = 50081 + ErrCodePerformedOperationOnArchivedThread = 50083 + ErrCodeBeforeValueIsEarlierThanThreadCreationDate = 50085 + ErrCodeCommunityServerChannelsMustBeTextChannels = 50086 + ErrCodeThisServerIsNotAvailableInYourLocation = 50095 + ErrCodeThisServerNeedsMonetizationEnabledInOrderToPerformThisAction = 50097 + ErrCodeThisServerNeedsMoreBoostsToPerformThisAction = 50101 + ErrCodeTheRequestBodyContainsInvalidJSON = 50109 + + ErrCodeNoUsersWithDiscordTagExist = 80004 + + ErrCodeReactionBlocked = 90001 + + ErrCodeAPIResourceIsCurrentlyOverloaded = 130000 + + ErrCodeTheStageIsAlreadyOpen = 150006 + + ErrCodeCannotReplyWithoutPermissionToReadMessageHistory = 160002 + ErrCodeThreadAlreadyCreatedForThisMessage = 160004 + ErrCodeThreadIsLocked = 160005 + ErrCodeMaximumNumberOfActiveThreadsReached = 160006 + ErrCodeMaximumNumberOfActiveAnnouncementThreadsReached = 160007 + + ErrCodeInvalidJSONForUploadedLottieFile = 170001 + ErrCodeUploadedLottiesCannotContainRasterizedImages = 170002 + ErrCodeStickerMaximumFramerateExceeded = 170003 + ErrCodeStickerFrameCountExceedsMaximumOfOneThousandFrames = 170004 + ErrCodeLottieAnimationMaximumDimensionsExceeded = 170005 + ErrCodeStickerFrameRateOutOfRange = 170006 + ErrCodeStickerAnimationDurationExceedsMaximumOfFiveSeconds = 170007 + + ErrCodeCannotUpdateAFinishedEvent = 180000 + ErrCodeFailedToCreateStageNeededForStageEvent = 180002 +) + +// Intent is the type of a Gateway Intent +// https://discord.com/developers/docs/topics/gateway#gateway-intents +type Intent int + +// Constants for the different bit offsets of intents +const ( + IntentGuilds Intent = 1 << 0 + IntentGuildMembers Intent = 1 << 1 + IntentGuildBans Intent = 1 << 2 + IntentGuildEmojis Intent = 1 << 3 + IntentGuildIntegrations Intent = 1 << 4 + IntentGuildWebhooks Intent = 1 << 5 + IntentGuildInvites Intent = 1 << 6 + IntentGuildVoiceStates Intent = 1 << 7 + IntentGuildPresences Intent = 1 << 8 + IntentGuildMessages Intent = 1 << 9 + IntentGuildMessageReactions Intent = 1 << 10 + IntentGuildMessageTyping Intent = 1 << 11 + IntentDirectMessages Intent = 1 << 12 + IntentDirectMessageReactions Intent = 1 << 13 + IntentDirectMessageTyping Intent = 1 << 14 + IntentMessageContent Intent = 1 << 15 + IntentGuildScheduledEvents Intent = 1 << 16 + + // TODO: remove when compatibility is not needed + + IntentsGuilds Intent = 1 << 0 + IntentsGuildMembers Intent = 1 << 1 + IntentsGuildBans Intent = 1 << 2 + IntentsGuildEmojis Intent = 1 << 3 + IntentsGuildIntegrations Intent = 1 << 4 + IntentsGuildWebhooks Intent = 1 << 5 + IntentsGuildInvites Intent = 1 << 6 + IntentsGuildVoiceStates Intent = 1 << 7 + IntentsGuildPresences Intent = 1 << 8 + IntentsGuildMessages Intent = 1 << 9 + IntentsGuildMessageReactions Intent = 1 << 10 + IntentsGuildMessageTyping Intent = 1 << 11 + IntentsDirectMessages Intent = 1 << 12 + IntentsDirectMessageReactions Intent = 1 << 13 + IntentsDirectMessageTyping Intent = 1 << 14 + IntentsMessageContent Intent = 1 << 15 + IntentsGuildScheduledEvents Intent = 1 << 16 + + IntentsAllWithoutPrivileged = IntentGuilds | + IntentGuildBans | + IntentGuildEmojis | + IntentGuildIntegrations | + IntentGuildWebhooks | + IntentGuildInvites | + IntentGuildVoiceStates | + IntentGuildMessages | + IntentGuildMessageReactions | + IntentGuildMessageTyping | + IntentDirectMessages | + IntentDirectMessageReactions | + IntentDirectMessageTyping | + IntentGuildScheduledEvents + + IntentsAll = IntentsAllWithoutPrivileged | + IntentGuildMembers | + IntentGuildPresences | + IntentMessageContent + + IntentsNone Intent = 0 +) + +// MakeIntent used to help convert a gateway intent value for use in the Identify structure; +// this was useful to help support the use of a pointer type when intents were optional. +// This is now a no-op, and is not necessary to use. +func MakeIntent(intents Intent) Intent { + return intents +} diff --git a/vendor/github.com/bwmarrin/discordgo/types.go b/vendor/github.com/bwmarrin/discordgo/types.go new file mode 100644 index 00000000..7f969aef --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/types.go @@ -0,0 +1,47 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains custom types, currently only a timestamp wrapper. + +package discordgo + +import ( + "encoding/json" + "net/http" +) + +// RESTError stores error information about a request with a bad response code. +// Message is not always present, there are cases where api calls can fail +// without returning a json message. +type RESTError struct { + Request *http.Request + Response *http.Response + ResponseBody []byte + + Message *APIErrorMessage // Message may be nil. +} + +func newRestError(req *http.Request, resp *http.Response, body []byte) *RESTError { + restErr := &RESTError{ + Request: req, + Response: resp, + ResponseBody: body, + } + + // Attempt to decode the error and assume no message was provided if it fails + var msg *APIErrorMessage + err := json.Unmarshal(body, &msg) + if err == nil { + restErr.Message = msg + } + + return restErr +} + +func (r RESTError) Error() string { + return "HTTP " + r.Response.Status + ", " + string(r.ResponseBody) +} diff --git a/vendor/github.com/bwmarrin/discordgo/user.go b/vendor/github.com/bwmarrin/discordgo/user.go new file mode 100644 index 00000000..6c48bf25 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/user.go @@ -0,0 +1,107 @@ +package discordgo + +// UserFlags is the flags of "user" (see UserFlags* consts) +// https://discord.com/developers/docs/resources/user#user-object-user-flags +type UserFlags int + +// Valid UserFlags values +const ( + UserFlagDiscordEmployee UserFlags = 1 << 0 + UserFlagDiscordPartner UserFlags = 1 << 1 + UserFlagHypeSquadEvents UserFlags = 1 << 2 + UserFlagBugHunterLevel1 UserFlags = 1 << 3 + UserFlagHouseBravery UserFlags = 1 << 6 + UserFlagHouseBrilliance UserFlags = 1 << 7 + UserFlagHouseBalance UserFlags = 1 << 8 + UserFlagEarlySupporter UserFlags = 1 << 9 + UserFlagTeamUser UserFlags = 1 << 10 + UserFlagSystem UserFlags = 1 << 12 + UserFlagBugHunterLevel2 UserFlags = 1 << 14 + UserFlagVerifiedBot UserFlags = 1 << 16 + UserFlagVerifiedBotDeveloper UserFlags = 1 << 17 + UserFlagDiscordCertifiedModerator UserFlags = 1 << 18 +) + +// A User stores all data for an individual Discord user. +type User struct { + // The ID of the user. + ID string `json:"id"` + + // The email of the user. This is only present when + // the application possesses the email scope for the user. + Email string `json:"email"` + + // The user's username. + Username string `json:"username"` + + // The hash of the user's avatar. Use Session.UserAvatar + // to retrieve the avatar itself. + Avatar string `json:"avatar"` + + // The user's chosen language option. + Locale string `json:"locale"` + + // The discriminator of the user (4 numbers after name). + Discriminator string `json:"discriminator"` + + // The token of the user. This is only present for + // the user represented by the current session. + Token string `json:"token"` + + // Whether the user's email is verified. + Verified bool `json:"verified"` + + // Whether the user has multi-factor authentication enabled. + MFAEnabled bool `json:"mfa_enabled"` + + // The hash of the user's banner image. + Banner string `json:"banner"` + + // User's banner color, encoded as an integer representation of hexadecimal color code + AccentColor int `json:"accent_color"` + + // Whether the user is a bot. + Bot bool `json:"bot"` + + // The public flags on a user's account. + // This is a combination of bit masks; the presence of a certain flag can + // be checked by performing a bitwise AND between this int and the flag. + PublicFlags UserFlags `json:"public_flags"` + + // The type of Nitro subscription on a user's account. + // Only available when the request is authorized via a Bearer token. + PremiumType int `json:"premium_type"` + + // Whether the user is an Official Discord System user (part of the urgent message system). + System bool `json:"system"` + + // The flags on a user's account. + // Only available when the request is authorized via a Bearer token. + Flags int `json:"flags"` +} + +// String returns a unique identifier of the form username#discriminator +func (u *User) String() string { + return u.Username + "#" + u.Discriminator +} + +// Mention return a string which mentions the user +func (u *User) Mention() string { + return "<@" + u.ID + ">" +} + +// AvatarURL returns a URL to the user's avatar. +// size: The size of the user's avatar as a power of two +// if size is an empty string, no size parameter will +// be added to the URL. +func (u *User) AvatarURL(size string) string { + return avatarURL(u.Avatar, EndpointDefaultUserAvatar(u.Discriminator), + EndpointUserAvatar(u.ID, u.Avatar), EndpointUserAvatarAnimated(u.ID, u.Avatar), size) +} + +// BannerURL returns the URL of the users's banner image. +// size: The size of the desired banner image as a power of two +// Image size can be any power of two between 16 and 4096. +func (u *User) BannerURL(size string) string { + return bannerURL(u.Banner, EndpointUserBanner(u.ID, u.Banner), EndpointUserBannerAnimated(u.ID, u.Banner), size) +} diff --git a/vendor/github.com/bwmarrin/discordgo/util.go b/vendor/github.com/bwmarrin/discordgo/util.go new file mode 100644 index 00000000..62313033 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/util.go @@ -0,0 +1,110 @@ +package discordgo + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/textproto" + "strconv" + "strings" + "time" +) + +// SnowflakeTimestamp returns the creation time of a Snowflake ID relative to the creation of Discord. +func SnowflakeTimestamp(ID string) (t time.Time, err error) { + i, err := strconv.ParseInt(ID, 10, 64) + if err != nil { + return + } + timestamp := (i >> 22) + 1420070400000 + t = time.Unix(0, timestamp*1000000) + return +} + +// MultipartBodyWithJSON returns the contentType and body for a discord request +// data : The object to encode for payload_json in the multipart request +// files : Files to include in the request +func MultipartBodyWithJSON(data interface{}, files []*File) (requestContentType string, requestBody []byte, err error) { + body := &bytes.Buffer{} + bodywriter := multipart.NewWriter(body) + + payload, err := json.Marshal(data) + if err != nil { + return + } + + var p io.Writer + + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", `form-data; name="payload_json"`) + h.Set("Content-Type", "application/json") + + p, err = bodywriter.CreatePart(h) + if err != nil { + return + } + + if _, err = p.Write(payload); err != nil { + return + } + + for i, file := range files { + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file%d"; filename="%s"`, i, quoteEscaper.Replace(file.Name))) + contentType := file.ContentType + if contentType == "" { + contentType = "application/octet-stream" + } + h.Set("Content-Type", contentType) + + p, err = bodywriter.CreatePart(h) + if err != nil { + return + } + + if _, err = io.Copy(p, file.Reader); err != nil { + return + } + } + + err = bodywriter.Close() + if err != nil { + return + } + + return bodywriter.FormDataContentType(), body.Bytes(), nil +} + +func avatarURL(avatarHash, defaultAvatarURL, staticAvatarURL, animatedAvatarURL, size string) string { + var URL string + if avatarHash == "" { + URL = defaultAvatarURL + } else if strings.HasPrefix(avatarHash, "a_") { + URL = animatedAvatarURL + } else { + URL = staticAvatarURL + } + + if size != "" { + return URL + "?size=" + size + } + return URL +} + +func bannerURL(bannerHash, staticBannerURL, animatedBannerURL, size string) string { + var URL string + if bannerHash == "" { + return "" + } else if strings.HasPrefix(bannerHash, "a_") { + URL = animatedBannerURL + } else { + URL = staticBannerURL + } + + if size != "" { + return URL + "?size=" + size + } + return URL +} diff --git a/vendor/github.com/bwmarrin/discordgo/voice.go b/vendor/github.com/bwmarrin/discordgo/voice.go new file mode 100644 index 00000000..aedb8790 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/voice.go @@ -0,0 +1,914 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains code related to Discord voice suppport + +package discordgo + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "net" + "strconv" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" + "golang.org/x/crypto/nacl/secretbox" +) + +// ------------------------------------------------------------------------------------------------ +// Code related to both VoiceConnection Websocket and UDP connections. +// ------------------------------------------------------------------------------------------------ + +// A VoiceConnection struct holds all the data and functions related to a Discord Voice Connection. +type VoiceConnection struct { + sync.RWMutex + + Debug bool // If true, print extra logging -- DEPRECATED + LogLevel int + Ready bool // If true, voice is ready to send/receive audio + UserID string + GuildID string + ChannelID string + deaf bool + mute bool + speaking bool + reconnecting bool // If true, voice connection is trying to reconnect + + OpusSend chan []byte // Chan for sending opus audio + OpusRecv chan *Packet // Chan for receiving opus audio + + wsConn *websocket.Conn + wsMutex sync.Mutex + udpConn *net.UDPConn + session *Session + + sessionID string + token string + endpoint string + + // Used to send a close signal to goroutines + close chan struct{} + + // Used to allow blocking until connected + connected chan bool + + // Used to pass the sessionid from onVoiceStateUpdate + // sessionRecv chan string UNUSED ATM + + op4 voiceOP4 + op2 voiceOP2 + + voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler +} + +// VoiceSpeakingUpdateHandler type provides a function definition for the +// VoiceSpeakingUpdate event +type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate) + +// Speaking sends a speaking notification to Discord over the voice websocket. +// This must be sent as true prior to sending audio and should be set to false +// once finished sending audio. +// b : Send true if speaking, false if not. +func (v *VoiceConnection) Speaking(b bool) (err error) { + + v.log(LogDebug, "called (%t)", b) + + type voiceSpeakingData struct { + Speaking bool `json:"speaking"` + Delay int `json:"delay"` + } + + type voiceSpeakingOp struct { + Op int `json:"op"` // Always 5 + Data voiceSpeakingData `json:"d"` + } + + if v.wsConn == nil { + return fmt.Errorf("no VoiceConnection websocket") + } + + data := voiceSpeakingOp{5, voiceSpeakingData{b, 0}} + v.wsMutex.Lock() + err = v.wsConn.WriteJSON(data) + v.wsMutex.Unlock() + + v.Lock() + defer v.Unlock() + if err != nil { + v.speaking = false + v.log(LogError, "Speaking() write json error, %s", err) + return + } + + v.speaking = b + + return +} + +// ChangeChannel sends Discord a request to change channels within a Guild +// !!! NOTE !!! This function may be removed in favour of just using ChannelVoiceJoin +func (v *VoiceConnection) ChangeChannel(channelID string, mute, deaf bool) (err error) { + + v.log(LogInformational, "called") + + data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, mute, deaf}} + v.wsMutex.Lock() + err = v.session.wsConn.WriteJSON(data) + v.wsMutex.Unlock() + if err != nil { + return + } + v.ChannelID = channelID + v.deaf = deaf + v.mute = mute + v.speaking = false + + return +} + +// Disconnect disconnects from this voice channel and closes the websocket +// and udp connections to Discord. +func (v *VoiceConnection) Disconnect() (err error) { + + // Send a OP4 with a nil channel to disconnect + v.Lock() + if v.sessionID != "" { + data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} + v.session.wsMutex.Lock() + err = v.session.wsConn.WriteJSON(data) + v.session.wsMutex.Unlock() + v.sessionID = "" + } + v.Unlock() + + // Close websocket and udp connections + v.Close() + + v.log(LogInformational, "Deleting VoiceConnection %s", v.GuildID) + + v.session.Lock() + delete(v.session.VoiceConnections, v.GuildID) + v.session.Unlock() + + return +} + +// Close closes the voice ws and udp connections +func (v *VoiceConnection) Close() { + + v.log(LogInformational, "called") + + v.Lock() + defer v.Unlock() + + v.Ready = false + v.speaking = false + + if v.close != nil { + v.log(LogInformational, "closing v.close") + close(v.close) + v.close = nil + } + + if v.udpConn != nil { + v.log(LogInformational, "closing udp") + err := v.udpConn.Close() + if err != nil { + v.log(LogError, "error closing udp connection, %s", err) + } + v.udpConn = nil + } + + if v.wsConn != nil { + v.log(LogInformational, "sending close frame") + + // To cleanly close a connection, a client should send a close + // frame and wait for the server to close the connection. + v.wsMutex.Lock() + err := v.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + v.wsMutex.Unlock() + if err != nil { + v.log(LogError, "error closing websocket, %s", err) + } + + // TODO: Wait for Discord to actually close the connection. + time.Sleep(1 * time.Second) + + v.log(LogInformational, "closing websocket") + err = v.wsConn.Close() + if err != nil { + v.log(LogError, "error closing websocket, %s", err) + } + + v.wsConn = nil + } +} + +// AddHandler adds a Handler for VoiceSpeakingUpdate events. +func (v *VoiceConnection) AddHandler(h VoiceSpeakingUpdateHandler) { + v.Lock() + defer v.Unlock() + + v.voiceSpeakingUpdateHandlers = append(v.voiceSpeakingUpdateHandlers, h) +} + +// VoiceSpeakingUpdate is a struct for a VoiceSpeakingUpdate event. +type VoiceSpeakingUpdate struct { + UserID string `json:"user_id"` + SSRC int `json:"ssrc"` + Speaking bool `json:"speaking"` +} + +// ------------------------------------------------------------------------------------------------ +// Unexported Internal Functions Below. +// ------------------------------------------------------------------------------------------------ + +// A voiceOP4 stores the data for the voice operation 4 websocket event +// which provides us with the NaCl SecretBox encryption key +type voiceOP4 struct { + SecretKey [32]byte `json:"secret_key"` + Mode string `json:"mode"` +} + +// A voiceOP2 stores the data for the voice operation 2 websocket event +// which is sort of like the voice READY packet +type voiceOP2 struct { + SSRC uint32 `json:"ssrc"` + Port int `json:"port"` + Modes []string `json:"modes"` + HeartbeatInterval time.Duration `json:"heartbeat_interval"` + IP string `json:"ip"` +} + +// WaitUntilConnected waits for the Voice Connection to +// become ready, if it does not become ready it returns an err +func (v *VoiceConnection) waitUntilConnected() error { + + v.log(LogInformational, "called") + + i := 0 + for { + v.RLock() + ready := v.Ready + v.RUnlock() + if ready { + return nil + } + + if i > 10 { + return fmt.Errorf("timeout waiting for voice") + } + + time.Sleep(1 * time.Second) + i++ + } +} + +// Open opens a voice connection. This should be called +// after VoiceChannelJoin is used and the data VOICE websocket events +// are captured. +func (v *VoiceConnection) open() (err error) { + + v.log(LogInformational, "called") + + v.Lock() + defer v.Unlock() + + // Don't open a websocket if one is already open + if v.wsConn != nil { + v.log(LogWarning, "refusing to overwrite non-nil websocket") + return + } + + // TODO temp? loop to wait for the SessionID + i := 0 + for { + if v.sessionID != "" { + break + } + if i > 20 { // only loop for up to 1 second total + return fmt.Errorf("did not receive voice Session ID in time") + } + time.Sleep(50 * time.Millisecond) + i++ + } + + // Connect to VoiceConnection Websocket + vg := "wss://" + strings.TrimSuffix(v.endpoint, ":80") + v.log(LogInformational, "connecting to voice endpoint %s", vg) + v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil) + if err != nil { + v.log(LogWarning, "error connecting to voice endpoint %s, %s", vg, err) + v.log(LogDebug, "voice struct: %#v\n", v) + return + } + + type voiceHandshakeData struct { + ServerID string `json:"server_id"` + UserID string `json:"user_id"` + SessionID string `json:"session_id"` + Token string `json:"token"` + } + type voiceHandshakeOp struct { + Op int `json:"op"` // Always 0 + Data voiceHandshakeData `json:"d"` + } + data := voiceHandshakeOp{0, voiceHandshakeData{v.GuildID, v.UserID, v.sessionID, v.token}} + + err = v.wsConn.WriteJSON(data) + if err != nil { + v.log(LogWarning, "error sending init packet, %s", err) + return + } + + v.close = make(chan struct{}) + go v.wsListen(v.wsConn, v.close) + + // add loop/check for Ready bool here? + // then return false if not ready? + // but then wsListen will also err. + + return +} + +// wsListen listens on the voice websocket for messages and passes them +// to the voice event handler. This is automatically called by the Open func +func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}) { + + v.log(LogInformational, "called") + + for { + _, message, err := v.wsConn.ReadMessage() + if err != nil { + // 4014 indicates a manual disconnection by someone in the guild; + // we shouldn't reconnect. + if websocket.IsCloseError(err, 4014) { + v.log(LogInformational, "received 4014 manual disconnection") + + // Abandon the voice WS connection + v.Lock() + v.wsConn = nil + v.Unlock() + + v.session.Lock() + delete(v.session.VoiceConnections, v.GuildID) + v.session.Unlock() + + v.Close() + + return + } + + // Detect if we have been closed manually. If a Close() has already + // happened, the websocket we are listening on will be different to the + // current session. + v.RLock() + sameConnection := v.wsConn == wsConn + v.RUnlock() + if sameConnection { + + v.log(LogError, "voice endpoint %s websocket closed unexpectantly, %s", v.endpoint, err) + + // Start reconnect goroutine then exit. + go v.reconnect() + } + return + } + + // Pass received message to voice event handler + select { + case <-close: + return + default: + go v.onEvent(message) + } + } +} + +// wsEvent handles any voice websocket events. This is only called by the +// wsListen() function. +func (v *VoiceConnection) onEvent(message []byte) { + + v.log(LogDebug, "received: %s", string(message)) + + var e Event + if err := json.Unmarshal(message, &e); err != nil { + v.log(LogError, "unmarshall error, %s", err) + return + } + + switch e.Operation { + + case 2: // READY + + if err := json.Unmarshal(e.RawData, &v.op2); err != nil { + v.log(LogError, "OP2 unmarshall error, %s, %s", err, string(e.RawData)) + return + } + + // Start the voice websocket heartbeat to keep the connection alive + go v.wsHeartbeat(v.wsConn, v.close, v.op2.HeartbeatInterval) + // TODO monitor a chan/bool to verify this was successful + + // Start the UDP connection + err := v.udpOpen() + if err != nil { + v.log(LogError, "error opening udp connection, %s", err) + return + } + + // Start the opusSender. + // TODO: Should we allow 48000/960 values to be user defined? + if v.OpusSend == nil { + v.OpusSend = make(chan []byte, 2) + } + go v.opusSender(v.udpConn, v.close, v.OpusSend, 48000, 960) + + // Start the opusReceiver + if !v.deaf { + if v.OpusRecv == nil { + v.OpusRecv = make(chan *Packet, 2) + } + + go v.opusReceiver(v.udpConn, v.close, v.OpusRecv) + } + + return + + case 3: // HEARTBEAT response + // add code to use this to track latency? + return + + case 4: // udp encryption secret key + v.Lock() + defer v.Unlock() + + v.op4 = voiceOP4{} + if err := json.Unmarshal(e.RawData, &v.op4); err != nil { + v.log(LogError, "OP4 unmarshall error, %s, %s", err, string(e.RawData)) + return + } + return + + case 5: + if len(v.voiceSpeakingUpdateHandlers) == 0 { + return + } + + voiceSpeakingUpdate := &VoiceSpeakingUpdate{} + if err := json.Unmarshal(e.RawData, voiceSpeakingUpdate); err != nil { + v.log(LogError, "OP5 unmarshall error, %s, %s", err, string(e.RawData)) + return + } + + for _, h := range v.voiceSpeakingUpdateHandlers { + h(v, voiceSpeakingUpdate) + } + + default: + v.log(LogDebug, "unknown voice operation, %d, %s", e.Operation, string(e.RawData)) + } + + return +} + +type voiceHeartbeatOp struct { + Op int `json:"op"` // Always 3 + Data int `json:"d"` +} + +// NOTE :: When a guild voice server changes how do we shut this down +// properly, so a new connection can be setup without fuss? +// +// wsHeartbeat sends regular heartbeats to voice Discord so it knows the client +// is still connected. If you do not send these heartbeats Discord will +// disconnect the websocket connection after a few seconds. +func (v *VoiceConnection) wsHeartbeat(wsConn *websocket.Conn, close <-chan struct{}, i time.Duration) { + + if close == nil || wsConn == nil { + return + } + + var err error + ticker := time.NewTicker(i * time.Millisecond) + defer ticker.Stop() + for { + v.log(LogDebug, "sending heartbeat packet") + v.wsMutex.Lock() + err = wsConn.WriteJSON(voiceHeartbeatOp{3, int(time.Now().Unix())}) + v.wsMutex.Unlock() + if err != nil { + v.log(LogError, "error sending heartbeat to voice endpoint %s, %s", v.endpoint, err) + return + } + + select { + case <-ticker.C: + // continue loop and send heartbeat + case <-close: + return + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Code related to the VoiceConnection UDP connection +// ------------------------------------------------------------------------------------------------ + +type voiceUDPData struct { + Address string `json:"address"` // Public IP of machine running this code + Port uint16 `json:"port"` // UDP Port of machine running this code + Mode string `json:"mode"` // always "xsalsa20_poly1305" +} + +type voiceUDPD struct { + Protocol string `json:"protocol"` // Always "udp" ? + Data voiceUDPData `json:"data"` +} + +type voiceUDPOp struct { + Op int `json:"op"` // Always 1 + Data voiceUDPD `json:"d"` +} + +// udpOpen opens a UDP connection to the voice server and completes the +// initial required handshake. This connection is left open in the session +// and can be used to send or receive audio. This should only be called +// from voice.wsEvent OP2 +func (v *VoiceConnection) udpOpen() (err error) { + + v.Lock() + defer v.Unlock() + + if v.wsConn == nil { + return fmt.Errorf("nil voice websocket") + } + + if v.udpConn != nil { + return fmt.Errorf("udp connection already open") + } + + if v.close == nil { + return fmt.Errorf("nil close channel") + } + + if v.endpoint == "" { + return fmt.Errorf("empty endpoint") + } + + host := v.op2.IP + ":" + strconv.Itoa(v.op2.Port) + addr, err := net.ResolveUDPAddr("udp", host) + if err != nil { + v.log(LogWarning, "error resolving udp host %s, %s", host, err) + return + } + + v.log(LogInformational, "connecting to udp addr %s", addr.String()) + v.udpConn, err = net.DialUDP("udp", nil, addr) + if err != nil { + v.log(LogWarning, "error connecting to udp addr %s, %s", addr.String(), err) + return + } + + // Create a 70 byte array and put the SSRC code from the Op 2 VoiceConnection event + // into it. Then send that over the UDP connection to Discord + sb := make([]byte, 70) + binary.BigEndian.PutUint32(sb, v.op2.SSRC) + _, err = v.udpConn.Write(sb) + if err != nil { + v.log(LogWarning, "udp write error to %s, %s", addr.String(), err) + return + } + + // Create a 70 byte array and listen for the initial handshake response + // from Discord. Once we get it parse the IP and PORT information out + // of the response. This should be our public IP and PORT as Discord + // saw us. + rb := make([]byte, 70) + rlen, _, err := v.udpConn.ReadFromUDP(rb) + if err != nil { + v.log(LogWarning, "udp read error, %s, %s", addr.String(), err) + return + } + + if rlen < 70 { + v.log(LogWarning, "received udp packet too small") + return fmt.Errorf("received udp packet too small") + } + + // Loop over position 4 through 20 to grab the IP address + // Should never be beyond position 20. + var ip string + for i := 4; i < 20; i++ { + if rb[i] == 0 { + break + } + ip += string(rb[i]) + } + + // Grab port from position 68 and 69 + port := binary.BigEndian.Uint16(rb[68:70]) + + // Take the data from above and send it back to Discord to finalize + // the UDP connection handshake. + data := voiceUDPOp{1, voiceUDPD{"udp", voiceUDPData{ip, port, "xsalsa20_poly1305"}}} + + v.wsMutex.Lock() + err = v.wsConn.WriteJSON(data) + v.wsMutex.Unlock() + if err != nil { + v.log(LogWarning, "udp write error, %#v, %s", data, err) + return + } + + // start udpKeepAlive + go v.udpKeepAlive(v.udpConn, v.close, 5*time.Second) + // TODO: find a way to check that it fired off okay + + return +} + +// udpKeepAlive sends a udp packet to keep the udp connection open +// This is still a bit of a "proof of concept" +func (v *VoiceConnection) udpKeepAlive(udpConn *net.UDPConn, close <-chan struct{}, i time.Duration) { + + if udpConn == nil || close == nil { + return + } + + var err error + var sequence uint64 + + packet := make([]byte, 8) + + ticker := time.NewTicker(i) + defer ticker.Stop() + for { + + binary.LittleEndian.PutUint64(packet, sequence) + sequence++ + + _, err = udpConn.Write(packet) + if err != nil { + v.log(LogError, "write error, %s", err) + return + } + + select { + case <-ticker.C: + // continue loop and send keepalive + case <-close: + return + } + } +} + +// opusSender will listen on the given channel and send any +// pre-encoded opus audio to Discord. Supposedly. +func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}, opus <-chan []byte, rate, size int) { + + if udpConn == nil || close == nil { + return + } + + // VoiceConnection is now ready to receive audio packets + // TODO: this needs reviewed as I think there must be a better way. + v.Lock() + v.Ready = true + v.Unlock() + defer func() { + v.Lock() + v.Ready = false + v.Unlock() + }() + + var sequence uint16 + var timestamp uint32 + var recvbuf []byte + var ok bool + udpHeader := make([]byte, 12) + var nonce [24]byte + + // build the parts that don't change in the udpHeader + udpHeader[0] = 0x80 + udpHeader[1] = 0x78 + binary.BigEndian.PutUint32(udpHeader[8:], v.op2.SSRC) + + // start a send loop that loops until buf chan is closed + ticker := time.NewTicker(time.Millisecond * time.Duration(size/(rate/1000))) + defer ticker.Stop() + for { + + // Get data from chan. If chan is closed, return. + select { + case <-close: + return + case recvbuf, ok = <-opus: + if !ok { + return + } + // else, continue loop + } + + v.RLock() + speaking := v.speaking + v.RUnlock() + if !speaking { + err := v.Speaking(true) + if err != nil { + v.log(LogError, "error sending speaking packet, %s", err) + } + } + + // Add sequence and timestamp to udpPacket + binary.BigEndian.PutUint16(udpHeader[2:], sequence) + binary.BigEndian.PutUint32(udpHeader[4:], timestamp) + + // encrypt the opus data + copy(nonce[:], udpHeader) + v.RLock() + sendbuf := secretbox.Seal(udpHeader, recvbuf, &nonce, &v.op4.SecretKey) + v.RUnlock() + + // block here until we're exactly at the right time :) + // Then send rtp audio packet to Discord over UDP + select { + case <-close: + return + case <-ticker.C: + // continue + } + _, err := udpConn.Write(sendbuf) + + if err != nil { + v.log(LogError, "udp write error, %s", err) + v.log(LogDebug, "voice struct: %#v\n", v) + return + } + + if (sequence) == 0xFFFF { + sequence = 0 + } else { + sequence++ + } + + if (timestamp + uint32(size)) >= 0xFFFFFFFF { + timestamp = 0 + } else { + timestamp += uint32(size) + } + } +} + +// A Packet contains the headers and content of a received voice packet. +type Packet struct { + SSRC uint32 + Sequence uint16 + Timestamp uint32 + Type []byte + Opus []byte + PCM []int16 +} + +// opusReceiver listens on the UDP socket for incoming packets +// and sends them across the given channel +// NOTE :: This function may change names later. +func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct{}, c chan *Packet) { + + if udpConn == nil || close == nil { + return + } + + recvbuf := make([]byte, 1024) + var nonce [24]byte + + for { + rlen, err := udpConn.Read(recvbuf) + if err != nil { + // Detect if we have been closed manually. If a Close() has already + // happened, the udp connection we are listening on will be different + // to the current session. + v.RLock() + sameConnection := v.udpConn == udpConn + v.RUnlock() + if sameConnection { + + v.log(LogError, "udp read error, %s, %s", v.endpoint, err) + v.log(LogDebug, "voice struct: %#v\n", v) + + go v.reconnect() + } + return + } + + select { + case <-close: + return + default: + // continue loop + } + + // For now, skip anything except audio. + if rlen < 12 || (recvbuf[0] != 0x80 && recvbuf[0] != 0x90) { + continue + } + + // build a audio packet struct + p := Packet{} + p.Type = recvbuf[0:2] + p.Sequence = binary.BigEndian.Uint16(recvbuf[2:4]) + p.Timestamp = binary.BigEndian.Uint32(recvbuf[4:8]) + p.SSRC = binary.BigEndian.Uint32(recvbuf[8:12]) + // decrypt opus data + copy(nonce[:], recvbuf[0:12]) + p.Opus, _ = secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey) + + // extension bit set, and not a RTCP packet + if ((recvbuf[0] & 0x10) == 0x10) && ((recvbuf[1] & 0x80) == 0) { + // get extended header length + extlen := binary.BigEndian.Uint16(p.Opus[2:4]) + // 4 bytes (ext header header) + 4*extlen (ext header data) + shift := int(4 + 4*extlen) + if len(p.Opus) > shift { + p.Opus = p.Opus[shift:] + } + } + + if c != nil { + select { + case c <- &p: + case <-close: + return + } + } + } +} + +// Reconnect will close down a voice connection then immediately try to +// reconnect to that session. +// NOTE : This func is messy and a WIP while I find what works. +// It will be cleaned up once a proven stable option is flushed out. +// aka: this is ugly shit code, please don't judge too harshly. +func (v *VoiceConnection) reconnect() { + + v.log(LogInformational, "called") + + v.Lock() + if v.reconnecting { + v.log(LogInformational, "already reconnecting to channel %s, exiting", v.ChannelID) + v.Unlock() + return + } + v.reconnecting = true + v.Unlock() + + defer func() { v.reconnecting = false }() + + // Close any currently open connections + v.Close() + + wait := time.Duration(1) + for { + + <-time.After(wait * time.Second) + wait *= 2 + if wait > 600 { + wait = 600 + } + + if v.session.DataReady == false || v.session.wsConn == nil { + v.log(LogInformational, "cannot reconnect to channel %s with unready session", v.ChannelID) + continue + } + + v.log(LogInformational, "trying to reconnect to channel %s", v.ChannelID) + + _, err := v.session.ChannelVoiceJoin(v.GuildID, v.ChannelID, v.mute, v.deaf) + if err == nil { + v.log(LogInformational, "successfully reconnected to channel %s", v.ChannelID) + return + } + + v.log(LogInformational, "error reconnecting to channel %s, %s", v.ChannelID, err) + + // if the reconnect above didn't work lets just send a disconnect + // packet to reset things. + // Send a OP4 with a nil channel to disconnect + data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} + v.session.wsMutex.Lock() + err = v.session.wsConn.WriteJSON(data) + v.session.wsMutex.Unlock() + if err != nil { + v.log(LogError, "error sending disconnect packet, %s", err) + } + + } +} diff --git a/vendor/github.com/bwmarrin/discordgo/webhook.go b/vendor/github.com/bwmarrin/discordgo/webhook.go new file mode 100644 index 00000000..f54a45ce --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/webhook.go @@ -0,0 +1,49 @@ +package discordgo + +// Webhook stores the data for a webhook. +type Webhook struct { + ID string `json:"id"` + Type WebhookType `json:"type"` + GuildID string `json:"guild_id"` + ChannelID string `json:"channel_id"` + User *User `json:"user"` + Name string `json:"name"` + Avatar string `json:"avatar"` + Token string `json:"token"` + + // ApplicationID is the bot/OAuth2 application that created this webhook + ApplicationID string `json:"application_id,omitempty"` +} + +// WebhookType is the type of Webhook (see WebhookType* consts) in the Webhook struct +// https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types +type WebhookType int + +// Valid WebhookType values +const ( + WebhookTypeIncoming WebhookType = 1 + WebhookTypeChannelFollower WebhookType = 2 +) + +// WebhookParams is a struct for webhook params, used in the WebhookExecute command. +type WebhookParams struct { + Content string `json:"content,omitempty"` + Username string `json:"username,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + TTS bool `json:"tts,omitempty"` + Files []*File `json:"-"` + Components []MessageComponent `json:"components"` + Embeds []*MessageEmbed `json:"embeds,omitempty"` + AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` + // NOTE: Works only for followup messages. + Flags uint64 `json:"flags,omitempty"` +} + +// WebhookEdit stores data for editing of a webhook message. +type WebhookEdit struct { + Content string `json:"content,omitempty"` + Components []MessageComponent `json:"components"` + Embeds []*MessageEmbed `json:"embeds,omitempty"` + Files []*File `json:"-"` + AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` +} diff --git a/vendor/github.com/bwmarrin/discordgo/wsapi.go b/vendor/github.com/bwmarrin/discordgo/wsapi.go new file mode 100644 index 00000000..f2c228d5 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/wsapi.go @@ -0,0 +1,912 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains low level functions for interacting with the Discord +// data websocket interface. + +package discordgo + +import ( + "bytes" + "compress/zlib" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "sync/atomic" + "time" + + "github.com/gorilla/websocket" +) + +// ErrWSAlreadyOpen is thrown when you attempt to open +// a websocket that already is open. +var ErrWSAlreadyOpen = errors.New("web socket already opened") + +// ErrWSNotFound is thrown when you attempt to use a websocket +// that doesn't exist +var ErrWSNotFound = errors.New("no websocket connection exists") + +// ErrWSShardBounds is thrown when you try to use a shard ID that is +// more than the total shard count +var ErrWSShardBounds = errors.New("ShardID must be less than ShardCount") + +type resumePacket struct { + Op int `json:"op"` + Data struct { + Token string `json:"token"` + SessionID string `json:"session_id"` + Sequence int64 `json:"seq"` + } `json:"d"` +} + +// Open creates a websocket connection to Discord. +// See: https://discord.com/developers/docs/topics/gateway#connecting +func (s *Session) Open() error { + s.log(LogInformational, "called") + + var err error + + // Prevent Open or other major Session functions from + // being called while Open is still running. + s.Lock() + defer s.Unlock() + + // If the websock is already open, bail out here. + if s.wsConn != nil { + return ErrWSAlreadyOpen + } + + // Get the gateway to use for the Websocket connection + if s.gateway == "" { + s.gateway, err = s.Gateway() + if err != nil { + return err + } + + // Add the version and encoding to the URL + s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json" + } + + // Connect to the Gateway + s.log(LogInformational, "connecting to gateway %s", s.gateway) + header := http.Header{} + header.Add("accept-encoding", "zlib") + s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header) + if err != nil { + s.log(LogError, "error connecting to gateway %s, %s", s.gateway, err) + s.gateway = "" // clear cached gateway + s.wsConn = nil // Just to be safe. + return err + } + + s.wsConn.SetCloseHandler(func(code int, text string) error { + return nil + }) + + defer func() { + // because of this, all code below must set err to the error + // when exiting with an error :) Maybe someone has a better + // way :) + if err != nil { + s.wsConn.Close() + s.wsConn = nil + } + }() + + // The first response from Discord should be an Op 10 (Hello) Packet. + // When processed by onEvent the heartbeat goroutine will be started. + mt, m, err := s.wsConn.ReadMessage() + if err != nil { + return err + } + e, err := s.onEvent(mt, m) + if err != nil { + return err + } + if e.Operation != 10 { + err = fmt.Errorf("expecting Op 10, got Op %d instead", e.Operation) + return err + } + s.log(LogInformational, "Op 10 Hello Packet received from Discord") + s.LastHeartbeatAck = time.Now().UTC() + var h helloOp + if err = json.Unmarshal(e.RawData, &h); err != nil { + err = fmt.Errorf("error unmarshalling helloOp, %s", err) + return err + } + + // Now we send either an Op 2 Identity if this is a brand new + // connection or Op 6 Resume if we are resuming an existing connection. + sequence := atomic.LoadInt64(s.sequence) + if s.sessionID == "" && sequence == 0 { + + // Send Op 2 Identity Packet + err = s.identify() + if err != nil { + err = fmt.Errorf("error sending identify packet to gateway, %s, %s", s.gateway, err) + return err + } + + } else { + + // Send Op 6 Resume Packet + p := resumePacket{} + p.Op = 6 + p.Data.Token = s.Token + p.Data.SessionID = s.sessionID + p.Data.Sequence = sequence + + s.log(LogInformational, "sending resume packet to gateway") + s.wsMutex.Lock() + err = s.wsConn.WriteJSON(p) + s.wsMutex.Unlock() + if err != nil { + err = fmt.Errorf("error sending gateway resume packet, %s, %s", s.gateway, err) + return err + } + + } + + // A basic state is a hard requirement for Voice. + // We create it here so the below READY/RESUMED packet can populate + // the state :) + // XXX: Move to New() func? + if s.State == nil { + state := NewState() + state.TrackChannels = false + state.TrackEmojis = false + state.TrackMembers = false + state.TrackRoles = false + state.TrackVoice = false + s.State = state + } + + // Now Discord should send us a READY or RESUMED packet. + mt, m, err = s.wsConn.ReadMessage() + if err != nil { + return err + } + e, err = s.onEvent(mt, m) + if err != nil { + return err + } + if e.Type != `READY` && e.Type != `RESUMED` { + // This is not fatal, but it does not follow their API documentation. + s.log(LogWarning, "Expected READY/RESUMED, instead got:\n%#v\n", e) + } + s.log(LogInformational, "First Packet:\n%#v\n", e) + + s.log(LogInformational, "We are now connected to Discord, emitting connect event") + s.handleEvent(connectEventType, &Connect{}) + + // A VoiceConnections map is a hard requirement for Voice. + // XXX: can this be moved to when opening a voice connection? + if s.VoiceConnections == nil { + s.log(LogInformational, "creating new VoiceConnections map") + s.VoiceConnections = make(map[string]*VoiceConnection) + } + + // Create listening chan outside of listen, as it needs to happen inside the + // mutex lock and needs to exist before calling heartbeat and listen + // go rountines. + s.listening = make(chan interface{}) + + // Start sending heartbeats and reading messages from Discord. + go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval) + go s.listen(s.wsConn, s.listening) + + s.log(LogInformational, "exiting") + return nil +} + +// listen polls the websocket connection for events, it will stop when the +// listening channel is closed, or an error occurs. +func (s *Session) listen(wsConn *websocket.Conn, listening <-chan interface{}) { + + s.log(LogInformational, "called") + + for { + + messageType, message, err := wsConn.ReadMessage() + + if err != nil { + + // Detect if we have been closed manually. If a Close() has already + // happened, the websocket we are listening on will be different to + // the current session. + s.RLock() + sameConnection := s.wsConn == wsConn + s.RUnlock() + + if sameConnection { + + s.log(LogWarning, "error reading from gateway %s websocket, %s", s.gateway, err) + // There has been an error reading, close the websocket so that + // OnDisconnect event is emitted. + err := s.Close() + if err != nil { + s.log(LogWarning, "error closing session connection, %s", err) + } + + s.log(LogInformational, "calling reconnect() now") + s.reconnect() + } + + return + } + + select { + + case <-listening: + return + + default: + s.onEvent(messageType, message) + + } + } +} + +type heartbeatOp struct { + Op int `json:"op"` + Data int64 `json:"d"` +} + +type helloOp struct { + HeartbeatInterval time.Duration `json:"heartbeat_interval"` +} + +// FailedHeartbeatAcks is the Number of heartbeat intervals to wait until forcing a connection restart. +const FailedHeartbeatAcks time.Duration = 5 * time.Millisecond + +// HeartbeatLatency returns the latency between heartbeat acknowledgement and heartbeat send. +func (s *Session) HeartbeatLatency() time.Duration { + + return s.LastHeartbeatAck.Sub(s.LastHeartbeatSent) + +} + +// heartbeat sends regular heartbeats to Discord so it knows the client +// is still connected. If you do not send these heartbeats Discord will +// disconnect the websocket connection after a few seconds. +func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}, heartbeatIntervalMsec time.Duration) { + + s.log(LogInformational, "called") + + if listening == nil || wsConn == nil { + return + } + + var err error + ticker := time.NewTicker(heartbeatIntervalMsec * time.Millisecond) + defer ticker.Stop() + + for { + s.RLock() + last := s.LastHeartbeatAck + s.RUnlock() + sequence := atomic.LoadInt64(s.sequence) + s.log(LogDebug, "sending gateway websocket heartbeat seq %d", sequence) + s.wsMutex.Lock() + s.LastHeartbeatSent = time.Now().UTC() + err = wsConn.WriteJSON(heartbeatOp{1, sequence}) + s.wsMutex.Unlock() + if err != nil || time.Now().UTC().Sub(last) > (heartbeatIntervalMsec*FailedHeartbeatAcks) { + if err != nil { + s.log(LogError, "error sending heartbeat to gateway %s, %s", s.gateway, err) + } else { + s.log(LogError, "haven't gotten a heartbeat ACK in %v, triggering a reconnection", time.Now().UTC().Sub(last)) + } + s.Close() + s.reconnect() + return + } + s.Lock() + s.DataReady = true + s.Unlock() + + select { + case <-ticker.C: + // continue loop and send heartbeat + case <-listening: + return + } + } +} + +// UpdateStatusData ia provided to UpdateStatusComplex() +type UpdateStatusData struct { + IdleSince *int `json:"since"` + Activities []*Activity `json:"activities"` + AFK bool `json:"afk"` + Status string `json:"status"` +} + +type updateStatusOp struct { + Op int `json:"op"` + Data UpdateStatusData `json:"d"` +} + +func newUpdateStatusData(idle int, activityType ActivityType, name, url string) *UpdateStatusData { + usd := &UpdateStatusData{ + Status: "online", + } + + if idle > 0 { + usd.IdleSince = &idle + } + + if name != "" { + usd.Activities = []*Activity{{ + Name: name, + Type: activityType, + URL: url, + }} + } + + return usd +} + +// UpdateGameStatus is used to update the user's status. +// If idle>0 then set status to idle. +// If name!="" then set game. +// if otherwise, set status to active, and no activity. +func (s *Session) UpdateGameStatus(idle int, name string) (err error) { + return s.UpdateStatusComplex(*newUpdateStatusData(idle, ActivityTypeGame, name, "")) +} + +// UpdateStreamingStatus is used to update the user's streaming status. +// If idle>0 then set status to idle. +// If name!="" then set game. +// If name!="" and url!="" then set the status type to streaming with the URL set. +// if otherwise, set status to active, and no game. +func (s *Session) UpdateStreamingStatus(idle int, name string, url string) (err error) { + gameType := ActivityTypeGame + if url != "" { + gameType = ActivityTypeStreaming + } + return s.UpdateStatusComplex(*newUpdateStatusData(idle, gameType, name, url)) +} + +// UpdateListeningStatus is used to set the user to "Listening to..." +// If name!="" then set to what user is listening to +// Else, set user to active and no activity. +func (s *Session) UpdateListeningStatus(name string) (err error) { + return s.UpdateStatusComplex(*newUpdateStatusData(0, ActivityTypeListening, name, "")) +} + +// UpdateStatusComplex allows for sending the raw status update data untouched by discordgo. +func (s *Session) UpdateStatusComplex(usd UpdateStatusData) (err error) { + // The comment does say "untouched by discordgo", but we might need to lie a bit here. + // The Discord documentation lists `activities` as being nullable, but in practice this + // doesn't seem to be the case. I had filed an issue about this at + // https://github.com/discord/discord-api-docs/issues/2559, but as of writing this + // haven't had any movement on it, so at this point I'm assuming this is an error, + // and am fixing this bug accordingly. Because sending `null` for `activities` instantly + // disconnects us, I think that disallowing it from being sent in `UpdateStatusComplex` + // isn't that big of an issue. + if usd.Activities == nil { + usd.Activities = make([]*Activity, 0) + } + + s.RLock() + defer s.RUnlock() + if s.wsConn == nil { + return ErrWSNotFound + } + + s.wsMutex.Lock() + err = s.wsConn.WriteJSON(updateStatusOp{3, usd}) + s.wsMutex.Unlock() + + return +} + +type requestGuildMembersData struct { + GuildIDs []string `json:"guild_id"` + Query string `json:"query"` + Limit int `json:"limit"` + Presences bool `json:"presences"` +} + +type requestGuildMembersOp struct { + Op int `json:"op"` + Data requestGuildMembersData `json:"d"` +} + +// RequestGuildMembers requests guild members from the gateway +// The gateway responds with GuildMembersChunk events +// guildID : Single Guild ID to request members of +// query : String that username starts with, leave empty to return all members +// limit : Max number of items to return, or 0 to request all members matched +// presences : Whether to request presences of guild members +func (s *Session) RequestGuildMembers(guildID string, query string, limit int, presences bool) (err error) { + data := requestGuildMembersData{ + GuildIDs: []string{guildID}, + Query: query, + Limit: limit, + Presences: presences, + } + err = s.requestGuildMembers(data) + return +} + +// RequestGuildMembersBatch requests guild members from the gateway +// The gateway responds with GuildMembersChunk events +// guildID : Slice of guild IDs to request members of +// query : String that username starts with, leave empty to return all members +// limit : Max number of items to return, or 0 to request all members matched +// presences : Whether to request presences of guild members +func (s *Session) RequestGuildMembersBatch(guildIDs []string, query string, limit int, presences bool) (err error) { + data := requestGuildMembersData{ + GuildIDs: guildIDs, + Query: query, + Limit: limit, + Presences: presences, + } + err = s.requestGuildMembers(data) + return +} + +func (s *Session) requestGuildMembers(data requestGuildMembersData) (err error) { + s.log(LogInformational, "called") + + s.RLock() + defer s.RUnlock() + if s.wsConn == nil { + return ErrWSNotFound + } + + s.wsMutex.Lock() + err = s.wsConn.WriteJSON(requestGuildMembersOp{8, data}) + s.wsMutex.Unlock() + + return +} + +// onEvent is the "event handler" for all messages received on the +// Discord Gateway API websocket connection. +// +// If you use the AddHandler() function to register a handler for a +// specific event this function will pass the event along to that handler. +// +// If you use the AddHandler() function to register a handler for the +// "OnEvent" event then all events will be passed to that handler. +func (s *Session) onEvent(messageType int, message []byte) (*Event, error) { + + var err error + var reader io.Reader + reader = bytes.NewBuffer(message) + + // If this is a compressed message, uncompress it. + if messageType == websocket.BinaryMessage { + + z, err2 := zlib.NewReader(reader) + if err2 != nil { + s.log(LogError, "error uncompressing websocket message, %s", err) + return nil, err2 + } + + defer func() { + err3 := z.Close() + if err3 != nil { + s.log(LogWarning, "error closing zlib, %s", err) + } + }() + + reader = z + } + + // Decode the event into an Event struct. + var e *Event + decoder := json.NewDecoder(reader) + if err = decoder.Decode(&e); err != nil { + s.log(LogError, "error decoding websocket message, %s", err) + return e, err + } + + s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s\n\n", e.Operation, e.Sequence, e.Type, string(e.RawData)) + + // Ping request. + // Must respond with a heartbeat packet within 5 seconds + if e.Operation == 1 { + s.log(LogInformational, "sending heartbeat in response to Op1") + s.wsMutex.Lock() + err = s.wsConn.WriteJSON(heartbeatOp{1, atomic.LoadInt64(s.sequence)}) + s.wsMutex.Unlock() + if err != nil { + s.log(LogError, "error sending heartbeat in response to Op1") + return e, err + } + + return e, nil + } + + // Reconnect + // Must immediately disconnect from gateway and reconnect to new gateway. + if e.Operation == 7 { + s.log(LogInformational, "Closing and reconnecting in response to Op7") + s.CloseWithCode(websocket.CloseServiceRestart) + s.reconnect() + return e, nil + } + + // Invalid Session + // Must respond with a Identify packet. + if e.Operation == 9 { + + s.log(LogInformational, "sending identify packet to gateway in response to Op9") + + err = s.identify() + if err != nil { + s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err) + return e, err + } + + return e, nil + } + + if e.Operation == 10 { + // Op10 is handled by Open() + return e, nil + } + + if e.Operation == 11 { + s.Lock() + s.LastHeartbeatAck = time.Now().UTC() + s.Unlock() + s.log(LogDebug, "got heartbeat ACK") + return e, nil + } + + // Do not try to Dispatch a non-Dispatch Message + if e.Operation != 0 { + // But we probably should be doing something with them. + // TEMP + s.log(LogWarning, "unknown Op: %d, Seq: %d, Type: %s, Data: %s, message: %s", e.Operation, e.Sequence, e.Type, string(e.RawData), string(message)) + return e, nil + } + + // Store the message sequence + atomic.StoreInt64(s.sequence, e.Sequence) + + // Map event to registered event handlers and pass it along to any registered handlers. + if eh, ok := registeredInterfaceProviders[e.Type]; ok { + e.Struct = eh.New() + + // Attempt to unmarshal our event. + if err = json.Unmarshal(e.RawData, e.Struct); err != nil { + s.log(LogError, "error unmarshalling %s event, %s", e.Type, err) + } + + // Send event to any registered event handlers for it's type. + // Because the above doesn't cancel this, in case of an error + // the struct could be partially populated or at default values. + // However, most errors are due to a single field and I feel + // it's better to pass along what we received than nothing at all. + // TODO: Think about that decision :) + // Either way, READY events must fire, even with errors. + s.handleEvent(e.Type, e.Struct) + } else { + s.log(LogWarning, "unknown event: Op: %d, Seq: %d, Type: %s, Data: %s", e.Operation, e.Sequence, e.Type, string(e.RawData)) + } + + // For legacy reasons, we send the raw event also, this could be useful for handling unknown events. + s.handleEvent(eventEventType, e) + + return e, nil +} + +// ------------------------------------------------------------------------------------------------ +// Code related to voice connections that initiate over the data websocket +// ------------------------------------------------------------------------------------------------ + +type voiceChannelJoinData struct { + GuildID *string `json:"guild_id"` + ChannelID *string `json:"channel_id"` + SelfMute bool `json:"self_mute"` + SelfDeaf bool `json:"self_deaf"` +} + +type voiceChannelJoinOp struct { + Op int `json:"op"` + Data voiceChannelJoinData `json:"d"` +} + +// ChannelVoiceJoin joins the session user to a voice channel. +// +// gID : Guild ID of the channel to join. +// cID : Channel ID of the channel to join. +// mute : If true, you will be set to muted upon joining. +// deaf : If true, you will be set to deafened upon joining. +func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *VoiceConnection, err error) { + + s.log(LogInformational, "called") + + s.RLock() + voice, _ = s.VoiceConnections[gID] + s.RUnlock() + + if voice == nil { + voice = &VoiceConnection{} + s.Lock() + s.VoiceConnections[gID] = voice + s.Unlock() + } + + voice.Lock() + voice.GuildID = gID + voice.ChannelID = cID + voice.deaf = deaf + voice.mute = mute + voice.session = s + voice.Unlock() + + err = s.ChannelVoiceJoinManual(gID, cID, mute, deaf) + if err != nil { + return + } + + // doesn't exactly work perfect yet.. TODO + err = voice.waitUntilConnected() + if err != nil { + s.log(LogWarning, "error waiting for voice to connect, %s", err) + voice.Close() + return + } + + return +} + +// ChannelVoiceJoinManual initiates a voice session to a voice channel, but does not complete it. +// +// This should only be used when the VoiceServerUpdate will be intercepted and used elsewhere. +// +// gID : Guild ID of the channel to join. +// cID : Channel ID of the channel to join, leave empty to disconnect. +// mute : If true, you will be set to muted upon joining. +// deaf : If true, you will be set to deafened upon joining. +func (s *Session) ChannelVoiceJoinManual(gID, cID string, mute, deaf bool) (err error) { + + s.log(LogInformational, "called") + + var channelID *string + if cID == "" { + channelID = nil + } else { + channelID = &cID + } + + // Send the request to Discord that we want to join the voice channel + data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, channelID, mute, deaf}} + s.wsMutex.Lock() + err = s.wsConn.WriteJSON(data) + s.wsMutex.Unlock() + return +} + +// onVoiceStateUpdate handles Voice State Update events on the data websocket. +func (s *Session) onVoiceStateUpdate(st *VoiceStateUpdate) { + + // If we don't have a connection for the channel, don't bother + if st.ChannelID == "" { + return + } + + // Check if we have a voice connection to update + s.RLock() + voice, exists := s.VoiceConnections[st.GuildID] + s.RUnlock() + if !exists { + return + } + + // We only care about events that are about us. + if s.State.User.ID != st.UserID { + return + } + + // Store the SessionID for later use. + voice.Lock() + voice.UserID = st.UserID + voice.sessionID = st.SessionID + voice.ChannelID = st.ChannelID + voice.Unlock() +} + +// onVoiceServerUpdate handles the Voice Server Update data websocket event. +// +// This is also fired if the Guild's voice region changes while connected +// to a voice channel. In that case, need to re-establish connection to +// the new region endpoint. +func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) { + + s.log(LogInformational, "called") + + s.RLock() + voice, exists := s.VoiceConnections[st.GuildID] + s.RUnlock() + + // If no VoiceConnection exists, just skip this + if !exists { + return + } + + // If currently connected to voice ws/udp, then disconnect. + // Has no effect if not connected. + voice.Close() + + // Store values for later use + voice.Lock() + voice.token = st.Token + voice.endpoint = st.Endpoint + voice.GuildID = st.GuildID + voice.Unlock() + + // Open a connection to the voice server + err := voice.open() + if err != nil { + s.log(LogError, "onVoiceServerUpdate voice.open, %s", err) + } +} + +type identifyOp struct { + Op int `json:"op"` + Data Identify `json:"d"` +} + +// identify sends the identify packet to the gateway +func (s *Session) identify() error { + s.log(LogDebug, "called") + + // TODO: This is a temporary block of code to help + // maintain backwards compatibility + if s.Compress == false { + s.Identify.Compress = false + } + + // TODO: This is a temporary block of code to help + // maintain backwards compatibility + if s.Token != "" && s.Identify.Token == "" { + s.Identify.Token = s.Token + } + + // TODO: Below block should be refactored so ShardID and ShardCount + // can be deprecated and their usage moved to the Session.Identify + // struct + if s.ShardCount > 1 { + + if s.ShardID >= s.ShardCount { + return ErrWSShardBounds + } + + s.Identify.Shard = &[2]int{s.ShardID, s.ShardCount} + } + + // Send Identify packet to Discord + op := identifyOp{2, s.Identify} + s.log(LogDebug, "Identify Packet: \n%#v", op) + s.wsMutex.Lock() + err := s.wsConn.WriteJSON(op) + s.wsMutex.Unlock() + + return err +} + +func (s *Session) reconnect() { + + s.log(LogInformational, "called") + + var err error + + if s.ShouldReconnectOnError { + + wait := time.Duration(1) + + for { + s.log(LogInformational, "trying to reconnect to gateway") + + err = s.Open() + if err == nil { + s.log(LogInformational, "successfully reconnected to gateway") + + // I'm not sure if this is actually needed. + // if the gw reconnect works properly, voice should stay alive + // However, there seems to be cases where something "weird" + // happens. So we're doing this for now just to improve + // stability in those edge cases. + s.RLock() + defer s.RUnlock() + for _, v := range s.VoiceConnections { + + s.log(LogInformational, "reconnecting voice connection to guild %s", v.GuildID) + go v.reconnect() + + // This is here just to prevent violently spamming the + // voice reconnects + time.Sleep(1 * time.Second) + + } + return + } + + // Certain race conditions can call reconnect() twice. If this happens, we + // just break out of the reconnect loop + if err == ErrWSAlreadyOpen { + s.log(LogInformational, "Websocket already exists, no need to reconnect") + return + } + + s.log(LogError, "error reconnecting to gateway, %s", err) + + <-time.After(wait * time.Second) + wait *= 2 + if wait > 600 { + wait = 600 + } + } + } +} + +// Close closes a websocket and stops all listening/heartbeat goroutines. +// TODO: Add support for Voice WS/UDP +func (s *Session) Close() error { + return s.CloseWithCode(websocket.CloseNormalClosure) +} + +// CloseWithCode closes a websocket using the provided closeCode and stops all +// listening/heartbeat goroutines. +// TODO: Add support for Voice WS/UDP connections +func (s *Session) CloseWithCode(closeCode int) (err error) { + + s.log(LogInformational, "called") + s.Lock() + + s.DataReady = false + + if s.listening != nil { + s.log(LogInformational, "closing listening channel") + close(s.listening) + s.listening = nil + } + + // TODO: Close all active Voice Connections too + // this should force stop any reconnecting voice channels too + + if s.wsConn != nil { + + s.log(LogInformational, "sending close frame") + // To cleanly close a connection, a client should send a close + // frame and wait for the server to close the connection. + s.wsMutex.Lock() + err := s.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(closeCode, "")) + s.wsMutex.Unlock() + if err != nil { + s.log(LogInformational, "error closing websocket, %s", err) + } + + // TODO: Wait for Discord to actually close the connection. + time.Sleep(1 * time.Second) + + s.log(LogInformational, "closing gateway websocket") + err = s.wsConn.Close() + if err != nil { + s.log(LogInformational, "error closing websocket, %s", err) + } + + s.wsConn = nil + } + + s.Unlock() + + s.log(LogInformational, "emit disconnect event") + s.handleEvent(disconnectEventType, &Disconnect{}) + + return +} diff --git a/vendor/github.com/matterbridge/discordgo/.gitignore b/vendor/github.com/matterbridge/discordgo/.gitignore deleted file mode 100644 index 34d2efa5..00000000 --- a/vendor/github.com/matterbridge/discordgo/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# IDE-specific metadata -.idea/ diff --git a/vendor/github.com/matterbridge/discordgo/.travis.yml b/vendor/github.com/matterbridge/discordgo/.travis.yml deleted file mode 100644 index b54331b1..00000000 --- a/vendor/github.com/matterbridge/discordgo/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: go -go: - - 1.13.x - - 1.14.x - - 1.15.x -env: - - GO111MODULE=on -install: - - go get github.com/bwmarrin/discordgo - - go get -v . - - go get -v golang.org/x/lint/golint -script: - - diff <(gofmt -d .) <(echo -n) - - go vet -x ./... - - golint -set_exit_status ./... - - go test -v -race ./... diff --git a/vendor/github.com/matterbridge/discordgo/LICENSE b/vendor/github.com/matterbridge/discordgo/LICENSE deleted file mode 100644 index 8d062ea5..00000000 --- a/vendor/github.com/matterbridge/discordgo/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2015, Bruce Marriner -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of discordgo nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/vendor/github.com/matterbridge/discordgo/README.md b/vendor/github.com/matterbridge/discordgo/README.md deleted file mode 100644 index 289e1622..00000000 --- a/vendor/github.com/matterbridge/discordgo/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# DiscordGo - -[![GoDoc](https://godoc.org/github.com/bwmarrin/discordgo?status.svg)](https://godoc.org/github.com/bwmarrin/discordgo) [![Go report](http://goreportcard.com/badge/bwmarrin/discordgo)](http://goreportcard.com/report/bwmarrin/discordgo) [![Build Status](https://travis-ci.org/bwmarrin/discordgo.svg?branch=master)](https://travis-ci.org/bwmarrin/discordgo) [![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23discordgo-blue.svg)](https://discord.gg/0f1SbxBZjYoCtNPP) [![Discord API](https://img.shields.io/badge/Discord%20API-%23go_discordgo-blue.svg)](https://discord.com/invite/discord-api) - - - -DiscordGo is a [Go](https://golang.org/) package that provides low level -bindings to the [Discord](https://discord.com/) chat client API. DiscordGo -has nearly complete support for all of the Discord API endpoints, websocket -interface, and voice interface. - -If you would like to help the DiscordGo package please use -[this link](https://discord.com/oauth2/authorize?client_id=173113690092994561&scope=bot) -to add the official DiscordGo test bot **dgo** to your server. This provides -indispensable help to this project. - -* See [dgVoice](https://github.com/bwmarrin/dgvoice) package for an example of -additional voice helper functions and features for DiscordGo. - -* See [dca](https://github.com/bwmarrin/dca) for an **experimental** stand alone -tool that wraps `ffmpeg` to create opus encoded audio appropriate for use with -Discord (and DiscordGo). - -**For help with this package or general Go discussion, please join the [Discord -Gophers](https://discord.gg/0f1SbxBZjYq9jLBk) chat server.** - -## Getting Started - -### Installing - -This assumes you already have a working Go environment, if not please see -[this page](https://golang.org/doc/install) first. - -`go get` *will always pull the latest tagged release from the master branch.* - -```sh -go get github.com/bwmarrin/discordgo -``` - -### Usage - -Import the package into your project. - -```go -import "github.com/bwmarrin/discordgo" -``` - -Construct a new Discord client which can be used to access the variety of -Discord API functions and to set callback functions for Discord events. - -```go -discord, err := discordgo.New("Bot " + "authentication token") -``` - -See Documentation and Examples below for more detailed information. - - -## Documentation - -**NOTICE**: This library and the Discord API are unfinished. -Because of that there may be major changes to library in the future. - -The DiscordGo code is fairly well documented at this point and is currently -the only documentation available. Both GoDoc and GoWalker (below) present -that information in a nice format. - -- [![GoDoc](https://godoc.org/github.com/bwmarrin/discordgo?status.svg)](https://godoc.org/github.com/bwmarrin/discordgo) -- [![Go Walker](http://gowalker.org/api/v1/badge)](https://gowalker.org/github.com/bwmarrin/discordgo) -- Hand crafted documentation coming eventually. - - -## Examples - -Below is a list of examples and other projects using DiscordGo. Please submit -an issue if you would like your project added or removed from this list. - -- [DiscordGo Examples](https://github.com/bwmarrin/discordgo/tree/master/examples) - A collection of example programs written with DiscordGo -- [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) - A curated list of high quality projects using DiscordGo - -## Troubleshooting -For help with common problems please reference the -[Troubleshooting](https://github.com/bwmarrin/discordgo/wiki/Troubleshooting) -section of the project wiki. - - -## Contributing -Contributions are very welcomed, however please follow the below guidelines. - -- First open an issue describing the bug or enhancement so it can be -discussed. -- Try to match current naming conventions as closely as possible. -- This package is intended to be a low level direct mapping of the Discord API, -so please avoid adding enhancements outside of that scope without first -discussing it. -- Create a Pull Request with your changes against the master branch. - - -## List of Discord APIs - -See [this chart](https://abal.moe/Discord/Libraries.html) for a feature -comparison and list of other Discord API libraries. - -## Special Thanks - -[Chris Rhodes](https://github.com/iopred) - For the DiscordGo logo and tons of PRs. diff --git a/vendor/github.com/matterbridge/discordgo/discord.go b/vendor/github.com/matterbridge/discordgo/discord.go deleted file mode 100644 index d319b7b4..00000000 --- a/vendor/github.com/matterbridge/discordgo/discord.go +++ /dev/null @@ -1,161 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/matterbridge/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains high level helper functions and easy entry points for the -// entire discordgo package. These functions are being developed and are very -// experimental at this point. They will most likely change so please use the -// low level functions if that's a problem. - -// Package discordgo provides Discord binding for Go -package discordgo - -import ( - "errors" - "fmt" - "net/http" - "runtime" - "time" -) - -// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) -const VERSION = "0.23.0" - -// ErrMFA will be risen by New when the user has 2FA. -var ErrMFA = errors.New("account has 2FA enabled") - -// New creates a new Discord session and will automate some startup -// tasks if given enough information to do so. Currently you can pass zero -// arguments and it will return an empty Discord session. -// There are 3 ways to call New: -// With a single auth token - All requests will use the token blindly -// (just tossing it into the HTTP Authorization header); -// no verification of the token will be done and requests may fail. -// IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT ` -// eg: `"Bot "` -// IF IT IS AN OAUTH2 ACCESS TOKEN, IT MUST BE PREFIXED WITH `Bearer ` -// eg: `"Bearer "` -// With an email and password - Discord will sign in with the provided -// credentials. -// With an email, password and auth token - Discord will verify the auth -// token, if it is invalid it will sign in with the provided -// credentials. This is the Discord recommended way to sign in. -// -// NOTE: While email/pass authentication is supported by DiscordGo it is -// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token -// and then use that authentication token for all future connections. -// Also, doing any form of automation with a user (non Bot) account may result -// in that account being permanently banned from Discord. -func New(args ...interface{}) (s *Session, err error) { - - // Create an empty Session interface. - s = &Session{ - State: NewState(), - Ratelimiter: NewRatelimiter(), - StateEnabled: true, - Compress: true, - ShouldReconnectOnError: true, - ShardID: 0, - ShardCount: 1, - MaxRestRetries: 3, - Client: &http.Client{Timeout: (20 * time.Second)}, - UserAgent: "DiscordBot (https://github.com/matterbridge/discordgo, v" + VERSION + ")", - sequence: new(int64), - LastHeartbeatAck: time.Now().UTC(), - } - - // Initilize the Identify Package with defaults - // These can be modified prior to calling Open() - s.Identify.Compress = true - s.Identify.LargeThreshold = 250 - s.Identify.GuildSubscriptions = true - s.Identify.Properties.OS = runtime.GOOS - s.Identify.Properties.Browser = "DiscordGo v" + VERSION - s.Identify.Intents = MakeIntent(IntentsAllWithoutPrivileged) - - // If no arguments are passed return the empty Session interface. - if args == nil { - return - } - - // Variables used below when parsing func arguments - var auth, pass string - - // Parse passed arguments - for _, arg := range args { - - switch v := arg.(type) { - - case []string: - if len(v) > 3 { - err = fmt.Errorf("too many string parameters provided") - return - } - - // First string is either token or username - if len(v) > 0 { - auth = v[0] - } - - // If second string exists, it must be a password. - if len(v) > 1 { - pass = v[1] - } - - // If third string exists, it must be an auth token. - if len(v) > 2 { - s.Identify.Token = v[2] - s.Token = v[2] // TODO: Remove, Deprecated - Kept for backwards compatibility. - } - - case string: - // First string must be either auth token or username. - // Second string must be a password. - // Only 2 input strings are supported. - - if auth == "" { - auth = v - } else if pass == "" { - pass = v - } else if s.Token == "" { - s.Identify.Token = v - s.Token = v // TODO: Remove, Deprecated - Kept for backwards compatibility. - } else { - err = fmt.Errorf("too many string parameters provided") - return - } - - // case Config: - // TODO: Parse configuration struct - - default: - err = fmt.Errorf("unsupported parameter type provided") - return - } - } - - // If only one string was provided, assume it is an auth token. - // Otherwise get auth token from Discord, if a token was specified - // Discord will verify it for free, or log the user in if it is - // invalid. - if pass == "" { - s.Identify.Token = auth - s.Token = auth // TODO: Remove, Deprecated - Kept for backwards compatibility. - } else { - err = s.Login(auth, pass) - // TODO: Remove last s.Token part, Deprecated - Kept for backwards compatibility. - if err != nil || s.Identify.Token == "" || s.Token == "" { - if s.MFA { - err = ErrMFA - } else { - err = fmt.Errorf("Unable to fetch discord authentication token. %v", err) - } - return - } - } - - return -} diff --git a/vendor/github.com/matterbridge/discordgo/endpoints.go b/vendor/github.com/matterbridge/discordgo/endpoints.go deleted file mode 100644 index 89d56eda..00000000 --- a/vendor/github.com/matterbridge/discordgo/endpoints.go +++ /dev/null @@ -1,153 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains variables for all known Discord end points. All functions -// throughout the Discordgo package use these variables for all connections -// to Discord. These are all exported and you may modify them if needed. - -package discordgo - -import "strconv" - -// APIVersion is the Discord API version used for the REST and Websocket API. -var APIVersion = "8" - -// Known Discord API Endpoints. -var ( - EndpointStatus = "https://status.discord.com/api/v2/" - EndpointSm = EndpointStatus + "scheduled-maintenances/" - EndpointSmActive = EndpointSm + "active.json" - EndpointSmUpcoming = EndpointSm + "upcoming.json" - - EndpointDiscord = "https://discord.com/" - EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/" - EndpointGuilds = EndpointAPI + "guilds/" - EndpointChannels = EndpointAPI + "channels/" - EndpointUsers = EndpointAPI + "users/" - EndpointGateway = EndpointAPI + "gateway" - EndpointGatewayBot = EndpointGateway + "/bot" - EndpointWebhooks = EndpointAPI + "webhooks/" - - EndpointCDN = "https://cdn.discordapp.com/" - EndpointCDNAttachments = EndpointCDN + "attachments/" - EndpointCDNAvatars = EndpointCDN + "avatars/" - EndpointCDNIcons = EndpointCDN + "icons/" - EndpointCDNSplashes = EndpointCDN + "splashes/" - EndpointCDNChannelIcons = EndpointCDN + "channel-icons/" - EndpointCDNBanners = EndpointCDN + "banners/" - - EndpointAuth = EndpointAPI + "auth/" - EndpointLogin = EndpointAuth + "login" - EndpointLogout = EndpointAuth + "logout" - EndpointVerify = EndpointAuth + "verify" - EndpointVerifyResend = EndpointAuth + "verify/resend" - EndpointForgotPassword = EndpointAuth + "forgot" - EndpointResetPassword = EndpointAuth + "reset" - EndpointRegister = EndpointAuth + "register" - - EndpointVoice = EndpointAPI + "/voice/" - EndpointVoiceRegions = EndpointVoice + "regions" - EndpointVoiceIce = EndpointVoice + "ice" - - EndpointTutorial = EndpointAPI + "tutorial/" - EndpointTutorialIndicators = EndpointTutorial + "indicators" - - EndpointTrack = EndpointAPI + "track" - EndpointSso = EndpointAPI + "sso" - EndpointReport = EndpointAPI + "report" - EndpointIntegrations = EndpointAPI + "integrations" - - EndpointUser = func(uID string) string { return EndpointUsers + uID } - EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" } - EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" } - EndpointDefaultUserAvatar = func(uDiscriminator string) string { - uDiscriminatorInt, _ := strconv.Atoi(uDiscriminator) - return EndpointCDN + "embed/avatars/" + strconv.Itoa(uDiscriminatorInt%5) + ".png" - } - EndpointUserSettings = func(uID string) string { return EndpointUsers + uID + "/settings" } - EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" } - EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } - EndpointUserGuildSettings = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID + "/settings" } - EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } - EndpointUserDevices = func(uID string) string { return EndpointUsers + uID + "/devices" } - EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } - EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID } - - EndpointGuild = func(gID string) string { return EndpointGuilds + gID } - EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" } - EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" } - EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } - EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID } - EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" } - EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID } - EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" } - EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID } - EndpointGuildIntegrationSync = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID + "/sync" } - EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" } - EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID } - EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" } - EndpointGuildWidget = func(gID string) string { return EndpointGuilds + gID + "/widget" } - EndpointGuildEmbed = EndpointGuildWidget - EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } - EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } - EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" } - EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" } - EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" } - EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" } - EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" } - EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID } - EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" } - - EndpointChannel = func(cID string) string { return EndpointChannels + cID } - EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" } - EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID } - EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" } - EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" } - EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" } - EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID } - EndpointChannelMessageAck = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" } - EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" } - EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" } - EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } - EndpointChannelMessageCrosspost = func(cID, mID string) string { return EndpointChannel(cID) + "/messages/" + mID + "/crosspost" } - EndpointChannelFollow = func(cID string) string { return EndpointChannel(cID) + "/followers" } - - EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" } - - EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" } - EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID } - EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token } - - EndpointMessageReactionsAll = func(cID, mID string) string { - return EndpointChannelMessage(cID, mID) + "/reactions" - } - EndpointMessageReactions = func(cID, mID, eID string) string { - return EndpointChannelMessage(cID, mID) + "/reactions/" + eID - } - EndpointMessageReaction = func(cID, mID, eID, uID string) string { - return EndpointMessageReactions(cID, mID, eID) + "/" + uID - } - - EndpointRelationships = func() string { return EndpointUsers + "@me" + "/relationships" } - EndpointRelationship = func(uID string) string { return EndpointRelationships() + "/" + uID } - EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" } - - EndpointGuildCreate = EndpointAPI + "guilds" - - EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID } - - EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" } - - EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" } - EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" } - - EndpointOauth2 = EndpointAPI + "oauth2/" - EndpointApplications = EndpointOauth2 + "applications" - EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID } - EndpointApplicationsBot = func(aID string) string { return EndpointApplications + "/" + aID + "/bot" } - EndpointApplicationAssets = func(aID string) string { return EndpointApplications + "/" + aID + "/assets" } -) diff --git a/vendor/github.com/matterbridge/discordgo/event.go b/vendor/github.com/matterbridge/discordgo/event.go deleted file mode 100644 index 67c5f7dd..00000000 --- a/vendor/github.com/matterbridge/discordgo/event.go +++ /dev/null @@ -1,247 +0,0 @@ -package discordgo - -// EventHandler is an interface for Discord events. -type EventHandler interface { - // Type returns the type of event this handler belongs to. - Type() string - - // Handle is called whenever an event of Type() happens. - // It is the receivers responsibility to type assert that the interface - // is the expected struct. - Handle(*Session, interface{}) -} - -// EventInterfaceProvider is an interface for providing empty interfaces for -// Discord events. -type EventInterfaceProvider interface { - // Type is the type of event this handler belongs to. - Type() string - - // New returns a new instance of the struct this event handler handles. - // This is called once per event. - // The struct is provided to all handlers of the same Type(). - New() interface{} -} - -// interfaceEventType is the event handler type for interface{} events. -const interfaceEventType = "__INTERFACE__" - -// interfaceEventHandler is an event handler for interface{} events. -type interfaceEventHandler func(*Session, interface{}) - -// Type returns the event type for interface{} events. -func (eh interfaceEventHandler) Type() string { - return interfaceEventType -} - -// Handle is the handler for an interface{} event. -func (eh interfaceEventHandler) Handle(s *Session, i interface{}) { - eh(s, i) -} - -var registeredInterfaceProviders = map[string]EventInterfaceProvider{} - -// registerInterfaceProvider registers a provider so that DiscordGo can -// access it's New() method. -func registerInterfaceProvider(eh EventInterfaceProvider) { - if _, ok := registeredInterfaceProviders[eh.Type()]; ok { - return - // XXX: - // if we should error here, we need to do something with it. - // fmt.Errorf("event %s already registered", eh.Type()) - } - registeredInterfaceProviders[eh.Type()] = eh - return -} - -// eventHandlerInstance is a wrapper around an event handler, as functions -// cannot be compared directly. -type eventHandlerInstance struct { - eventHandler EventHandler -} - -// addEventHandler adds an event handler that will be fired anytime -// the Discord WSAPI matching eventHandler.Type() fires. -func (s *Session) addEventHandler(eventHandler EventHandler) func() { - s.handlersMu.Lock() - defer s.handlersMu.Unlock() - - if s.handlers == nil { - s.handlers = map[string][]*eventHandlerInstance{} - } - - ehi := &eventHandlerInstance{eventHandler} - s.handlers[eventHandler.Type()] = append(s.handlers[eventHandler.Type()], ehi) - - return func() { - s.removeEventHandlerInstance(eventHandler.Type(), ehi) - } -} - -// addEventHandler adds an event handler that will be fired the next time -// the Discord WSAPI matching eventHandler.Type() fires. -func (s *Session) addEventHandlerOnce(eventHandler EventHandler) func() { - s.handlersMu.Lock() - defer s.handlersMu.Unlock() - - if s.onceHandlers == nil { - s.onceHandlers = map[string][]*eventHandlerInstance{} - } - - ehi := &eventHandlerInstance{eventHandler} - s.onceHandlers[eventHandler.Type()] = append(s.onceHandlers[eventHandler.Type()], ehi) - - return func() { - s.removeEventHandlerInstance(eventHandler.Type(), ehi) - } -} - -// AddHandler allows you to add an event handler that will be fired anytime -// the Discord WSAPI event that matches the function fires. -// The first parameter is a *Session, and the second parameter is a pointer -// to a struct corresponding to the event for which you want to listen. -// -// eg: -// Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { -// }) -// -// or: -// Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) { -// }) -// -// List of events can be found at this page, with corresponding names in the -// library for each event: https://discord.com/developers/docs/topics/gateway#event-names -// There are also synthetic events fired by the library internally which are -// available for handling, like Connect, Disconnect, and RateLimit. -// events.go contains all of the Discord WSAPI and synthetic events that can be handled. -// -// The return value of this method is a function, that when called will remove the -// event handler. -func (s *Session) AddHandler(handler interface{}) func() { - eh := handlerForInterface(handler) - - if eh == nil { - s.log(LogError, "Invalid handler type, handler will never be called") - return func() {} - } - - return s.addEventHandler(eh) -} - -// AddHandlerOnce allows you to add an event handler that will be fired the next time -// the Discord WSAPI event that matches the function fires. -// See AddHandler for more details. -func (s *Session) AddHandlerOnce(handler interface{}) func() { - eh := handlerForInterface(handler) - - if eh == nil { - s.log(LogError, "Invalid handler type, handler will never be called") - return func() {} - } - - return s.addEventHandlerOnce(eh) -} - -// removeEventHandler instance removes an event handler instance. -func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance) { - s.handlersMu.Lock() - defer s.handlersMu.Unlock() - - handlers := s.handlers[t] - for i := range handlers { - if handlers[i] == ehi { - s.handlers[t] = append(handlers[:i], handlers[i+1:]...) - } - } - - onceHandlers := s.onceHandlers[t] - for i := range onceHandlers { - if onceHandlers[i] == ehi { - s.onceHandlers[t] = append(onceHandlers[:i], handlers[i+1:]...) - } - } -} - -// Handles calling permanent and once handlers for an event type. -func (s *Session) handle(t string, i interface{}) { - for _, eh := range s.handlers[t] { - if s.SyncEvents { - eh.eventHandler.Handle(s, i) - } else { - go eh.eventHandler.Handle(s, i) - } - } - - if len(s.onceHandlers[t]) > 0 { - for _, eh := range s.onceHandlers[t] { - if s.SyncEvents { - eh.eventHandler.Handle(s, i) - } else { - go eh.eventHandler.Handle(s, i) - } - } - s.onceHandlers[t] = nil - } -} - -// Handles an event type by calling internal methods, firing handlers and firing the -// interface{} event. -func (s *Session) handleEvent(t string, i interface{}) { - s.handlersMu.RLock() - defer s.handlersMu.RUnlock() - - // All events are dispatched internally first. - s.onInterface(i) - - // Then they are dispatched to anyone handling interface{} events. - s.handle(interfaceEventType, i) - - // Finally they are dispatched to any typed handlers. - s.handle(t, i) -} - -// setGuildIds will set the GuildID on all the members of a guild. -// This is done as event data does not have it set. -func setGuildIds(g *Guild) { - for _, c := range g.Channels { - c.GuildID = g.ID - } - - for _, m := range g.Members { - m.GuildID = g.ID - } - - for _, vs := range g.VoiceStates { - vs.GuildID = g.ID - } -} - -// onInterface handles all internal events and routes them to the appropriate internal handler. -func (s *Session) onInterface(i interface{}) { - switch t := i.(type) { - case *Ready: - for _, g := range t.Guilds { - setGuildIds(g) - } - s.onReady(t) - case *GuildCreate: - setGuildIds(t.Guild) - case *GuildUpdate: - setGuildIds(t.Guild) - case *VoiceServerUpdate: - go s.onVoiceServerUpdate(t) - case *VoiceStateUpdate: - go s.onVoiceStateUpdate(t) - } - err := s.State.OnInterface(s, i) - if err != nil { - s.log(LogDebug, "error dispatching internal event, %s", err) - } -} - -// onReady handles the ready event. -func (s *Session) onReady(r *Ready) { - - // Store the SessionID within the Session struct. - s.sessionID = r.SessionID -} diff --git a/vendor/github.com/matterbridge/discordgo/eventhandlers.go b/vendor/github.com/matterbridge/discordgo/eventhandlers.go deleted file mode 100644 index d2b9a98b..00000000 --- a/vendor/github.com/matterbridge/discordgo/eventhandlers.go +++ /dev/null @@ -1,1054 +0,0 @@ -// Code generated by \"eventhandlers\"; DO NOT EDIT -// See events.go - -package discordgo - -// Following are all the event types. -// Event type values are used to match the events returned by Discord. -// EventTypes surrounded by __ are synthetic and are internal to DiscordGo. -const ( - channelCreateEventType = "CHANNEL_CREATE" - channelDeleteEventType = "CHANNEL_DELETE" - channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE" - channelUpdateEventType = "CHANNEL_UPDATE" - connectEventType = "__CONNECT__" - disconnectEventType = "__DISCONNECT__" - eventEventType = "__EVENT__" - guildBanAddEventType = "GUILD_BAN_ADD" - guildBanRemoveEventType = "GUILD_BAN_REMOVE" - guildCreateEventType = "GUILD_CREATE" - guildDeleteEventType = "GUILD_DELETE" - guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE" - guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE" - guildMemberAddEventType = "GUILD_MEMBER_ADD" - guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE" - guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE" - guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK" - guildRoleCreateEventType = "GUILD_ROLE_CREATE" - guildRoleDeleteEventType = "GUILD_ROLE_DELETE" - guildRoleUpdateEventType = "GUILD_ROLE_UPDATE" - guildUpdateEventType = "GUILD_UPDATE" - messageAckEventType = "MESSAGE_ACK" - messageCreateEventType = "MESSAGE_CREATE" - messageDeleteEventType = "MESSAGE_DELETE" - messageDeleteBulkEventType = "MESSAGE_DELETE_BULK" - messageReactionAddEventType = "MESSAGE_REACTION_ADD" - messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE" - messageReactionRemoveAllEventType = "MESSAGE_REACTION_REMOVE_ALL" - messageUpdateEventType = "MESSAGE_UPDATE" - presenceUpdateEventType = "PRESENCE_UPDATE" - presencesReplaceEventType = "PRESENCES_REPLACE" - rateLimitEventType = "__RATE_LIMIT__" - readyEventType = "READY" - relationshipAddEventType = "RELATIONSHIP_ADD" - relationshipRemoveEventType = "RELATIONSHIP_REMOVE" - resumedEventType = "RESUMED" - typingStartEventType = "TYPING_START" - userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE" - userNoteUpdateEventType = "USER_NOTE_UPDATE" - userSettingsUpdateEventType = "USER_SETTINGS_UPDATE" - userUpdateEventType = "USER_UPDATE" - voiceServerUpdateEventType = "VOICE_SERVER_UPDATE" - voiceStateUpdateEventType = "VOICE_STATE_UPDATE" - webhooksUpdateEventType = "WEBHOOKS_UPDATE" -) - -// channelCreateEventHandler is an event handler for ChannelCreate events. -type channelCreateEventHandler func(*Session, *ChannelCreate) - -// Type returns the event type for ChannelCreate events. -func (eh channelCreateEventHandler) Type() string { - return channelCreateEventType -} - -// New returns a new instance of ChannelCreate. -func (eh channelCreateEventHandler) New() interface{} { - return &ChannelCreate{} -} - -// Handle is the handler for ChannelCreate events. -func (eh channelCreateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*ChannelCreate); ok { - eh(s, t) - } -} - -// channelDeleteEventHandler is an event handler for ChannelDelete events. -type channelDeleteEventHandler func(*Session, *ChannelDelete) - -// Type returns the event type for ChannelDelete events. -func (eh channelDeleteEventHandler) Type() string { - return channelDeleteEventType -} - -// New returns a new instance of ChannelDelete. -func (eh channelDeleteEventHandler) New() interface{} { - return &ChannelDelete{} -} - -// Handle is the handler for ChannelDelete events. -func (eh channelDeleteEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*ChannelDelete); ok { - eh(s, t) - } -} - -// channelPinsUpdateEventHandler is an event handler for ChannelPinsUpdate events. -type channelPinsUpdateEventHandler func(*Session, *ChannelPinsUpdate) - -// Type returns the event type for ChannelPinsUpdate events. -func (eh channelPinsUpdateEventHandler) Type() string { - return channelPinsUpdateEventType -} - -// New returns a new instance of ChannelPinsUpdate. -func (eh channelPinsUpdateEventHandler) New() interface{} { - return &ChannelPinsUpdate{} -} - -// Handle is the handler for ChannelPinsUpdate events. -func (eh channelPinsUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*ChannelPinsUpdate); ok { - eh(s, t) - } -} - -// channelUpdateEventHandler is an event handler for ChannelUpdate events. -type channelUpdateEventHandler func(*Session, *ChannelUpdate) - -// Type returns the event type for ChannelUpdate events. -func (eh channelUpdateEventHandler) Type() string { - return channelUpdateEventType -} - -// New returns a new instance of ChannelUpdate. -func (eh channelUpdateEventHandler) New() interface{} { - return &ChannelUpdate{} -} - -// Handle is the handler for ChannelUpdate events. -func (eh channelUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*ChannelUpdate); ok { - eh(s, t) - } -} - -// connectEventHandler is an event handler for Connect events. -type connectEventHandler func(*Session, *Connect) - -// Type returns the event type for Connect events. -func (eh connectEventHandler) Type() string { - return connectEventType -} - -// Handle is the handler for Connect events. -func (eh connectEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*Connect); ok { - eh(s, t) - } -} - -// disconnectEventHandler is an event handler for Disconnect events. -type disconnectEventHandler func(*Session, *Disconnect) - -// Type returns the event type for Disconnect events. -func (eh disconnectEventHandler) Type() string { - return disconnectEventType -} - -// Handle is the handler for Disconnect events. -func (eh disconnectEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*Disconnect); ok { - eh(s, t) - } -} - -// eventEventHandler is an event handler for Event events. -type eventEventHandler func(*Session, *Event) - -// Type returns the event type for Event events. -func (eh eventEventHandler) Type() string { - return eventEventType -} - -// Handle is the handler for Event events. -func (eh eventEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*Event); ok { - eh(s, t) - } -} - -// guildBanAddEventHandler is an event handler for GuildBanAdd events. -type guildBanAddEventHandler func(*Session, *GuildBanAdd) - -// Type returns the event type for GuildBanAdd events. -func (eh guildBanAddEventHandler) Type() string { - return guildBanAddEventType -} - -// New returns a new instance of GuildBanAdd. -func (eh guildBanAddEventHandler) New() interface{} { - return &GuildBanAdd{} -} - -// Handle is the handler for GuildBanAdd events. -func (eh guildBanAddEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildBanAdd); ok { - eh(s, t) - } -} - -// guildBanRemoveEventHandler is an event handler for GuildBanRemove events. -type guildBanRemoveEventHandler func(*Session, *GuildBanRemove) - -// Type returns the event type for GuildBanRemove events. -func (eh guildBanRemoveEventHandler) Type() string { - return guildBanRemoveEventType -} - -// New returns a new instance of GuildBanRemove. -func (eh guildBanRemoveEventHandler) New() interface{} { - return &GuildBanRemove{} -} - -// Handle is the handler for GuildBanRemove events. -func (eh guildBanRemoveEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildBanRemove); ok { - eh(s, t) - } -} - -// guildCreateEventHandler is an event handler for GuildCreate events. -type guildCreateEventHandler func(*Session, *GuildCreate) - -// Type returns the event type for GuildCreate events. -func (eh guildCreateEventHandler) Type() string { - return guildCreateEventType -} - -// New returns a new instance of GuildCreate. -func (eh guildCreateEventHandler) New() interface{} { - return &GuildCreate{} -} - -// Handle is the handler for GuildCreate events. -func (eh guildCreateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildCreate); ok { - eh(s, t) - } -} - -// guildDeleteEventHandler is an event handler for GuildDelete events. -type guildDeleteEventHandler func(*Session, *GuildDelete) - -// Type returns the event type for GuildDelete events. -func (eh guildDeleteEventHandler) Type() string { - return guildDeleteEventType -} - -// New returns a new instance of GuildDelete. -func (eh guildDeleteEventHandler) New() interface{} { - return &GuildDelete{} -} - -// Handle is the handler for GuildDelete events. -func (eh guildDeleteEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildDelete); ok { - eh(s, t) - } -} - -// guildEmojisUpdateEventHandler is an event handler for GuildEmojisUpdate events. -type guildEmojisUpdateEventHandler func(*Session, *GuildEmojisUpdate) - -// Type returns the event type for GuildEmojisUpdate events. -func (eh guildEmojisUpdateEventHandler) Type() string { - return guildEmojisUpdateEventType -} - -// New returns a new instance of GuildEmojisUpdate. -func (eh guildEmojisUpdateEventHandler) New() interface{} { - return &GuildEmojisUpdate{} -} - -// Handle is the handler for GuildEmojisUpdate events. -func (eh guildEmojisUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildEmojisUpdate); ok { - eh(s, t) - } -} - -// guildIntegrationsUpdateEventHandler is an event handler for GuildIntegrationsUpdate events. -type guildIntegrationsUpdateEventHandler func(*Session, *GuildIntegrationsUpdate) - -// Type returns the event type for GuildIntegrationsUpdate events. -func (eh guildIntegrationsUpdateEventHandler) Type() string { - return guildIntegrationsUpdateEventType -} - -// New returns a new instance of GuildIntegrationsUpdate. -func (eh guildIntegrationsUpdateEventHandler) New() interface{} { - return &GuildIntegrationsUpdate{} -} - -// Handle is the handler for GuildIntegrationsUpdate events. -func (eh guildIntegrationsUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildIntegrationsUpdate); ok { - eh(s, t) - } -} - -// guildMemberAddEventHandler is an event handler for GuildMemberAdd events. -type guildMemberAddEventHandler func(*Session, *GuildMemberAdd) - -// Type returns the event type for GuildMemberAdd events. -func (eh guildMemberAddEventHandler) Type() string { - return guildMemberAddEventType -} - -// New returns a new instance of GuildMemberAdd. -func (eh guildMemberAddEventHandler) New() interface{} { - return &GuildMemberAdd{} -} - -// Handle is the handler for GuildMemberAdd events. -func (eh guildMemberAddEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildMemberAdd); ok { - eh(s, t) - } -} - -// guildMemberRemoveEventHandler is an event handler for GuildMemberRemove events. -type guildMemberRemoveEventHandler func(*Session, *GuildMemberRemove) - -// Type returns the event type for GuildMemberRemove events. -func (eh guildMemberRemoveEventHandler) Type() string { - return guildMemberRemoveEventType -} - -// New returns a new instance of GuildMemberRemove. -func (eh guildMemberRemoveEventHandler) New() interface{} { - return &GuildMemberRemove{} -} - -// Handle is the handler for GuildMemberRemove events. -func (eh guildMemberRemoveEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildMemberRemove); ok { - eh(s, t) - } -} - -// guildMemberUpdateEventHandler is an event handler for GuildMemberUpdate events. -type guildMemberUpdateEventHandler func(*Session, *GuildMemberUpdate) - -// Type returns the event type for GuildMemberUpdate events. -func (eh guildMemberUpdateEventHandler) Type() string { - return guildMemberUpdateEventType -} - -// New returns a new instance of GuildMemberUpdate. -func (eh guildMemberUpdateEventHandler) New() interface{} { - return &GuildMemberUpdate{} -} - -// Handle is the handler for GuildMemberUpdate events. -func (eh guildMemberUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildMemberUpdate); ok { - eh(s, t) - } -} - -// guildMembersChunkEventHandler is an event handler for GuildMembersChunk events. -type guildMembersChunkEventHandler func(*Session, *GuildMembersChunk) - -// Type returns the event type for GuildMembersChunk events. -func (eh guildMembersChunkEventHandler) Type() string { - return guildMembersChunkEventType -} - -// New returns a new instance of GuildMembersChunk. -func (eh guildMembersChunkEventHandler) New() interface{} { - return &GuildMembersChunk{} -} - -// Handle is the handler for GuildMembersChunk events. -func (eh guildMembersChunkEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildMembersChunk); ok { - eh(s, t) - } -} - -// guildRoleCreateEventHandler is an event handler for GuildRoleCreate events. -type guildRoleCreateEventHandler func(*Session, *GuildRoleCreate) - -// Type returns the event type for GuildRoleCreate events. -func (eh guildRoleCreateEventHandler) Type() string { - return guildRoleCreateEventType -} - -// New returns a new instance of GuildRoleCreate. -func (eh guildRoleCreateEventHandler) New() interface{} { - return &GuildRoleCreate{} -} - -// Handle is the handler for GuildRoleCreate events. -func (eh guildRoleCreateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildRoleCreate); ok { - eh(s, t) - } -} - -// guildRoleDeleteEventHandler is an event handler for GuildRoleDelete events. -type guildRoleDeleteEventHandler func(*Session, *GuildRoleDelete) - -// Type returns the event type for GuildRoleDelete events. -func (eh guildRoleDeleteEventHandler) Type() string { - return guildRoleDeleteEventType -} - -// New returns a new instance of GuildRoleDelete. -func (eh guildRoleDeleteEventHandler) New() interface{} { - return &GuildRoleDelete{} -} - -// Handle is the handler for GuildRoleDelete events. -func (eh guildRoleDeleteEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildRoleDelete); ok { - eh(s, t) - } -} - -// guildRoleUpdateEventHandler is an event handler for GuildRoleUpdate events. -type guildRoleUpdateEventHandler func(*Session, *GuildRoleUpdate) - -// Type returns the event type for GuildRoleUpdate events. -func (eh guildRoleUpdateEventHandler) Type() string { - return guildRoleUpdateEventType -} - -// New returns a new instance of GuildRoleUpdate. -func (eh guildRoleUpdateEventHandler) New() interface{} { - return &GuildRoleUpdate{} -} - -// Handle is the handler for GuildRoleUpdate events. -func (eh guildRoleUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildRoleUpdate); ok { - eh(s, t) - } -} - -// guildUpdateEventHandler is an event handler for GuildUpdate events. -type guildUpdateEventHandler func(*Session, *GuildUpdate) - -// Type returns the event type for GuildUpdate events. -func (eh guildUpdateEventHandler) Type() string { - return guildUpdateEventType -} - -// New returns a new instance of GuildUpdate. -func (eh guildUpdateEventHandler) New() interface{} { - return &GuildUpdate{} -} - -// Handle is the handler for GuildUpdate events. -func (eh guildUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*GuildUpdate); ok { - eh(s, t) - } -} - -// messageAckEventHandler is an event handler for MessageAck events. -type messageAckEventHandler func(*Session, *MessageAck) - -// Type returns the event type for MessageAck events. -func (eh messageAckEventHandler) Type() string { - return messageAckEventType -} - -// New returns a new instance of MessageAck. -func (eh messageAckEventHandler) New() interface{} { - return &MessageAck{} -} - -// Handle is the handler for MessageAck events. -func (eh messageAckEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageAck); ok { - eh(s, t) - } -} - -// messageCreateEventHandler is an event handler for MessageCreate events. -type messageCreateEventHandler func(*Session, *MessageCreate) - -// Type returns the event type for MessageCreate events. -func (eh messageCreateEventHandler) Type() string { - return messageCreateEventType -} - -// New returns a new instance of MessageCreate. -func (eh messageCreateEventHandler) New() interface{} { - return &MessageCreate{} -} - -// Handle is the handler for MessageCreate events. -func (eh messageCreateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageCreate); ok { - eh(s, t) - } -} - -// messageDeleteEventHandler is an event handler for MessageDelete events. -type messageDeleteEventHandler func(*Session, *MessageDelete) - -// Type returns the event type for MessageDelete events. -func (eh messageDeleteEventHandler) Type() string { - return messageDeleteEventType -} - -// New returns a new instance of MessageDelete. -func (eh messageDeleteEventHandler) New() interface{} { - return &MessageDelete{} -} - -// Handle is the handler for MessageDelete events. -func (eh messageDeleteEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageDelete); ok { - eh(s, t) - } -} - -// messageDeleteBulkEventHandler is an event handler for MessageDeleteBulk events. -type messageDeleteBulkEventHandler func(*Session, *MessageDeleteBulk) - -// Type returns the event type for MessageDeleteBulk events. -func (eh messageDeleteBulkEventHandler) Type() string { - return messageDeleteBulkEventType -} - -// New returns a new instance of MessageDeleteBulk. -func (eh messageDeleteBulkEventHandler) New() interface{} { - return &MessageDeleteBulk{} -} - -// Handle is the handler for MessageDeleteBulk events. -func (eh messageDeleteBulkEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageDeleteBulk); ok { - eh(s, t) - } -} - -// messageReactionAddEventHandler is an event handler for MessageReactionAdd events. -type messageReactionAddEventHandler func(*Session, *MessageReactionAdd) - -// Type returns the event type for MessageReactionAdd events. -func (eh messageReactionAddEventHandler) Type() string { - return messageReactionAddEventType -} - -// New returns a new instance of MessageReactionAdd. -func (eh messageReactionAddEventHandler) New() interface{} { - return &MessageReactionAdd{} -} - -// Handle is the handler for MessageReactionAdd events. -func (eh messageReactionAddEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageReactionAdd); ok { - eh(s, t) - } -} - -// messageReactionRemoveEventHandler is an event handler for MessageReactionRemove events. -type messageReactionRemoveEventHandler func(*Session, *MessageReactionRemove) - -// Type returns the event type for MessageReactionRemove events. -func (eh messageReactionRemoveEventHandler) Type() string { - return messageReactionRemoveEventType -} - -// New returns a new instance of MessageReactionRemove. -func (eh messageReactionRemoveEventHandler) New() interface{} { - return &MessageReactionRemove{} -} - -// Handle is the handler for MessageReactionRemove events. -func (eh messageReactionRemoveEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageReactionRemove); ok { - eh(s, t) - } -} - -// messageReactionRemoveAllEventHandler is an event handler for MessageReactionRemoveAll events. -type messageReactionRemoveAllEventHandler func(*Session, *MessageReactionRemoveAll) - -// Type returns the event type for MessageReactionRemoveAll events. -func (eh messageReactionRemoveAllEventHandler) Type() string { - return messageReactionRemoveAllEventType -} - -// New returns a new instance of MessageReactionRemoveAll. -func (eh messageReactionRemoveAllEventHandler) New() interface{} { - return &MessageReactionRemoveAll{} -} - -// Handle is the handler for MessageReactionRemoveAll events. -func (eh messageReactionRemoveAllEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageReactionRemoveAll); ok { - eh(s, t) - } -} - -// messageUpdateEventHandler is an event handler for MessageUpdate events. -type messageUpdateEventHandler func(*Session, *MessageUpdate) - -// Type returns the event type for MessageUpdate events. -func (eh messageUpdateEventHandler) Type() string { - return messageUpdateEventType -} - -// New returns a new instance of MessageUpdate. -func (eh messageUpdateEventHandler) New() interface{} { - return &MessageUpdate{} -} - -// Handle is the handler for MessageUpdate events. -func (eh messageUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*MessageUpdate); ok { - eh(s, t) - } -} - -// presenceUpdateEventHandler is an event handler for PresenceUpdate events. -type presenceUpdateEventHandler func(*Session, *PresenceUpdate) - -// Type returns the event type for PresenceUpdate events. -func (eh presenceUpdateEventHandler) Type() string { - return presenceUpdateEventType -} - -// New returns a new instance of PresenceUpdate. -func (eh presenceUpdateEventHandler) New() interface{} { - return &PresenceUpdate{} -} - -// Handle is the handler for PresenceUpdate events. -func (eh presenceUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*PresenceUpdate); ok { - eh(s, t) - } -} - -// presencesReplaceEventHandler is an event handler for PresencesReplace events. -type presencesReplaceEventHandler func(*Session, *PresencesReplace) - -// Type returns the event type for PresencesReplace events. -func (eh presencesReplaceEventHandler) Type() string { - return presencesReplaceEventType -} - -// New returns a new instance of PresencesReplace. -func (eh presencesReplaceEventHandler) New() interface{} { - return &PresencesReplace{} -} - -// Handle is the handler for PresencesReplace events. -func (eh presencesReplaceEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*PresencesReplace); ok { - eh(s, t) - } -} - -// rateLimitEventHandler is an event handler for RateLimit events. -type rateLimitEventHandler func(*Session, *RateLimit) - -// Type returns the event type for RateLimit events. -func (eh rateLimitEventHandler) Type() string { - return rateLimitEventType -} - -// Handle is the handler for RateLimit events. -func (eh rateLimitEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*RateLimit); ok { - eh(s, t) - } -} - -// readyEventHandler is an event handler for Ready events. -type readyEventHandler func(*Session, *Ready) - -// Type returns the event type for Ready events. -func (eh readyEventHandler) Type() string { - return readyEventType -} - -// New returns a new instance of Ready. -func (eh readyEventHandler) New() interface{} { - return &Ready{} -} - -// Handle is the handler for Ready events. -func (eh readyEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*Ready); ok { - eh(s, t) - } -} - -// relationshipAddEventHandler is an event handler for RelationshipAdd events. -type relationshipAddEventHandler func(*Session, *RelationshipAdd) - -// Type returns the event type for RelationshipAdd events. -func (eh relationshipAddEventHandler) Type() string { - return relationshipAddEventType -} - -// New returns a new instance of RelationshipAdd. -func (eh relationshipAddEventHandler) New() interface{} { - return &RelationshipAdd{} -} - -// Handle is the handler for RelationshipAdd events. -func (eh relationshipAddEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*RelationshipAdd); ok { - eh(s, t) - } -} - -// relationshipRemoveEventHandler is an event handler for RelationshipRemove events. -type relationshipRemoveEventHandler func(*Session, *RelationshipRemove) - -// Type returns the event type for RelationshipRemove events. -func (eh relationshipRemoveEventHandler) Type() string { - return relationshipRemoveEventType -} - -// New returns a new instance of RelationshipRemove. -func (eh relationshipRemoveEventHandler) New() interface{} { - return &RelationshipRemove{} -} - -// Handle is the handler for RelationshipRemove events. -func (eh relationshipRemoveEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*RelationshipRemove); ok { - eh(s, t) - } -} - -// resumedEventHandler is an event handler for Resumed events. -type resumedEventHandler func(*Session, *Resumed) - -// Type returns the event type for Resumed events. -func (eh resumedEventHandler) Type() string { - return resumedEventType -} - -// New returns a new instance of Resumed. -func (eh resumedEventHandler) New() interface{} { - return &Resumed{} -} - -// Handle is the handler for Resumed events. -func (eh resumedEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*Resumed); ok { - eh(s, t) - } -} - -// typingStartEventHandler is an event handler for TypingStart events. -type typingStartEventHandler func(*Session, *TypingStart) - -// Type returns the event type for TypingStart events. -func (eh typingStartEventHandler) Type() string { - return typingStartEventType -} - -// New returns a new instance of TypingStart. -func (eh typingStartEventHandler) New() interface{} { - return &TypingStart{} -} - -// Handle is the handler for TypingStart events. -func (eh typingStartEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*TypingStart); ok { - eh(s, t) - } -} - -// userGuildSettingsUpdateEventHandler is an event handler for UserGuildSettingsUpdate events. -type userGuildSettingsUpdateEventHandler func(*Session, *UserGuildSettingsUpdate) - -// Type returns the event type for UserGuildSettingsUpdate events. -func (eh userGuildSettingsUpdateEventHandler) Type() string { - return userGuildSettingsUpdateEventType -} - -// New returns a new instance of UserGuildSettingsUpdate. -func (eh userGuildSettingsUpdateEventHandler) New() interface{} { - return &UserGuildSettingsUpdate{} -} - -// Handle is the handler for UserGuildSettingsUpdate events. -func (eh userGuildSettingsUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*UserGuildSettingsUpdate); ok { - eh(s, t) - } -} - -// userNoteUpdateEventHandler is an event handler for UserNoteUpdate events. -type userNoteUpdateEventHandler func(*Session, *UserNoteUpdate) - -// Type returns the event type for UserNoteUpdate events. -func (eh userNoteUpdateEventHandler) Type() string { - return userNoteUpdateEventType -} - -// New returns a new instance of UserNoteUpdate. -func (eh userNoteUpdateEventHandler) New() interface{} { - return &UserNoteUpdate{} -} - -// Handle is the handler for UserNoteUpdate events. -func (eh userNoteUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*UserNoteUpdate); ok { - eh(s, t) - } -} - -// userSettingsUpdateEventHandler is an event handler for UserSettingsUpdate events. -type userSettingsUpdateEventHandler func(*Session, *UserSettingsUpdate) - -// Type returns the event type for UserSettingsUpdate events. -func (eh userSettingsUpdateEventHandler) Type() string { - return userSettingsUpdateEventType -} - -// New returns a new instance of UserSettingsUpdate. -func (eh userSettingsUpdateEventHandler) New() interface{} { - return &UserSettingsUpdate{} -} - -// Handle is the handler for UserSettingsUpdate events. -func (eh userSettingsUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*UserSettingsUpdate); ok { - eh(s, t) - } -} - -// userUpdateEventHandler is an event handler for UserUpdate events. -type userUpdateEventHandler func(*Session, *UserUpdate) - -// Type returns the event type for UserUpdate events. -func (eh userUpdateEventHandler) Type() string { - return userUpdateEventType -} - -// New returns a new instance of UserUpdate. -func (eh userUpdateEventHandler) New() interface{} { - return &UserUpdate{} -} - -// Handle is the handler for UserUpdate events. -func (eh userUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*UserUpdate); ok { - eh(s, t) - } -} - -// voiceServerUpdateEventHandler is an event handler for VoiceServerUpdate events. -type voiceServerUpdateEventHandler func(*Session, *VoiceServerUpdate) - -// Type returns the event type for VoiceServerUpdate events. -func (eh voiceServerUpdateEventHandler) Type() string { - return voiceServerUpdateEventType -} - -// New returns a new instance of VoiceServerUpdate. -func (eh voiceServerUpdateEventHandler) New() interface{} { - return &VoiceServerUpdate{} -} - -// Handle is the handler for VoiceServerUpdate events. -func (eh voiceServerUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*VoiceServerUpdate); ok { - eh(s, t) - } -} - -// voiceStateUpdateEventHandler is an event handler for VoiceStateUpdate events. -type voiceStateUpdateEventHandler func(*Session, *VoiceStateUpdate) - -// Type returns the event type for VoiceStateUpdate events. -func (eh voiceStateUpdateEventHandler) Type() string { - return voiceStateUpdateEventType -} - -// New returns a new instance of VoiceStateUpdate. -func (eh voiceStateUpdateEventHandler) New() interface{} { - return &VoiceStateUpdate{} -} - -// Handle is the handler for VoiceStateUpdate events. -func (eh voiceStateUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*VoiceStateUpdate); ok { - eh(s, t) - } -} - -// webhooksUpdateEventHandler is an event handler for WebhooksUpdate events. -type webhooksUpdateEventHandler func(*Session, *WebhooksUpdate) - -// Type returns the event type for WebhooksUpdate events. -func (eh webhooksUpdateEventHandler) Type() string { - return webhooksUpdateEventType -} - -// New returns a new instance of WebhooksUpdate. -func (eh webhooksUpdateEventHandler) New() interface{} { - return &WebhooksUpdate{} -} - -// Handle is the handler for WebhooksUpdate events. -func (eh webhooksUpdateEventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*WebhooksUpdate); ok { - eh(s, t) - } -} - -func handlerForInterface(handler interface{}) EventHandler { - switch v := handler.(type) { - case func(*Session, interface{}): - return interfaceEventHandler(v) - case func(*Session, *ChannelCreate): - return channelCreateEventHandler(v) - case func(*Session, *ChannelDelete): - return channelDeleteEventHandler(v) - case func(*Session, *ChannelPinsUpdate): - return channelPinsUpdateEventHandler(v) - case func(*Session, *ChannelUpdate): - return channelUpdateEventHandler(v) - case func(*Session, *Connect): - return connectEventHandler(v) - case func(*Session, *Disconnect): - return disconnectEventHandler(v) - case func(*Session, *Event): - return eventEventHandler(v) - case func(*Session, *GuildBanAdd): - return guildBanAddEventHandler(v) - case func(*Session, *GuildBanRemove): - return guildBanRemoveEventHandler(v) - case func(*Session, *GuildCreate): - return guildCreateEventHandler(v) - case func(*Session, *GuildDelete): - return guildDeleteEventHandler(v) - case func(*Session, *GuildEmojisUpdate): - return guildEmojisUpdateEventHandler(v) - case func(*Session, *GuildIntegrationsUpdate): - return guildIntegrationsUpdateEventHandler(v) - case func(*Session, *GuildMemberAdd): - return guildMemberAddEventHandler(v) - case func(*Session, *GuildMemberRemove): - return guildMemberRemoveEventHandler(v) - case func(*Session, *GuildMemberUpdate): - return guildMemberUpdateEventHandler(v) - case func(*Session, *GuildMembersChunk): - return guildMembersChunkEventHandler(v) - case func(*Session, *GuildRoleCreate): - return guildRoleCreateEventHandler(v) - case func(*Session, *GuildRoleDelete): - return guildRoleDeleteEventHandler(v) - case func(*Session, *GuildRoleUpdate): - return guildRoleUpdateEventHandler(v) - case func(*Session, *GuildUpdate): - return guildUpdateEventHandler(v) - case func(*Session, *MessageAck): - return messageAckEventHandler(v) - case func(*Session, *MessageCreate): - return messageCreateEventHandler(v) - case func(*Session, *MessageDelete): - return messageDeleteEventHandler(v) - case func(*Session, *MessageDeleteBulk): - return messageDeleteBulkEventHandler(v) - case func(*Session, *MessageReactionAdd): - return messageReactionAddEventHandler(v) - case func(*Session, *MessageReactionRemove): - return messageReactionRemoveEventHandler(v) - case func(*Session, *MessageReactionRemoveAll): - return messageReactionRemoveAllEventHandler(v) - case func(*Session, *MessageUpdate): - return messageUpdateEventHandler(v) - case func(*Session, *PresenceUpdate): - return presenceUpdateEventHandler(v) - case func(*Session, *PresencesReplace): - return presencesReplaceEventHandler(v) - case func(*Session, *RateLimit): - return rateLimitEventHandler(v) - case func(*Session, *Ready): - return readyEventHandler(v) - case func(*Session, *RelationshipAdd): - return relationshipAddEventHandler(v) - case func(*Session, *RelationshipRemove): - return relationshipRemoveEventHandler(v) - case func(*Session, *Resumed): - return resumedEventHandler(v) - case func(*Session, *TypingStart): - return typingStartEventHandler(v) - case func(*Session, *UserGuildSettingsUpdate): - return userGuildSettingsUpdateEventHandler(v) - case func(*Session, *UserNoteUpdate): - return userNoteUpdateEventHandler(v) - case func(*Session, *UserSettingsUpdate): - return userSettingsUpdateEventHandler(v) - case func(*Session, *UserUpdate): - return userUpdateEventHandler(v) - case func(*Session, *VoiceServerUpdate): - return voiceServerUpdateEventHandler(v) - case func(*Session, *VoiceStateUpdate): - return voiceStateUpdateEventHandler(v) - case func(*Session, *WebhooksUpdate): - return webhooksUpdateEventHandler(v) - } - - return nil -} - -func init() { - registerInterfaceProvider(channelCreateEventHandler(nil)) - registerInterfaceProvider(channelDeleteEventHandler(nil)) - registerInterfaceProvider(channelPinsUpdateEventHandler(nil)) - registerInterfaceProvider(channelUpdateEventHandler(nil)) - registerInterfaceProvider(guildBanAddEventHandler(nil)) - registerInterfaceProvider(guildBanRemoveEventHandler(nil)) - registerInterfaceProvider(guildCreateEventHandler(nil)) - registerInterfaceProvider(guildDeleteEventHandler(nil)) - registerInterfaceProvider(guildEmojisUpdateEventHandler(nil)) - registerInterfaceProvider(guildIntegrationsUpdateEventHandler(nil)) - registerInterfaceProvider(guildMemberAddEventHandler(nil)) - registerInterfaceProvider(guildMemberRemoveEventHandler(nil)) - registerInterfaceProvider(guildMemberUpdateEventHandler(nil)) - registerInterfaceProvider(guildMembersChunkEventHandler(nil)) - registerInterfaceProvider(guildRoleCreateEventHandler(nil)) - registerInterfaceProvider(guildRoleDeleteEventHandler(nil)) - registerInterfaceProvider(guildRoleUpdateEventHandler(nil)) - registerInterfaceProvider(guildUpdateEventHandler(nil)) - registerInterfaceProvider(messageAckEventHandler(nil)) - registerInterfaceProvider(messageCreateEventHandler(nil)) - registerInterfaceProvider(messageDeleteEventHandler(nil)) - registerInterfaceProvider(messageDeleteBulkEventHandler(nil)) - registerInterfaceProvider(messageReactionAddEventHandler(nil)) - registerInterfaceProvider(messageReactionRemoveEventHandler(nil)) - registerInterfaceProvider(messageReactionRemoveAllEventHandler(nil)) - registerInterfaceProvider(messageUpdateEventHandler(nil)) - registerInterfaceProvider(presenceUpdateEventHandler(nil)) - registerInterfaceProvider(presencesReplaceEventHandler(nil)) - registerInterfaceProvider(readyEventHandler(nil)) - registerInterfaceProvider(relationshipAddEventHandler(nil)) - registerInterfaceProvider(relationshipRemoveEventHandler(nil)) - registerInterfaceProvider(resumedEventHandler(nil)) - registerInterfaceProvider(typingStartEventHandler(nil)) - registerInterfaceProvider(userGuildSettingsUpdateEventHandler(nil)) - registerInterfaceProvider(userNoteUpdateEventHandler(nil)) - registerInterfaceProvider(userSettingsUpdateEventHandler(nil)) - registerInterfaceProvider(userUpdateEventHandler(nil)) - registerInterfaceProvider(voiceServerUpdateEventHandler(nil)) - registerInterfaceProvider(voiceStateUpdateEventHandler(nil)) - registerInterfaceProvider(webhooksUpdateEventHandler(nil)) -} diff --git a/vendor/github.com/matterbridge/discordgo/events.go b/vendor/github.com/matterbridge/discordgo/events.go deleted file mode 100644 index 7488dcc7..00000000 --- a/vendor/github.com/matterbridge/discordgo/events.go +++ /dev/null @@ -1,269 +0,0 @@ -package discordgo - -import ( - "encoding/json" -) - -// This file contains all the possible structs that can be -// handled by AddHandler/EventHandler. -// DO NOT ADD ANYTHING BUT EVENT HANDLER STRUCTS TO THIS FILE. -//go:generate go run tools/cmd/eventhandlers/main.go - -// Connect is the data for a Connect event. -// This is a synthetic event and is not dispatched by Discord. -type Connect struct{} - -// Disconnect is the data for a Disconnect event. -// This is a synthetic event and is not dispatched by Discord. -type Disconnect struct{} - -// RateLimit is the data for a RateLimit event. -// This is a synthetic event and is not dispatched by Discord. -type RateLimit struct { - *TooManyRequests - URL string -} - -// Event provides a basic initial struct for all websocket events. -type Event struct { - Operation int `json:"op"` - Sequence int64 `json:"s"` - Type string `json:"t"` - RawData json.RawMessage `json:"d"` - // Struct contains one of the other types in this file. - Struct interface{} `json:"-"` -} - -// A Ready stores all data for the websocket READY event. -type Ready struct { - Version int `json:"v"` - SessionID string `json:"session_id"` - User *User `json:"user"` - ReadState []*ReadState `json:"read_state"` - PrivateChannels []*Channel `json:"private_channels"` - Guilds []*Guild `json:"guilds"` - - // Undocumented fields - Settings *Settings `json:"user_settings"` - UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"` - Relationships []*Relationship `json:"relationships"` - Presences []*Presence `json:"presences"` - Notes map[string]string `json:"notes"` -} - -// ChannelCreate is the data for a ChannelCreate event. -type ChannelCreate struct { - *Channel -} - -// ChannelUpdate is the data for a ChannelUpdate event. -type ChannelUpdate struct { - *Channel -} - -// ChannelDelete is the data for a ChannelDelete event. -type ChannelDelete struct { - *Channel -} - -// ChannelPinsUpdate stores data for a ChannelPinsUpdate event. -type ChannelPinsUpdate struct { - LastPinTimestamp string `json:"last_pin_timestamp"` - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id,omitempty"` -} - -// GuildCreate is the data for a GuildCreate event. -type GuildCreate struct { - *Guild -} - -// GuildUpdate is the data for a GuildUpdate event. -type GuildUpdate struct { - *Guild -} - -// GuildDelete is the data for a GuildDelete event. -type GuildDelete struct { - *Guild -} - -// GuildBanAdd is the data for a GuildBanAdd event. -type GuildBanAdd struct { - User *User `json:"user"` - GuildID string `json:"guild_id"` -} - -// GuildBanRemove is the data for a GuildBanRemove event. -type GuildBanRemove struct { - User *User `json:"user"` - GuildID string `json:"guild_id"` -} - -// GuildMemberAdd is the data for a GuildMemberAdd event. -type GuildMemberAdd struct { - *Member -} - -// GuildMemberUpdate is the data for a GuildMemberUpdate event. -type GuildMemberUpdate struct { - *Member -} - -// GuildMemberRemove is the data for a GuildMemberRemove event. -type GuildMemberRemove struct { - *Member -} - -// GuildRoleCreate is the data for a GuildRoleCreate event. -type GuildRoleCreate struct { - *GuildRole -} - -// GuildRoleUpdate is the data for a GuildRoleUpdate event. -type GuildRoleUpdate struct { - *GuildRole -} - -// A GuildRoleDelete is the data for a GuildRoleDelete event. -type GuildRoleDelete struct { - RoleID string `json:"role_id"` - GuildID string `json:"guild_id"` -} - -// A GuildEmojisUpdate is the data for a guild emoji update event. -type GuildEmojisUpdate struct { - GuildID string `json:"guild_id"` - Emojis []*Emoji `json:"emojis"` -} - -// A GuildMembersChunk is the data for a GuildMembersChunk event. -type GuildMembersChunk struct { - GuildID string `json:"guild_id"` - Members []*Member `json:"members"` - ChunkIndex int `json:"chunk_index"` - ChunkCount int `json:"chunk_count"` - Presences []*Presence `json:"presences,omitempty"` -} - -// GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event. -type GuildIntegrationsUpdate struct { - GuildID string `json:"guild_id"` -} - -// MessageAck is the data for a MessageAck event. -type MessageAck struct { - MessageID string `json:"message_id"` - ChannelID string `json:"channel_id"` -} - -// MessageCreate is the data for a MessageCreate event. -type MessageCreate struct { - *Message -} - -// MessageUpdate is the data for a MessageUpdate event. -type MessageUpdate struct { - *Message - // BeforeUpdate will be nil if the Message was not previously cached in the state cache. - BeforeUpdate *Message `json:"-"` -} - -// MessageDelete is the data for a MessageDelete event. -type MessageDelete struct { - *Message - BeforeDelete *Message `json:"-"` -} - -// MessageReactionAdd is the data for a MessageReactionAdd event. -type MessageReactionAdd struct { - *MessageReaction -} - -// MessageReactionRemove is the data for a MessageReactionRemove event. -type MessageReactionRemove struct { - *MessageReaction -} - -// MessageReactionRemoveAll is the data for a MessageReactionRemoveAll event. -type MessageReactionRemoveAll struct { - *MessageReaction -} - -// PresencesReplace is the data for a PresencesReplace event. -type PresencesReplace []*Presence - -// PresenceUpdate is the data for a PresenceUpdate event. -type PresenceUpdate struct { - Presence - GuildID string `json:"guild_id"` -} - -// Resumed is the data for a Resumed event. -type Resumed struct { - Trace []string `json:"_trace"` -} - -// RelationshipAdd is the data for a RelationshipAdd event. -type RelationshipAdd struct { - *Relationship -} - -// RelationshipRemove is the data for a RelationshipRemove event. -type RelationshipRemove struct { - *Relationship -} - -// TypingStart is the data for a TypingStart event. -type TypingStart struct { - UserID string `json:"user_id"` - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id,omitempty"` - Timestamp int `json:"timestamp"` -} - -// UserUpdate is the data for a UserUpdate event. -type UserUpdate struct { - *User -} - -// UserSettingsUpdate is the data for a UserSettingsUpdate event. -type UserSettingsUpdate map[string]interface{} - -// UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event. -type UserGuildSettingsUpdate struct { - *UserGuildSettings -} - -// UserNoteUpdate is the data for a UserNoteUpdate event. -type UserNoteUpdate struct { - ID string `json:"id"` - Note string `json:"note"` -} - -// VoiceServerUpdate is the data for a VoiceServerUpdate event. -type VoiceServerUpdate struct { - Token string `json:"token"` - GuildID string `json:"guild_id"` - Endpoint string `json:"endpoint"` -} - -// VoiceStateUpdate is the data for a VoiceStateUpdate event. -type VoiceStateUpdate struct { - *VoiceState - // BeforeUpdate will be nil if the VoiceState was not previously cached in the state cache. - BeforeUpdate *VoiceState `json:"-"` -} - -// MessageDeleteBulk is the data for a MessageDeleteBulk event -type MessageDeleteBulk struct { - Messages []string `json:"ids"` - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id"` -} - -// WebhooksUpdate is the data for a WebhooksUpdate event -type WebhooksUpdate struct { - GuildID string `json:"guild_id"` - ChannelID string `json:"channel_id"` -} diff --git a/vendor/github.com/matterbridge/discordgo/interactions.go b/vendor/github.com/matterbridge/discordgo/interactions.go deleted file mode 100644 index 6fc2f55e..00000000 --- a/vendor/github.com/matterbridge/discordgo/interactions.go +++ /dev/null @@ -1,54 +0,0 @@ -package discordgo - -import ( - "bytes" - "crypto/ed25519" - "encoding/hex" - "io" - "io/ioutil" - "net/http" -) - -// VerifyInteraction implements message verification of the discord interactions api -// signing algorithm, as documented here: -// https://discord.com/developers/docs/interactions/slash-commands#security-and-authorization -func VerifyInteraction(r *http.Request, key ed25519.PublicKey) bool { - var msg bytes.Buffer - - signature := r.Header.Get("X-Signature-Ed25519") - if signature == "" { - return false - } - - sig, err := hex.DecodeString(signature) - if err != nil { - return false - } - - if len(sig) != ed25519.SignatureSize { - return false - } - - timestamp := r.Header.Get("X-Signature-Timestamp") - if timestamp == "" { - return false - } - - msg.WriteString(timestamp) - - defer r.Body.Close() - var body bytes.Buffer - - // at the end of the function, copy the original body back into the request - defer func() { - r.Body = ioutil.NopCloser(&body) - }() - - // copy body into buffers - _, err = io.Copy(&msg, io.TeeReader(r.Body, &body)) - if err != nil { - return false - } - - return ed25519.Verify(key, msg.Bytes(), sig) -} diff --git a/vendor/github.com/matterbridge/discordgo/logging.go b/vendor/github.com/matterbridge/discordgo/logging.go deleted file mode 100644 index 41f0481f..00000000 --- a/vendor/github.com/matterbridge/discordgo/logging.go +++ /dev/null @@ -1,103 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains code related to discordgo package logging - -package discordgo - -import ( - "fmt" - "log" - "runtime" - "strings" -) - -const ( - - // LogError level is used for critical errors that could lead to data loss - // or panic that would not be returned to a calling function. - LogError int = iota - - // LogWarning level is used for very abnormal events and errors that are - // also returned to a calling function. - LogWarning - - // LogInformational level is used for normal non-error activity - LogInformational - - // LogDebug level is for very detailed non-error activity. This is - // very spammy and will impact performance. - LogDebug -) - -// Logger can be used to replace the standard logging for discordgo -var Logger func(msgL, caller int, format string, a ...interface{}) - -// msglog provides package wide logging consistency for discordgo -// the format, a... portion this command follows that of fmt.Printf -// msgL : LogLevel of the message -// caller : 1 + the number of callers away from the message source -// format : Printf style message format -// a ... : comma separated list of values to pass -func msglog(msgL, caller int, format string, a ...interface{}) { - - if Logger != nil { - Logger(msgL, caller, format, a...) - } else { - - pc, file, line, _ := runtime.Caller(caller) - - files := strings.Split(file, "/") - file = files[len(files)-1] - - name := runtime.FuncForPC(pc).Name() - fns := strings.Split(name, ".") - name = fns[len(fns)-1] - - msg := fmt.Sprintf(format, a...) - - log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) - } -} - -// helper function that wraps msglog for the Session struct -// This adds a check to insure the message is only logged -// if the session log level is equal or higher than the -// message log level -func (s *Session) log(msgL int, format string, a ...interface{}) { - - if msgL > s.LogLevel { - return - } - - msglog(msgL, 2, format, a...) -} - -// helper function that wraps msglog for the VoiceConnection struct -// This adds a check to insure the message is only logged -// if the voice connection log level is equal or higher than the -// message log level -func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) { - - if msgL > v.LogLevel { - return - } - - msglog(msgL, 2, format, a...) -} - -// printJSON is a helper function to display JSON data in a easy to read format. -/* NOT USED ATM -func printJSON(body []byte) { - var prettyJSON bytes.Buffer - error := json.Indent(&prettyJSON, body, "", "\t") - if error != nil { - log.Print("JSON parse error: ", error) - } - log.Println(string(prettyJSON.Bytes())) -} -*/ diff --git a/vendor/github.com/matterbridge/discordgo/message.go b/vendor/github.com/matterbridge/discordgo/message.go deleted file mode 100644 index 61cd0d9c..00000000 --- a/vendor/github.com/matterbridge/discordgo/message.go +++ /dev/null @@ -1,449 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains code related to the Message struct - -package discordgo - -import ( - "io" - "regexp" - "strings" -) - -// MessageType is the type of Message -// https://discord.com/developers/docs/resources/channel#message-object-message-types -type MessageType int - -// Block contains the valid known MessageType values -const ( - MessageTypeDefault MessageType = iota - MessageTypeRecipientAdd - MessageTypeRecipientRemove - MessageTypeCall - MessageTypeChannelNameChange - MessageTypeChannelIconChange - MessageTypeChannelPinnedMessage - MessageTypeGuildMemberJoin - MessageTypeUserPremiumGuildSubscription - MessageTypeUserPremiumGuildSubscriptionTierOne - MessageTypeUserPremiumGuildSubscriptionTierTwo - MessageTypeUserPremiumGuildSubscriptionTierThree - MessageTypeChannelFollowAdd - MessageTypeGuildDiscoveryDisqualified = iota + 1 - MessageTypeGuildDiscoveryRequalified - MessageTypeReply = iota + 4 - MessageTypeApplicationCommand -) - -// A Message stores all data related to a specific Discord message. -type Message struct { - // The ID of the message. - ID string `json:"id"` - - // The ID of the channel in which the message was sent. - ChannelID string `json:"channel_id"` - - // The ID of the guild in which the message was sent. - GuildID string `json:"guild_id,omitempty"` - - // The content of the message. - Content string `json:"content"` - - // The time at which the messsage was sent. - // CAUTION: this field may be removed in a - // future API version; it is safer to calculate - // the creation time via the ID. - Timestamp Timestamp `json:"timestamp"` - - // The time at which the last edit of the message - // occurred, if it has been edited. - EditedTimestamp Timestamp `json:"edited_timestamp"` - - // The roles mentioned in the message. - MentionRoles []string `json:"mention_roles"` - - // Whether the message is text-to-speech. - TTS bool `json:"tts"` - - // Whether the message mentions everyone. - MentionEveryone bool `json:"mention_everyone"` - - // The author of the message. This is not guaranteed to be a - // valid user (webhook-sent messages do not possess a full author). - Author *User `json:"author"` - - // A list of attachments present in the message. - Attachments []*MessageAttachment `json:"attachments"` - - // A list of embeds present in the message. Multiple - // embeds can currently only be sent by webhooks. - Embeds []*MessageEmbed `json:"embeds"` - - // A list of users mentioned in the message. - Mentions []*User `json:"mentions"` - - // A list of reactions to the message. - Reactions []*MessageReactions `json:"reactions"` - - // Whether the message is pinned or not. - Pinned bool `json:"pinned"` - - // The type of the message. - Type MessageType `json:"type"` - - // The webhook ID of the message, if it was generated by a webhook - WebhookID string `json:"webhook_id"` - - // Member properties for this message's author, - // contains only partial information - Member *Member `json:"member"` - - // Channels specifically mentioned in this message - // Not all channel mentions in a message will appear in mention_channels. - // Only textual channels that are visible to everyone in a lurkable guild will ever be included. - // Only crossposted messages (via Channel Following) currently include mention_channels at all. - // If no mentions in the message meet these requirements, this field will not be sent. - MentionChannels []*Channel `json:"mention_channels"` - - // Is sent with Rich Presence-related chat embeds - Activity *MessageActivity `json:"activity"` - - // Is sent with Rich Presence-related chat embeds - Application *MessageApplication `json:"application"` - - // MessageReference contains reference data sent with crossposted messages - MessageReference *MessageReference `json:"message_reference"` - - // The flags of the message, which describe extra features of a message. - // This is a combination of bit masks; the presence of a certain permission can - // be checked by performing a bitwise AND between this int and the flag. - Flags MessageFlags `json:"flags"` -} - -// MessageFlags is the flags of "message" (see MessageFlags* consts) -// https://discord.com/developers/docs/resources/channel#message-object-message-flags -type MessageFlags int - -// Valid MessageFlags values -const ( - MessageFlagsCrossPosted MessageFlags = 1 << iota - MessageFlagsIsCrossPosted - MessageFlagsSupressEmbeds - MessageFlagsSourceMessageDeleted - MessageFlagsUrgent -) - -// File stores info about files you e.g. send in messages. -type File struct { - Name string - ContentType string - Reader io.Reader -} - -// MessageSend stores all parameters you can send with ChannelMessageSendComplex. -type MessageSend struct { - Content string `json:"content,omitempty"` - Embed *MessageEmbed `json:"embed,omitempty"` - TTS bool `json:"tts"` - Files []*File `json:"-"` - AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` - Reference *MessageReference `json:"message_reference,omitempty"` - - // TODO: Remove this when compatibility is not required. - File *File `json:"-"` -} - -// MessageEdit is used to chain parameters via ChannelMessageEditComplex, which -// is also where you should get the instance from. -type MessageEdit struct { - Content *string `json:"content,omitempty"` - Embed *MessageEmbed `json:"embed,omitempty"` - AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` - - ID string - Channel string -} - -// NewMessageEdit returns a MessageEdit struct, initialized -// with the Channel and ID. -func NewMessageEdit(channelID string, messageID string) *MessageEdit { - return &MessageEdit{ - Channel: channelID, - ID: messageID, - } -} - -// SetContent is the same as setting the variable Content, -// except it doesn't take a pointer. -func (m *MessageEdit) SetContent(str string) *MessageEdit { - m.Content = &str - return m -} - -// SetEmbed is a convenience function for setting the embed, -// so you can chain commands. -func (m *MessageEdit) SetEmbed(embed *MessageEmbed) *MessageEdit { - m.Embed = embed - return m -} - -// AllowedMentionType describes the types of mentions used -// in the MessageAllowedMentions type. -type AllowedMentionType string - -// The types of mentions used in MessageAllowedMentions. -const ( - AllowedMentionTypeRoles AllowedMentionType = "roles" - AllowedMentionTypeUsers AllowedMentionType = "users" - AllowedMentionTypeEveryone AllowedMentionType = "everyone" -) - -// MessageAllowedMentions allows the user to specify which mentions -// Discord is allowed to parse in this message. This is useful when -// sending user input as a message, as it prevents unwanted mentions. -// If this type is used, all mentions must be explicitly whitelisted, -// either by putting an AllowedMentionType in the Parse slice -// (allowing all mentions of that type) or, in the case of roles and -// users, explicitly allowing those mentions on an ID-by-ID basis. -// For more information on this functionality, see: -// https://discordapp.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-reference -type MessageAllowedMentions struct { - // The mention types that are allowed to be parsed in this message. - // Please note that this is purposely **not** marked as omitempty, - // so if a zero-value MessageAllowedMentions object is provided no - // mentions will be allowed. - Parse []AllowedMentionType `json:"parse"` - - // A list of role IDs to allow. This cannot be used when specifying - // AllowedMentionTypeRoles in the Parse slice. - Roles []string `json:"roles,omitempty"` - - // A list of user IDs to allow. This cannot be used when specifying - // AllowedMentionTypeUsers in the Parse slice. - Users []string `json:"users,omitempty"` -} - -// A MessageAttachment stores data for message attachments. -type MessageAttachment struct { - ID string `json:"id"` - URL string `json:"url"` - ProxyURL string `json:"proxy_url"` - Filename string `json:"filename"` - Width int `json:"width"` - Height int `json:"height"` - Size int `json:"size"` -} - -// MessageEmbedFooter is a part of a MessageEmbed struct. -type MessageEmbedFooter struct { - Text string `json:"text,omitempty"` - IconURL string `json:"icon_url,omitempty"` - ProxyIconURL string `json:"proxy_icon_url,omitempty"` -} - -// MessageEmbedImage is a part of a MessageEmbed struct. -type MessageEmbedImage struct { - URL string `json:"url,omitempty"` - ProxyURL string `json:"proxy_url,omitempty"` - Width int `json:"width,omitempty"` - Height int `json:"height,omitempty"` -} - -// MessageEmbedThumbnail is a part of a MessageEmbed struct. -type MessageEmbedThumbnail struct { - URL string `json:"url,omitempty"` - ProxyURL string `json:"proxy_url,omitempty"` - Width int `json:"width,omitempty"` - Height int `json:"height,omitempty"` -} - -// MessageEmbedVideo is a part of a MessageEmbed struct. -type MessageEmbedVideo struct { - URL string `json:"url,omitempty"` - Width int `json:"width,omitempty"` - Height int `json:"height,omitempty"` -} - -// MessageEmbedProvider is a part of a MessageEmbed struct. -type MessageEmbedProvider struct { - URL string `json:"url,omitempty"` - Name string `json:"name,omitempty"` -} - -// MessageEmbedAuthor is a part of a MessageEmbed struct. -type MessageEmbedAuthor struct { - URL string `json:"url,omitempty"` - Name string `json:"name,omitempty"` - IconURL string `json:"icon_url,omitempty"` - ProxyIconURL string `json:"proxy_icon_url,omitempty"` -} - -// MessageEmbedField is a part of a MessageEmbed struct. -type MessageEmbedField struct { - Name string `json:"name,omitempty"` - Value string `json:"value,omitempty"` - Inline bool `json:"inline,omitempty"` -} - -// An MessageEmbed stores data for message embeds. -type MessageEmbed struct { - URL string `json:"url,omitempty"` - Type EmbedType `json:"type,omitempty"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - Timestamp string `json:"timestamp,omitempty"` - Color int `json:"color,omitempty"` - Footer *MessageEmbedFooter `json:"footer,omitempty"` - Image *MessageEmbedImage `json:"image,omitempty"` - Thumbnail *MessageEmbedThumbnail `json:"thumbnail,omitempty"` - Video *MessageEmbedVideo `json:"video,omitempty"` - Provider *MessageEmbedProvider `json:"provider,omitempty"` - Author *MessageEmbedAuthor `json:"author,omitempty"` - Fields []*MessageEmbedField `json:"fields,omitempty"` -} - -// EmbedType is the type of embed -// https://discord.com/developers/docs/resources/channel#embed-object-embed-types -type EmbedType string - -// Block of valid EmbedTypes -const ( - EmbedTypeRich EmbedType = "rich" - EmbedTypeImage EmbedType = "image" - EmbedTypeVideo EmbedType = "video" - EmbedTypeGifv EmbedType = "gifv" - EmbedTypeArticle EmbedType = "article" - EmbedTypeLink EmbedType = "link" -) - -// MessageReactions holds a reactions object for a message. -type MessageReactions struct { - Count int `json:"count"` - Me bool `json:"me"` - Emoji *Emoji `json:"emoji"` -} - -// MessageActivity is sent with Rich Presence-related chat embeds -type MessageActivity struct { - Type MessageActivityType `json:"type"` - PartyID string `json:"party_id"` -} - -// MessageActivityType is the type of message activity -type MessageActivityType int - -// Constants for the different types of Message Activity -const ( - MessageActivityTypeJoin MessageActivityType = iota + 1 - MessageActivityTypeSpectate - MessageActivityTypeListen - MessageActivityTypeJoinRequest -) - -// MessageFlag describes an extra feature of the message -type MessageFlag int - -// Constants for the different bit offsets of Message Flags -const ( - // This message has been published to subscribed channels (via Channel Following) - MessageFlagCrossposted MessageFlag = 1 << iota - // This message originated from a message in another channel (via Channel Following) - MessageFlagIsCrosspost - // Do not include any embeds when serializing this message - MessageFlagSuppressEmbeds -) - -// MessageApplication is sent with Rich Presence-related chat embeds -type MessageApplication struct { - ID string `json:"id"` - CoverImage string `json:"cover_image"` - Description string `json:"description"` - Icon string `json:"icon"` - Name string `json:"name"` -} - -// MessageReference contains reference data sent with crossposted messages -type MessageReference struct { - MessageID string `json:"message_id"` - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id,omitempty"` -} - -// Reference returns MessageReference of given message -func (m *Message) Reference() *MessageReference { - return &MessageReference{ - GuildID: m.GuildID, - ChannelID: m.ChannelID, - MessageID: m.ID, - } -} - -// ContentWithMentionsReplaced will replace all @ mentions with the -// username of the mention. -func (m *Message) ContentWithMentionsReplaced() (content string) { - content = m.Content - - for _, user := range m.Mentions { - content = strings.NewReplacer( - "<@"+user.ID+">", "@"+user.Username, - "<@!"+user.ID+">", "@"+user.Username, - ).Replace(content) - } - return -} - -var patternChannels = regexp.MustCompile("<#[^>]*>") - -// ContentWithMoreMentionsReplaced will replace all @ mentions with the -// username of the mention, but also role IDs and more. -func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, err error) { - content = m.Content - - if !s.StateEnabled { - content = m.ContentWithMentionsReplaced() - return - } - - channel, err := s.State.Channel(m.ChannelID) - if err != nil { - content = m.ContentWithMentionsReplaced() - return - } - - for _, user := range m.Mentions { - nick := user.Username - - member, err := s.State.Member(channel.GuildID, user.ID) - if err == nil && member.Nick != "" { - nick = member.Nick - } - - content = strings.NewReplacer( - "<@"+user.ID+">", "@"+user.Username, - "<@!"+user.ID+">", "@"+nick, - ).Replace(content) - } - for _, roleID := range m.MentionRoles { - role, err := s.State.Role(channel.GuildID, roleID) - if err != nil || !role.Mentionable { - continue - } - - content = strings.Replace(content, "<@&"+role.ID+">", "@"+role.Name, -1) - } - - content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string { - channel, err := s.State.Channel(mention[2 : len(mention)-1]) - if err != nil || channel.Type == ChannelTypeGuildVoice { - return mention - } - - return "#" + channel.Name - }) - return -} diff --git a/vendor/github.com/matterbridge/discordgo/mkdocs.yml b/vendor/github.com/matterbridge/discordgo/mkdocs.yml deleted file mode 100644 index 3ee8eb37..00000000 --- a/vendor/github.com/matterbridge/discordgo/mkdocs.yml +++ /dev/null @@ -1,17 +0,0 @@ -site_name: DiscordGo -site_author: Bruce Marriner -site_url: http://bwmarrin.github.io/discordgo/ -repo_url: https://github.com/bwmarrin/discordgo - -dev_addr: 0.0.0.0:8000 -theme: yeti - -markdown_extensions: - - smarty - - toc: - permalink: True - - sane_lists - -pages: - - 'Home': 'index.md' - - 'Getting Started': 'GettingStarted.md' diff --git a/vendor/github.com/matterbridge/discordgo/oauth2.go b/vendor/github.com/matterbridge/discordgo/oauth2.go deleted file mode 100644 index 289eca94..00000000 --- a/vendor/github.com/matterbridge/discordgo/oauth2.go +++ /dev/null @@ -1,173 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains functions related to Discord OAuth2 endpoints - -package discordgo - -// ------------------------------------------------------------------------------------------------ -// Code specific to Discord OAuth2 Applications -// ------------------------------------------------------------------------------------------------ - -// The MembershipState represents whether the user is in the team or has been invited into it -type MembershipState int - -// Constants for the different stages of the MembershipState -const ( - MembershipStateInvited MembershipState = iota + 1 - MembershipStateAccepted -) - -// A TeamMember struct stores values for a single Team Member, extending the normal User data - note that the user field is partial -type TeamMember struct { - User *User `json:"user"` - TeamID string `json:"team_id"` - MembershipState MembershipState `json:"membership_state"` - Permissions []string `json:"permissions"` -} - -// A Team struct stores the members of a Discord Developer Team as well as some metadata about it -type Team struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Icon string `json:"icon"` - OwnerID string `json:"owner_user_id"` - Members []*TeamMember `json:"members"` -} - -// An Application struct stores values for a Discord OAuth2 Application -type Application struct { - ID string `json:"id,omitempty"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Icon string `json:"icon,omitempty"` - Secret string `json:"secret,omitempty"` - RedirectURIs *[]string `json:"redirect_uris,omitempty"` - BotRequireCodeGrant bool `json:"bot_require_code_grant,omitempty"` - BotPublic bool `json:"bot_public,omitempty"` - RPCApplicationState int `json:"rpc_application_state,omitempty"` - Flags int `json:"flags,omitempty"` - Owner *User `json:"owner"` - Bot *User `json:"bot"` - Team *Team `json:"team"` -} - -// Application returns an Application structure of a specific Application -// appID : The ID of an Application -func (s *Session) Application(appID string) (st *Application, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointApplication(appID), nil, EndpointApplication("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// Applications returns all applications for the authenticated user -func (s *Session) Applications() (st []*Application, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointApplications, nil, EndpointApplications) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ApplicationCreate creates a new Application -// name : Name of Application / Bot -// uris : Redirect URIs (Not required) -func (s *Session) ApplicationCreate(ap *Application) (st *Application, err error) { - - data := struct { - Name string `json:"name"` - Description string `json:"description"` - RedirectURIs *[]string `json:"redirect_uris,omitempty"` - }{ap.Name, ap.Description, ap.RedirectURIs} - - body, err := s.RequestWithBucketID("POST", EndpointApplications, data, EndpointApplications) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ApplicationUpdate updates an existing Application -// var : desc -func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Application, err error) { - - data := struct { - Name string `json:"name"` - Description string `json:"description"` - RedirectURIs *[]string `json:"redirect_uris,omitempty"` - }{ap.Name, ap.Description, ap.RedirectURIs} - - body, err := s.RequestWithBucketID("PUT", EndpointApplication(appID), data, EndpointApplication("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ApplicationDelete deletes an existing Application -// appID : The ID of an Application -func (s *Session) ApplicationDelete(appID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointApplication(appID), nil, EndpointApplication("")) - if err != nil { - return - } - - return -} - -// Asset struct stores values for an asset of an application -type Asset struct { - Type int `json:"type"` - ID string `json:"id"` - Name string `json:"name"` -} - -// ApplicationAssets returns an application's assets -func (s *Session) ApplicationAssets(appID string) (ass []*Asset, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointApplicationAssets(appID), nil, EndpointApplicationAssets("")) - if err != nil { - return - } - - err = unmarshal(body, &ass) - return -} - -// ------------------------------------------------------------------------------------------------ -// Code specific to Discord OAuth2 Application Bots -// ------------------------------------------------------------------------------------------------ - -// ApplicationBotCreate creates an Application Bot Account -// -// appID : The ID of an Application -// -// NOTE: func name may change, if I can think up something better. -func (s *Session) ApplicationBotCreate(appID string) (st *User, err error) { - - body, err := s.RequestWithBucketID("POST", EndpointApplicationsBot(appID), nil, EndpointApplicationsBot("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} diff --git a/vendor/github.com/matterbridge/discordgo/ratelimit.go b/vendor/github.com/matterbridge/discordgo/ratelimit.go deleted file mode 100644 index cd96eadf..00000000 --- a/vendor/github.com/matterbridge/discordgo/ratelimit.go +++ /dev/null @@ -1,197 +0,0 @@ -package discordgo - -import ( - "math" - "net/http" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" -) - -// customRateLimit holds information for defining a custom rate limit -type customRateLimit struct { - suffix string - requests int - reset time.Duration -} - -// RateLimiter holds all ratelimit buckets -type RateLimiter struct { - sync.Mutex - global *int64 - buckets map[string]*Bucket - globalRateLimit time.Duration - customRateLimits []*customRateLimit -} - -// NewRatelimiter returns a new RateLimiter -func NewRatelimiter() *RateLimiter { - - return &RateLimiter{ - buckets: make(map[string]*Bucket), - global: new(int64), - customRateLimits: []*customRateLimit{ - &customRateLimit{ - suffix: "//reactions//", - requests: 1, - reset: 200 * time.Millisecond, - }, - }, - } -} - -// GetBucket retrieves or creates a bucket -func (r *RateLimiter) GetBucket(key string) *Bucket { - r.Lock() - defer r.Unlock() - - if bucket, ok := r.buckets[key]; ok { - return bucket - } - - b := &Bucket{ - Remaining: 1, - Key: key, - global: r.global, - } - - // Check if there is a custom ratelimit set for this bucket ID. - for _, rl := range r.customRateLimits { - if strings.HasSuffix(b.Key, rl.suffix) { - b.customRateLimit = rl - break - } - } - - r.buckets[key] = b - return b -} - -// GetWaitTime returns the duration you should wait for a Bucket -func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration { - // If we ran out of calls and the reset time is still ahead of us - // then we need to take it easy and relax a little - if b.Remaining < minRemaining && b.reset.After(time.Now()) { - return b.reset.Sub(time.Now()) - } - - // Check for global ratelimits - sleepTo := time.Unix(0, atomic.LoadInt64(r.global)) - if now := time.Now(); now.Before(sleepTo) { - return sleepTo.Sub(now) - } - - return 0 -} - -// LockBucket Locks until a request can be made -func (r *RateLimiter) LockBucket(bucketID string) *Bucket { - return r.LockBucketObject(r.GetBucket(bucketID)) -} - -// LockBucketObject Locks an already resolved bucket until a request can be made -func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket { - b.Lock() - - if wait := r.GetWaitTime(b, 1); wait > 0 { - time.Sleep(wait) - } - - b.Remaining-- - return b -} - -// Bucket represents a ratelimit bucket, each bucket gets ratelimited individually (-global ratelimits) -type Bucket struct { - sync.Mutex - Key string - Remaining int - limit int - reset time.Time - global *int64 - - lastReset time.Time - customRateLimit *customRateLimit - Userdata interface{} -} - -// Release unlocks the bucket and reads the headers to update the buckets ratelimit info -// and locks up the whole thing in case if there's a global ratelimit. -func (b *Bucket) Release(headers http.Header) error { - defer b.Unlock() - - // Check if the bucket uses a custom ratelimiter - if rl := b.customRateLimit; rl != nil { - if time.Now().Sub(b.lastReset) >= rl.reset { - b.Remaining = rl.requests - 1 - b.lastReset = time.Now() - } - if b.Remaining < 1 { - b.reset = time.Now().Add(rl.reset) - } - return nil - } - - if headers == nil { - return nil - } - - remaining := headers.Get("X-RateLimit-Remaining") - reset := headers.Get("X-RateLimit-Reset") - global := headers.Get("X-RateLimit-Global") - resetAfter := headers.Get("X-RateLimit-Reset-After") - - // Update global and per bucket reset time if the proper headers are available - // If global is set, then it will block all buckets until after Retry-After - // If Retry-After without global is provided it will use that for the new reset - // time since it's more accurate than X-RateLimit-Reset. - // If Retry-After after is not proided, it will update the reset time from X-RateLimit-Reset - if resetAfter != "" { - parsedAfter, err := strconv.ParseFloat(resetAfter, 64) - if err != nil { - return err - } - - whole, frac := math.Modf(parsedAfter) - resetAt := time.Now().Add(time.Duration(whole) * time.Second).Add(time.Duration(frac*1000) * time.Millisecond) - - // Lock either this single bucket or all buckets - if global != "" { - atomic.StoreInt64(b.global, resetAt.UnixNano()) - } else { - b.reset = resetAt - } - } else if reset != "" { - // Calculate the reset time by using the date header returned from discord - discordTime, err := http.ParseTime(headers.Get("Date")) - if err != nil { - return err - } - - unix, err := strconv.ParseFloat(reset, 64) - if err != nil { - return err - } - - // Calculate the time until reset and add it to the current local time - // some extra time is added because without it i still encountered 429's. - // The added amount is the lowest amount that gave no 429's - // in 1k requests - whole, frac := math.Modf(unix) - delta := time.Unix(int64(whole), 0).Add(time.Duration(frac*1000)*time.Millisecond).Sub(discordTime) + time.Millisecond*250 - b.reset = time.Now().Add(delta) - } - - // Udpate remaining if header is present - if remaining != "" { - parsedRemaining, err := strconv.ParseInt(remaining, 10, 32) - if err != nil { - return err - } - b.Remaining = int(parsedRemaining) - } - - return nil -} diff --git a/vendor/github.com/matterbridge/discordgo/restapi.go b/vendor/github.com/matterbridge/discordgo/restapi.go deleted file mode 100644 index 879a554c..00000000 --- a/vendor/github.com/matterbridge/discordgo/restapi.go +++ /dev/null @@ -1,2387 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains functions for interacting with the Discord REST/JSON API -// at the lowest level. - -package discordgo - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "image" - _ "image/jpeg" // For JPEG decoding - _ "image/png" // For PNG decoding - "io" - "io/ioutil" - "log" - "mime/multipart" - "net/http" - "net/textproto" - "net/url" - "strconv" - "strings" - "time" -) - -// All error constants -var ( - ErrJSONUnmarshal = errors.New("json unmarshal") - ErrStatusOffline = errors.New("You can't set your Status to offline") - ErrVerificationLevelBounds = errors.New("VerificationLevel out of bounds, should be between 0 and 3") - ErrPruneDaysBounds = errors.New("the number of days should be more than or equal to 1") - ErrGuildNoIcon = errors.New("guild does not have an icon set") - ErrGuildNoSplash = errors.New("guild does not have a splash set") - ErrUnauthorized = errors.New("HTTP request was unauthorized. This could be because the provided token was not a bot token. Please add \"Bot \" to the start of your token. https://discord.com/developers/docs/reference#authentication-example-bot-token-authorization-header") -) - -// Request is the same as RequestWithBucketID but the bucket id is the same as the urlStr -func (s *Session) Request(method, urlStr string, data interface{}) (response []byte, err error) { - return s.RequestWithBucketID(method, urlStr, data, strings.SplitN(urlStr, "?", 2)[0]) -} - -// RequestWithBucketID makes a (GET/POST/...) Requests to Discord REST API with JSON data. -func (s *Session) RequestWithBucketID(method, urlStr string, data interface{}, bucketID string) (response []byte, err error) { - var body []byte - if data != nil { - body, err = json.Marshal(data) - if err != nil { - return - } - } - - return s.request(method, urlStr, "application/json", body, bucketID, 0) -} - -// request makes a (GET/POST/...) Requests to Discord REST API. -// Sequence is the sequence number, if it fails with a 502 it will -// retry with sequence+1 until it either succeeds or sequence >= session.MaxRestRetries -func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID string, sequence int) (response []byte, err error) { - if bucketID == "" { - bucketID = strings.SplitN(urlStr, "?", 2)[0] - } - return s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucket(bucketID), sequence) -} - -// RequestWithLockedBucket makes a request using a bucket that's already been locked -func (s *Session) RequestWithLockedBucket(method, urlStr, contentType string, b []byte, bucket *Bucket, sequence int) (response []byte, err error) { - if s.Debug { - log.Printf("API REQUEST %8s :: %s\n", method, urlStr) - log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b)) - } - - req, err := http.NewRequest(method, urlStr, bytes.NewBuffer(b)) - if err != nil { - bucket.Release(nil) - return - } - - // Not used on initial login.. - // TODO: Verify if a login, otherwise complain about no-token - if s.Token != "" { - req.Header.Set("authorization", s.Token) - } - - // Discord's API returns a 400 Bad Request is Content-Type is set, but the - // request body is empty. - if b != nil { - req.Header.Set("Content-Type", contentType) - } - - // TODO: Make a configurable static variable. - req.Header.Set("User-Agent", s.UserAgent) - - if s.Debug { - for k, v := range req.Header { - log.Printf("API REQUEST HEADER :: [%s] = %+v\n", k, v) - } - } - - resp, err := s.Client.Do(req) - if err != nil { - bucket.Release(nil) - return - } - defer func() { - err2 := resp.Body.Close() - if err2 != nil { - log.Println("error closing resp body") - } - }() - - err = bucket.Release(resp.Header) - if err != nil { - return - } - - response, err = ioutil.ReadAll(resp.Body) - if err != nil { - return - } - - if s.Debug { - - log.Printf("API RESPONSE STATUS :: %s\n", resp.Status) - for k, v := range resp.Header { - log.Printf("API RESPONSE HEADER :: [%s] = %+v\n", k, v) - } - log.Printf("API RESPONSE BODY :: [%s]\n\n\n", response) - } - - switch resp.StatusCode { - case http.StatusOK: - case http.StatusCreated: - case http.StatusNoContent: - case http.StatusBadGateway: - // Retry sending request if possible - if sequence < s.MaxRestRetries { - - s.log(LogInformational, "%s Failed (%s), Retrying...", urlStr, resp.Status) - response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence+1) - } else { - err = fmt.Errorf("Exceeded Max retries HTTP %s, %s", resp.Status, response) - } - case 429: // TOO MANY REQUESTS - Rate limiting - rl := TooManyRequests{} - err = json.Unmarshal(response, &rl) - if err != nil { - s.log(LogError, "rate limit unmarshal error, %s", err) - return - } - s.log(LogInformational, "Rate Limiting %s, retry in %d", urlStr, rl.RetryAfter) - s.handleEvent(rateLimitEventType, &RateLimit{TooManyRequests: &rl, URL: urlStr}) - - time.Sleep(rl.RetryAfter * time.Millisecond) - // we can make the above smarter - // this method can cause longer delays than required - - response, err = s.RequestWithLockedBucket(method, urlStr, contentType, b, s.Ratelimiter.LockBucketObject(bucket), sequence) - case http.StatusUnauthorized: - if strings.Index(s.Token, "Bot ") != 0 { - s.log(LogInformational, ErrUnauthorized.Error()) - err = ErrUnauthorized - } - fallthrough - default: // Error condition - err = newRestError(req, resp, response) - } - - return -} - -func unmarshal(data []byte, v interface{}) error { - err := json.Unmarshal(data, v) - if err != nil { - return ErrJSONUnmarshal - } - - return nil -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Sessions -// ------------------------------------------------------------------------------------------------ - -// Login asks the Discord server for an authentication token. -// -// NOTE: While email/pass authentication is supported by DiscordGo it is -// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token -// and then use that authentication token for all future connections. -// Also, doing any form of automation with a user (non Bot) account may result -// in that account being permanently banned from Discord. -func (s *Session) Login(email, password string) (err error) { - - data := struct { - Email string `json:"email"` - Password string `json:"password"` - }{email, password} - - response, err := s.RequestWithBucketID("POST", EndpointLogin, data, EndpointLogin) - if err != nil { - return - } - - temp := struct { - Token string `json:"token"` - MFA bool `json:"mfa"` - }{} - - err = unmarshal(response, &temp) - if err != nil { - return - } - - s.Token = temp.Token - s.MFA = temp.MFA - return -} - -// Register sends a Register request to Discord, and returns the authentication token -// Note that this account is temporary and should be verified for future use. -// Another option is to save the authentication token external, but this isn't recommended. -func (s *Session) Register(username string) (token string, err error) { - - data := struct { - Username string `json:"username"` - }{username} - - response, err := s.RequestWithBucketID("POST", EndpointRegister, data, EndpointRegister) - if err != nil { - return - } - - temp := struct { - Token string `json:"token"` - }{} - - err = unmarshal(response, &temp) - if err != nil { - return - } - - token = temp.Token - return -} - -// Logout sends a logout request to Discord. -// This does not seem to actually invalidate the token. So you can still -// make API calls even after a Logout. So, it seems almost pointless to -// even use. -func (s *Session) Logout() (err error) { - - // _, err = s.Request("POST", LOGOUT, `{"token": "` + s.Token + `"}`) - - if s.Token == "" { - return - } - - data := struct { - Token string `json:"token"` - }{s.Token} - - _, err = s.RequestWithBucketID("POST", EndpointLogout, data, EndpointLogout) - return -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Users -// ------------------------------------------------------------------------------------------------ - -// User returns the user details of the given userID -// userID : A user ID or "@me" which is a shortcut of current user ID -func (s *Session) User(userID string) (st *User, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointUser(userID), nil, EndpointUsers) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserAvatar is deprecated. Please use UserAvatarDecode -// userID : A user ID or "@me" which is a shortcut of current user ID -func (s *Session) UserAvatar(userID string) (img image.Image, err error) { - u, err := s.User(userID) - if err != nil { - return - } - img, err = s.UserAvatarDecode(u) - return -} - -// UserAvatarDecode returns an image.Image of a user's Avatar -// user : The user which avatar should be retrieved -func (s *Session) UserAvatarDecode(u *User) (img image.Image, err error) { - body, err := s.RequestWithBucketID("GET", EndpointUserAvatar(u.ID, u.Avatar), nil, EndpointUserAvatar("", "")) - if err != nil { - return - } - - img, _, err = image.Decode(bytes.NewReader(body)) - return -} - -// UserUpdate updates a users settings. -func (s *Session) UserUpdate(email, password, username, avatar, newPassword string) (st *User, err error) { - - // NOTE: Avatar must be either the hash/id of existing Avatar or - // data:image/png;base64,BASE64_STRING_OF_NEW_AVATAR_PNG - // to set a new avatar. - // If left blank, avatar will be set to null/blank - - data := struct { - Email string `json:"email,omitempty"` - Password string `json:"password,omitempty"` - Username string `json:"username,omitempty"` - Avatar string `json:"avatar,omitempty"` - NewPassword string `json:"new_password,omitempty"` - }{email, password, username, avatar, newPassword} - - body, err := s.RequestWithBucketID("PATCH", EndpointUser("@me"), data, EndpointUsers) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserSettings returns the settings for a given user -func (s *Session) UserSettings() (st *Settings, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointUserSettings("@me"), nil, EndpointUserSettings("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserUpdateStatus update the user status -// status : The new status (Actual valid status are 'online','idle','dnd','invisible') -func (s *Session) UserUpdateStatus(status Status) (st *Settings, err error) { - if status == StatusOffline { - err = ErrStatusOffline - return - } - - data := struct { - Status Status `json:"status"` - }{status} - - body, err := s.RequestWithBucketID("PATCH", EndpointUserSettings("@me"), data, EndpointUserSettings("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserConnections returns the user's connections -func (s *Session) UserConnections() (conn []*UserConnection, err error) { - response, err := s.RequestWithBucketID("GET", EndpointUserConnections("@me"), nil, EndpointUserConnections("@me")) - if err != nil { - return nil, err - } - - err = unmarshal(response, &conn) - if err != nil { - return - } - - return -} - -// UserChannels returns an array of Channel structures for all private -// channels. -func (s *Session) UserChannels() (st []*Channel, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointUserChannels("@me"), nil, EndpointUserChannels("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserChannelCreate creates a new User (Private) Channel with another User -// recipientID : A user ID for the user to which this channel is opened with. -func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) { - - data := struct { - RecipientID string `json:"recipient_id"` - }{recipientID} - - body, err := s.RequestWithBucketID("POST", EndpointUserChannels("@me"), data, EndpointUserChannels("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserGuilds returns an array of UserGuild structures for all guilds. -// limit : The number guilds that can be returned. (max 100) -// beforeID : If provided all guilds returned will be before given ID. -// afterID : If provided all guilds returned will be after given ID. -func (s *Session) UserGuilds(limit int, beforeID, afterID string) (st []*UserGuild, err error) { - - v := url.Values{} - - if limit > 0 { - v.Set("limit", strconv.Itoa(limit)) - } - if afterID != "" { - v.Set("after", afterID) - } - if beforeID != "" { - v.Set("before", beforeID) - } - - uri := EndpointUserGuilds("@me") - - if len(v) > 0 { - uri += "?" + v.Encode() - } - - body, err := s.RequestWithBucketID("GET", uri, nil, EndpointUserGuilds("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserGuildSettingsEdit Edits the users notification settings for a guild -// guildID : The ID of the guild to edit the settings on -// settings : The settings to update -func (s *Session) UserGuildSettingsEdit(guildID string, settings *UserGuildSettingsEdit) (st *UserGuildSettings, err error) { - - body, err := s.RequestWithBucketID("PATCH", EndpointUserGuildSettings("@me", guildID), settings, EndpointUserGuildSettings("", guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// UserChannelPermissions returns the permission of a user in a channel. -// userID : The ID of the user to calculate permissions for. -// channelID : The ID of the channel to calculate permission for. -// -// NOTE: This function is now deprecated and will be removed in the future. -// Please see the same function inside state.go -func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions int64, err error) { - // Try to just get permissions from state. - apermissions, err = s.State.UserChannelPermissions(userID, channelID) - if err == nil { - return - } - - // Otherwise try get as much data from state as possible, falling back to the network. - channel, err := s.State.Channel(channelID) - if err != nil || channel == nil { - channel, err = s.Channel(channelID) - if err != nil { - return - } - } - - guild, err := s.State.Guild(channel.GuildID) - if err != nil || guild == nil { - guild, err = s.Guild(channel.GuildID) - if err != nil { - return - } - } - - if userID == guild.OwnerID { - apermissions = PermissionAll - return - } - - member, err := s.State.Member(guild.ID, userID) - if err != nil || member == nil { - member, err = s.GuildMember(guild.ID, userID) - if err != nil { - return - } - } - - return memberPermissions(guild, channel, userID, member.Roles), nil -} - -// Calculates the permissions for a member. -// https://support.discord.com/hc/en-us/articles/206141927-How-is-the-permission-hierarchy-structured- -func memberPermissions(guild *Guild, channel *Channel, userID string, roles []string) (apermissions int64) { - if userID == guild.OwnerID { - apermissions = PermissionAll - return - } - - for _, role := range guild.Roles { - if role.ID == guild.ID { - apermissions |= role.Permissions - break - } - } - - for _, role := range guild.Roles { - for _, roleID := range roles { - if role.ID == roleID { - apermissions |= role.Permissions - break - } - } - } - - if apermissions&PermissionAdministrator == PermissionAdministrator { - apermissions |= PermissionAll - } - - // Apply @everyone overrides from the channel. - for _, overwrite := range channel.PermissionOverwrites { - if guild.ID == overwrite.ID { - apermissions &= ^overwrite.Deny - apermissions |= overwrite.Allow - break - } - } - - var denies, allows int64 - // Member overwrites can override role overrides, so do two passes - for _, overwrite := range channel.PermissionOverwrites { - for _, roleID := range roles { - if overwrite.Type == PermissionOverwriteTypeRole && roleID == overwrite.ID { - denies |= overwrite.Deny - allows |= overwrite.Allow - break - } - } - } - - apermissions &= ^denies - apermissions |= allows - - for _, overwrite := range channel.PermissionOverwrites { - if overwrite.Type == PermissionOverwriteTypeMember && overwrite.ID == userID { - apermissions &= ^overwrite.Deny - apermissions |= overwrite.Allow - break - } - } - - if apermissions&PermissionAdministrator == PermissionAdministrator { - apermissions |= PermissionAllChannel - } - - return apermissions -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Guilds -// ------------------------------------------------------------------------------------------------ - -// Guild returns a Guild structure of a specific Guild. -// guildID : The ID of a Guild -func (s *Session) Guild(guildID string) (st *Guild, err error) { - body, err := s.RequestWithBucketID("GET", EndpointGuild(guildID), nil, EndpointGuild(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildCreate creates a new Guild -// name : A name for the Guild (2-100 characters) -func (s *Session) GuildCreate(name string) (st *Guild, err error) { - - data := struct { - Name string `json:"name"` - }{name} - - body, err := s.RequestWithBucketID("POST", EndpointGuildCreate, data, EndpointGuildCreate) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildEdit edits a new Guild -// guildID : The ID of a Guild -// g : A GuildParams struct with the values Name, Region and VerificationLevel defined. -func (s *Session) GuildEdit(guildID string, g GuildParams) (st *Guild, err error) { - - // Bounds checking for VerificationLevel, interval: [0, 4] - if g.VerificationLevel != nil { - val := *g.VerificationLevel - if val < 0 || val > 4 { - err = ErrVerificationLevelBounds - return - } - } - - //Bounds checking for regions - if g.Region != "" { - isValid := false - regions, _ := s.VoiceRegions() - for _, r := range regions { - if g.Region == r.ID { - isValid = true - } - } - if !isValid { - var valid []string - for _, r := range regions { - valid = append(valid, r.ID) - } - err = fmt.Errorf("Region not a valid region (%q)", valid) - return - } - } - - body, err := s.RequestWithBucketID("PATCH", EndpointGuild(guildID), g, EndpointGuild(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildDelete deletes a Guild. -// guildID : The ID of a Guild -func (s *Session) GuildDelete(guildID string) (st *Guild, err error) { - - body, err := s.RequestWithBucketID("DELETE", EndpointGuild(guildID), nil, EndpointGuild(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildLeave leaves a Guild. -// guildID : The ID of a Guild -func (s *Session) GuildLeave(guildID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointUserGuild("@me", guildID), nil, EndpointUserGuild("", guildID)) - return -} - -// GuildBans returns an array of GuildBan structures for all bans of a -// given guild. -// guildID : The ID of a Guild. -func (s *Session) GuildBans(guildID string) (st []*GuildBan, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointGuildBans(guildID), nil, EndpointGuildBans(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// GuildBanCreate bans the given user from the given guild. -// guildID : The ID of a Guild. -// userID : The ID of a User -// days : The number of days of previous comments to delete. -func (s *Session) GuildBanCreate(guildID, userID string, days int) (err error) { - return s.GuildBanCreateWithReason(guildID, userID, "", days) -} - -// GuildBan finds ban by given guild and user id and returns GuildBan structure -func (s *Session) GuildBan(guildID, userID string) (st *GuildBan, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointGuildBan(guildID, userID), nil, EndpointGuildBan(guildID, userID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// GuildBanCreateWithReason bans the given user from the given guild also providing a reaso. -// guildID : The ID of a Guild. -// userID : The ID of a User -// reason : The reason for this ban -// days : The number of days of previous comments to delete. -func (s *Session) GuildBanCreateWithReason(guildID, userID, reason string, days int) (err error) { - - uri := EndpointGuildBan(guildID, userID) - - queryParams := url.Values{} - if days > 0 { - queryParams.Set("delete_message_days", strconv.Itoa(days)) - } - if reason != "" { - queryParams.Set("reason", reason) - } - - if len(queryParams) > 0 { - uri += "?" + queryParams.Encode() - } - - _, err = s.RequestWithBucketID("PUT", uri, nil, EndpointGuildBan(guildID, "")) - return -} - -// GuildBanDelete removes the given user from the guild bans -// guildID : The ID of a Guild. -// userID : The ID of a User -func (s *Session) GuildBanDelete(guildID, userID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointGuildBan(guildID, userID), nil, EndpointGuildBan(guildID, "")) - return -} - -// GuildMembers returns a list of members for a guild. -// guildID : The ID of a Guild. -// after : The id of the member to return members after -// limit : max number of members to return (max 1000) -func (s *Session) GuildMembers(guildID string, after string, limit int) (st []*Member, err error) { - - uri := EndpointGuildMembers(guildID) - - v := url.Values{} - - if after != "" { - v.Set("after", after) - } - - if limit > 0 { - v.Set("limit", strconv.Itoa(limit)) - } - - if len(v) > 0 { - uri += "?" + v.Encode() - } - - body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildMembers(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildMember returns a member of a guild. -// guildID : The ID of a Guild. -// userID : The ID of a User -func (s *Session) GuildMember(guildID, userID string) (st *Member, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointGuildMember(guildID, userID), nil, EndpointGuildMember(guildID, "")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildMemberAdd force joins a user to the guild. -// accessToken : Valid access_token for the user. -// guildID : The ID of a Guild. -// userID : The ID of a User. -// nick : Value to set users nickname to -// roles : A list of role ID's to set on the member. -// mute : If the user is muted. -// deaf : If the user is deafened. -func (s *Session) GuildMemberAdd(accessToken, guildID, userID, nick string, roles []string, mute, deaf bool) (err error) { - - data := struct { - AccessToken string `json:"access_token"` - Nick string `json:"nick,omitempty"` - Roles []string `json:"roles,omitempty"` - Mute bool `json:"mute,omitempty"` - Deaf bool `json:"deaf,omitempty"` - }{accessToken, nick, roles, mute, deaf} - - _, err = s.RequestWithBucketID("PUT", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) - if err != nil { - return err - } - - return err -} - -// GuildMemberDelete removes the given user from the given guild. -// guildID : The ID of a Guild. -// userID : The ID of a User -func (s *Session) GuildMemberDelete(guildID, userID string) (err error) { - - return s.GuildMemberDeleteWithReason(guildID, userID, "") -} - -// GuildMemberDeleteWithReason removes the given user from the given guild. -// guildID : The ID of a Guild. -// userID : The ID of a User -// reason : The reason for the kick -func (s *Session) GuildMemberDeleteWithReason(guildID, userID, reason string) (err error) { - - uri := EndpointGuildMember(guildID, userID) - if reason != "" { - uri += "?reason=" + url.QueryEscape(reason) - } - - _, err = s.RequestWithBucketID("DELETE", uri, nil, EndpointGuildMember(guildID, "")) - return -} - -// GuildMemberEdit edits the roles of a member. -// guildID : The ID of a Guild. -// userID : The ID of a User. -// roles : A list of role ID's to set on the member. -func (s *Session) GuildMemberEdit(guildID, userID string, roles []string) (err error) { - - data := struct { - Roles []string `json:"roles"` - }{roles} - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) - return -} - -// GuildMemberMove moves a guild member from one voice channel to another/none -// guildID : The ID of a Guild. -// userID : The ID of a User. -// channelID : The ID of a channel to move user to or nil to remove from voice channel -// NOTE : I am not entirely set on the name of this function and it may change -// prior to the final 1.0.0 release of Discordgo -func (s *Session) GuildMemberMove(guildID string, userID string, channelID *string) (err error) { - data := struct { - ChannelID *string `json:"channel_id"` - }{channelID} - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) - return -} - -// GuildMemberNickname updates the nickname of a guild member -// guildID : The ID of a guild -// userID : The ID of a user -// userID : The ID of a user or "@me" which is a shortcut of the current user ID -// nickname : The nickname of the member, "" will reset their nickname -func (s *Session) GuildMemberNickname(guildID, userID, nickname string) (err error) { - - data := struct { - Nick string `json:"nick"` - }{nickname} - - if userID == "@me" { - userID += "/nick" - } - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) - return -} - -// GuildMemberMute server mutes a guild member -// guildID : The ID of a Guild. -// userID : The ID of a User. -// mute : boolean value for if the user should be muted -func (s *Session) GuildMemberMute(guildID string, userID string, mute bool) (err error) { - data := struct { - Mute bool `json:"mute"` - }{mute} - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) - return -} - -// GuildMemberDeafen server deafens a guild member -// guildID : The ID of a Guild. -// userID : The ID of a User. -// deaf : boolean value for if the user should be deafened -func (s *Session) GuildMemberDeafen(guildID string, userID string, deaf bool) (err error) { - data := struct { - Deaf bool `json:"deaf"` - }{deaf} - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) - return -} - -// GuildMemberRoleAdd adds the specified role to a given member -// guildID : The ID of a Guild. -// userID : The ID of a User. -// roleID : The ID of a Role to be assigned to the user. -func (s *Session) GuildMemberRoleAdd(guildID, userID, roleID string) (err error) { - - _, err = s.RequestWithBucketID("PUT", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, "", "")) - - return -} - -// GuildMemberRoleRemove removes the specified role to a given member -// guildID : The ID of a Guild. -// userID : The ID of a User. -// roleID : The ID of a Role to be removed from the user. -func (s *Session) GuildMemberRoleRemove(guildID, userID, roleID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, "", "")) - - return -} - -// GuildChannels returns an array of Channel structures for all channels of a -// given guild. -// guildID : The ID of a Guild. -func (s *Session) GuildChannels(guildID string) (st []*Channel, err error) { - - body, err := s.request("GET", EndpointGuildChannels(guildID), "", nil, EndpointGuildChannels(guildID), 0) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// GuildChannelCreateData is provided to GuildChannelCreateComplex -type GuildChannelCreateData struct { - Name string `json:"name"` - Type ChannelType `json:"type"` - Topic string `json:"topic,omitempty"` - Bitrate int `json:"bitrate,omitempty"` - UserLimit int `json:"user_limit,omitempty"` - RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` - Position int `json:"position,omitempty"` - PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` - ParentID string `json:"parent_id,omitempty"` - NSFW bool `json:"nsfw,omitempty"` -} - -// GuildChannelCreateComplex creates a new channel in the given guild -// guildID : The ID of a Guild -// data : A data struct describing the new Channel, Name and Type are mandatory, other fields depending on the type -func (s *Session) GuildChannelCreateComplex(guildID string, data GuildChannelCreateData) (st *Channel, err error) { - body, err := s.RequestWithBucketID("POST", EndpointGuildChannels(guildID), data, EndpointGuildChannels(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildChannelCreate creates a new channel in the given guild -// guildID : The ID of a Guild. -// name : Name of the channel (2-100 chars length) -// ctype : Type of the channel -func (s *Session) GuildChannelCreate(guildID, name string, ctype ChannelType) (st *Channel, err error) { - return s.GuildChannelCreateComplex(guildID, GuildChannelCreateData{ - Name: name, - Type: ctype, - }) -} - -// GuildChannelsReorder updates the order of channels in a guild -// guildID : The ID of a Guild. -// channels : Updated channels. -func (s *Session) GuildChannelsReorder(guildID string, channels []*Channel) (err error) { - - data := make([]struct { - ID string `json:"id"` - Position int `json:"position"` - }, len(channels)) - - for i, c := range channels { - data[i].ID = c.ID - data[i].Position = c.Position - } - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildChannels(guildID), data, EndpointGuildChannels(guildID)) - return -} - -// GuildInvites returns an array of Invite structures for the given guild -// guildID : The ID of a Guild. -func (s *Session) GuildInvites(guildID string) (st []*Invite, err error) { - body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInvites(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildRoles returns all roles for a given guild. -// guildID : The ID of a Guild. -func (s *Session) GuildRoles(guildID string) (st []*Role, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointGuildRoles(guildID), nil, EndpointGuildRoles(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return // TODO return pointer -} - -// GuildRoleCreate returns a new Guild Role. -// guildID: The ID of a Guild. -func (s *Session) GuildRoleCreate(guildID string) (st *Role, err error) { - - body, err := s.RequestWithBucketID("POST", EndpointGuildRoles(guildID), nil, EndpointGuildRoles(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// GuildRoleEdit updates an existing Guild Role with new values -// guildID : The ID of a Guild. -// roleID : The ID of a Role. -// name : The name of the Role. -// color : The color of the role (decimal, not hex). -// hoist : Whether to display the role's users separately. -// perm : The permissions for the role. -// mention : Whether this role is mentionable -func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist bool, perm int, mention bool) (st *Role, err error) { - - // Prevent sending a color int that is too big. - if color > 0xFFFFFF { - err = fmt.Errorf("color value cannot be larger than 0xFFFFFF") - return nil, err - } - - data := struct { - Name string `json:"name"` // The role's name (overwrites existing) - Color int `json:"color"` // The color the role should have (as a decimal, not hex) - Hoist bool `json:"hoist"` // Whether to display the role's users separately - Permissions int `json:"permissions"` // The overall permissions number of the role (overwrites existing) - Mentionable bool `json:"mentionable"` // Whether this role is mentionable - }{name, color, hoist, perm, mention} - - body, err := s.RequestWithBucketID("PATCH", EndpointGuildRole(guildID, roleID), data, EndpointGuildRole(guildID, "")) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// GuildRoleReorder reoders guild roles -// guildID : The ID of a Guild. -// roles : A list of ordered roles. -func (s *Session) GuildRoleReorder(guildID string, roles []*Role) (st []*Role, err error) { - - body, err := s.RequestWithBucketID("PATCH", EndpointGuildRoles(guildID), roles, EndpointGuildRoles(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// GuildRoleDelete deletes an existing role. -// guildID : The ID of a Guild. -// roleID : The ID of a Role. -func (s *Session) GuildRoleDelete(guildID, roleID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointGuildRole(guildID, roleID), nil, EndpointGuildRole(guildID, "")) - - return -} - -// GuildPruneCount Returns the number of members that would be removed in a prune operation. -// Requires 'KICK_MEMBER' permission. -// guildID : The ID of a Guild. -// days : The number of days to count prune for (1 or more). -func (s *Session) GuildPruneCount(guildID string, days uint32) (count uint32, err error) { - count = 0 - - if days <= 0 { - err = ErrPruneDaysBounds - return - } - - p := struct { - Pruned uint32 `json:"pruned"` - }{} - - uri := EndpointGuildPrune(guildID) + "?days=" + strconv.FormatUint(uint64(days), 10) - body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildPrune(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &p) - if err != nil { - return - } - - count = p.Pruned - - return -} - -// GuildPrune Begin as prune operation. Requires the 'KICK_MEMBERS' permission. -// Returns an object with one 'pruned' key indicating the number of members that were removed in the prune operation. -// guildID : The ID of a Guild. -// days : The number of days to count prune for (1 or more). -func (s *Session) GuildPrune(guildID string, days uint32) (count uint32, err error) { - - count = 0 - - if days <= 0 { - err = ErrPruneDaysBounds - return - } - - data := struct { - days uint32 - }{days} - - p := struct { - Pruned uint32 `json:"pruned"` - }{} - - body, err := s.RequestWithBucketID("POST", EndpointGuildPrune(guildID), data, EndpointGuildPrune(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &p) - if err != nil { - return - } - - count = p.Pruned - - return -} - -// GuildIntegrations returns an array of Integrations for a guild. -// guildID : The ID of a Guild. -func (s *Session) GuildIntegrations(guildID string) (st []*Integration, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointGuildIntegrations(guildID), nil, EndpointGuildIntegrations(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// GuildIntegrationCreate creates a Guild Integration. -// guildID : The ID of a Guild. -// integrationType : The Integration type. -// integrationID : The ID of an integration. -func (s *Session) GuildIntegrationCreate(guildID, integrationType, integrationID string) (err error) { - - data := struct { - Type string `json:"type"` - ID string `json:"id"` - }{integrationType, integrationID} - - _, err = s.RequestWithBucketID("POST", EndpointGuildIntegrations(guildID), data, EndpointGuildIntegrations(guildID)) - return -} - -// GuildIntegrationEdit edits a Guild Integration. -// guildID : The ID of a Guild. -// integrationType : The Integration type. -// integrationID : The ID of an integration. -// expireBehavior : The behavior when an integration subscription lapses (see the integration object documentation). -// expireGracePeriod : Period (in seconds) where the integration will ignore lapsed subscriptions. -// enableEmoticons : Whether emoticons should be synced for this integration (twitch only currently). -func (s *Session) GuildIntegrationEdit(guildID, integrationID string, expireBehavior, expireGracePeriod int, enableEmoticons bool) (err error) { - - data := struct { - ExpireBehavior int `json:"expire_behavior"` - ExpireGracePeriod int `json:"expire_grace_period"` - EnableEmoticons bool `json:"enable_emoticons"` - }{expireBehavior, expireGracePeriod, enableEmoticons} - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildIntegration(guildID, integrationID), data, EndpointGuildIntegration(guildID, "")) - return -} - -// GuildIntegrationDelete removes the given integration from the Guild. -// guildID : The ID of a Guild. -// integrationID : The ID of an integration. -func (s *Session) GuildIntegrationDelete(guildID, integrationID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointGuildIntegration(guildID, integrationID), nil, EndpointGuildIntegration(guildID, "")) - return -} - -// GuildIntegrationSync syncs an integration. -// guildID : The ID of a Guild. -// integrationID : The ID of an integration. -func (s *Session) GuildIntegrationSync(guildID, integrationID string) (err error) { - - _, err = s.RequestWithBucketID("POST", EndpointGuildIntegrationSync(guildID, integrationID), nil, EndpointGuildIntegration(guildID, "")) - return -} - -// GuildIcon returns an image.Image of a guild icon. -// guildID : The ID of a Guild. -func (s *Session) GuildIcon(guildID string) (img image.Image, err error) { - g, err := s.Guild(guildID) - if err != nil { - return - } - - if g.Icon == "" { - err = ErrGuildNoIcon - return - } - - body, err := s.RequestWithBucketID("GET", EndpointGuildIcon(guildID, g.Icon), nil, EndpointGuildIcon(guildID, "")) - if err != nil { - return - } - - img, _, err = image.Decode(bytes.NewReader(body)) - return -} - -// GuildSplash returns an image.Image of a guild splash image. -// guildID : The ID of a Guild. -func (s *Session) GuildSplash(guildID string) (img image.Image, err error) { - g, err := s.Guild(guildID) - if err != nil { - return - } - - if g.Splash == "" { - err = ErrGuildNoSplash - return - } - - body, err := s.RequestWithBucketID("GET", EndpointGuildSplash(guildID, g.Splash), nil, EndpointGuildSplash(guildID, "")) - if err != nil { - return - } - - img, _, err = image.Decode(bytes.NewReader(body)) - return -} - -// GuildEmbed returns the embed for a Guild. -// guildID : The ID of a Guild. -func (s *Session) GuildEmbed(guildID string) (st *GuildEmbed, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointGuildEmbed(guildID), nil, EndpointGuildEmbed(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildEmbedEdit returns the embed for a Guild. -// guildID : The ID of a Guild. -func (s *Session) GuildEmbedEdit(guildID string, enabled bool, channelID string) (err error) { - - data := GuildEmbed{enabled, channelID} - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildEmbed(guildID), data, EndpointGuildEmbed(guildID)) - return -} - -// GuildAuditLog returns the audit log for a Guild. -// guildID : The ID of a Guild. -// userID : If provided the log will be filtered for the given ID. -// beforeID : If provided all log entries returned will be before the given ID. -// actionType : If provided the log will be filtered for the given Action Type. -// limit : The number messages that can be returned. (default 50, min 1, max 100) -func (s *Session) GuildAuditLog(guildID, userID, beforeID string, actionType, limit int) (st *GuildAuditLog, err error) { - - uri := EndpointGuildAuditLogs(guildID) - - v := url.Values{} - if userID != "" { - v.Set("user_id", userID) - } - if beforeID != "" { - v.Set("before", beforeID) - } - if actionType > 0 { - v.Set("action_type", strconv.Itoa(actionType)) - } - if limit > 0 { - v.Set("limit", strconv.Itoa(limit)) - } - if len(v) > 0 { - uri = fmt.Sprintf("%s?%s", uri, v.Encode()) - } - - body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildAuditLogs(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// GuildEmojis returns all emoji -// guildID : The ID of a Guild. -func (s *Session) GuildEmojis(guildID string) (emoji []*Emoji, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointGuildEmojis(guildID), nil, EndpointGuildEmojis(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &emoji) - return -} - -// GuildEmojiCreate creates a new emoji -// guildID : The ID of a Guild. -// name : The Name of the Emoji. -// image : The base64 encoded emoji image, has to be smaller than 256KB. -// roles : The roles for which this emoji will be whitelisted, can be nil. -func (s *Session) GuildEmojiCreate(guildID, name, image string, roles []string) (emoji *Emoji, err error) { - - data := struct { - Name string `json:"name"` - Image string `json:"image"` - Roles []string `json:"roles,omitempty"` - }{name, image, roles} - - body, err := s.RequestWithBucketID("POST", EndpointGuildEmojis(guildID), data, EndpointGuildEmojis(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &emoji) - return -} - -// GuildEmojiEdit modifies an emoji -// guildID : The ID of a Guild. -// emojiID : The ID of an Emoji. -// name : The Name of the Emoji. -// roles : The roles for which this emoji will be whitelisted, can be nil. -func (s *Session) GuildEmojiEdit(guildID, emojiID, name string, roles []string) (emoji *Emoji, err error) { - - data := struct { - Name string `json:"name"` - Roles []string `json:"roles,omitempty"` - }{name, roles} - - body, err := s.RequestWithBucketID("PATCH", EndpointGuildEmoji(guildID, emojiID), data, EndpointGuildEmojis(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &emoji) - return -} - -// GuildEmojiDelete deletes an Emoji. -// guildID : The ID of a Guild. -// emojiID : The ID of an Emoji. -func (s *Session) GuildEmojiDelete(guildID, emojiID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointGuildEmoji(guildID, emojiID), nil, EndpointGuildEmojis(guildID)) - return -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Channels -// ------------------------------------------------------------------------------------------------ - -// Channel returns a Channel structure of a specific Channel. -// channelID : The ID of the Channel you want returned. -func (s *Session) Channel(channelID string) (st *Channel, err error) { - body, err := s.RequestWithBucketID("GET", EndpointChannel(channelID), nil, EndpointChannel(channelID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelEdit edits the given channel -// channelID : The ID of a Channel -// name : The new name to assign the channel. -func (s *Session) ChannelEdit(channelID, name string) (*Channel, error) { - return s.ChannelEditComplex(channelID, &ChannelEdit{ - Name: name, - }) -} - -// ChannelEditComplex edits an existing channel, replacing the parameters entirely with ChannelEdit struct -// channelID : The ID of a Channel -// data : The channel struct to send -func (s *Session) ChannelEditComplex(channelID string, data *ChannelEdit) (st *Channel, err error) { - body, err := s.RequestWithBucketID("PATCH", EndpointChannel(channelID), data, EndpointChannel(channelID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelDelete deletes the given channel -// channelID : The ID of a Channel -func (s *Session) ChannelDelete(channelID string) (st *Channel, err error) { - - body, err := s.RequestWithBucketID("DELETE", EndpointChannel(channelID), nil, EndpointChannel(channelID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelTyping broadcasts to all members that authenticated user is typing in -// the given channel. -// channelID : The ID of a Channel -func (s *Session) ChannelTyping(channelID string) (err error) { - - _, err = s.RequestWithBucketID("POST", EndpointChannelTyping(channelID), nil, EndpointChannelTyping(channelID)) - return -} - -// ChannelMessages returns an array of Message structures for messages within -// a given channel. -// channelID : The ID of a Channel. -// limit : The number messages that can be returned. (max 100) -// beforeID : If provided all messages returned will be before given ID. -// afterID : If provided all messages returned will be after given ID. -// aroundID : If provided all messages returned will be around given ID. -func (s *Session) ChannelMessages(channelID string, limit int, beforeID, afterID, aroundID string) (st []*Message, err error) { - - uri := EndpointChannelMessages(channelID) - - v := url.Values{} - if limit > 0 { - v.Set("limit", strconv.Itoa(limit)) - } - if afterID != "" { - v.Set("after", afterID) - } - if beforeID != "" { - v.Set("before", beforeID) - } - if aroundID != "" { - v.Set("around", aroundID) - } - if len(v) > 0 { - uri += "?" + v.Encode() - } - - body, err := s.RequestWithBucketID("GET", uri, nil, EndpointChannelMessages(channelID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelMessage gets a single message by ID from a given channel. -// channeld : The ID of a Channel -// messageID : the ID of a Message -func (s *Session) ChannelMessage(channelID, messageID string) (st *Message, err error) { - - response, err := s.RequestWithBucketID("GET", EndpointChannelMessage(channelID, messageID), nil, EndpointChannelMessage(channelID, "")) - if err != nil { - return - } - - err = unmarshal(response, &st) - return -} - -// ChannelMessageAck acknowledges and marks the given message as read -// channeld : The ID of a Channel -// messageID : the ID of a Message -// lastToken : token returned by last ack -func (s *Session) ChannelMessageAck(channelID, messageID, lastToken string) (st *Ack, err error) { - - body, err := s.RequestWithBucketID("POST", EndpointChannelMessageAck(channelID, messageID), &Ack{Token: lastToken}, EndpointChannelMessageAck(channelID, "")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelMessageSend sends a message to the given channel. -// channelID : The ID of a Channel. -// content : The message to send. -func (s *Session) ChannelMessageSend(channelID string, content string) (*Message, error) { - return s.ChannelMessageSendComplex(channelID, &MessageSend{ - Content: content, - }) -} - -var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") - -// ChannelMessageSendComplex sends a message to the given channel. -// channelID : The ID of a Channel. -// data : The message struct to send. -func (s *Session) ChannelMessageSendComplex(channelID string, data *MessageSend) (st *Message, err error) { - if data.Embed != nil && data.Embed.Type == "" { - data.Embed.Type = "rich" - } - - endpoint := EndpointChannelMessages(channelID) - - // TODO: Remove this when compatibility is not required. - files := data.Files - if data.File != nil { - if files == nil { - files = []*File{data.File} - } else { - err = fmt.Errorf("cannot specify both File and Files") - return - } - } - - var response []byte - if len(files) > 0 { - body := &bytes.Buffer{} - bodywriter := multipart.NewWriter(body) - - var payload []byte - payload, err = json.Marshal(data) - if err != nil { - return - } - - var p io.Writer - - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", `form-data; name="payload_json"`) - h.Set("Content-Type", "application/json") - - p, err = bodywriter.CreatePart(h) - if err != nil { - return - } - - if _, err = p.Write(payload); err != nil { - return - } - - for i, file := range files { - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file%d"; filename="%s"`, i, quoteEscaper.Replace(file.Name))) - contentType := file.ContentType - if contentType == "" { - contentType = "application/octet-stream" - } - h.Set("Content-Type", contentType) - - p, err = bodywriter.CreatePart(h) - if err != nil { - return - } - - if _, err = io.Copy(p, file.Reader); err != nil { - return - } - } - - err = bodywriter.Close() - if err != nil { - return - } - - response, err = s.request("POST", endpoint, bodywriter.FormDataContentType(), body.Bytes(), endpoint, 0) - } else { - response, err = s.RequestWithBucketID("POST", endpoint, data, endpoint) - } - if err != nil { - return - } - - err = unmarshal(response, &st) - return -} - -// ChannelMessageSendTTS sends a message to the given channel with Text to Speech. -// channelID : The ID of a Channel. -// content : The message to send. -func (s *Session) ChannelMessageSendTTS(channelID string, content string) (*Message, error) { - return s.ChannelMessageSendComplex(channelID, &MessageSend{ - Content: content, - TTS: true, - }) -} - -// ChannelMessageSendEmbed sends a message to the given channel with embedded data. -// channelID : The ID of a Channel. -// embed : The embed data to send. -func (s *Session) ChannelMessageSendEmbed(channelID string, embed *MessageEmbed) (*Message, error) { - return s.ChannelMessageSendComplex(channelID, &MessageSend{ - Embed: embed, - }) -} - -// ChannelMessageSendReply sends a message to the given channel with reference data. -// channelID : The ID of a Channel. -// content : The message to send. -// reference : The message reference to send. -func (s *Session) ChannelMessageSendReply(channelID string, content string, reference *MessageReference) (*Message, error) { - return s.ChannelMessageSendComplex(channelID, &MessageSend{ - Content: content, - Reference: reference, - }) -} - -// ChannelMessageEdit edits an existing message, replacing it entirely with -// the given content. -// channelID : The ID of a Channel -// messageID : The ID of a Message -// content : The contents of the message -func (s *Session) ChannelMessageEdit(channelID, messageID, content string) (*Message, error) { - return s.ChannelMessageEditComplex(NewMessageEdit(channelID, messageID).SetContent(content)) -} - -// ChannelMessageEditComplex edits an existing message, replacing it entirely with -// the given MessageEdit struct -func (s *Session) ChannelMessageEditComplex(m *MessageEdit) (st *Message, err error) { - if m.Embed != nil && m.Embed.Type == "" { - m.Embed.Type = "rich" - } - - response, err := s.RequestWithBucketID("PATCH", EndpointChannelMessage(m.Channel, m.ID), m, EndpointChannelMessage(m.Channel, "")) - if err != nil { - return - } - - err = unmarshal(response, &st) - return -} - -// ChannelMessageEditEmbed edits an existing message with embedded data. -// channelID : The ID of a Channel -// messageID : The ID of a Message -// embed : The embed data to send -func (s *Session) ChannelMessageEditEmbed(channelID, messageID string, embed *MessageEmbed) (*Message, error) { - return s.ChannelMessageEditComplex(NewMessageEdit(channelID, messageID).SetEmbed(embed)) -} - -// ChannelMessageDelete deletes a message from the Channel. -func (s *Session) ChannelMessageDelete(channelID, messageID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointChannelMessage(channelID, messageID), nil, EndpointChannelMessage(channelID, "")) - return -} - -// ChannelMessagesBulkDelete bulk deletes the messages from the channel for the provided messageIDs. -// If only one messageID is in the slice call channelMessageDelete function. -// If the slice is empty do nothing. -// channelID : The ID of the channel for the messages to delete. -// messages : The IDs of the messages to be deleted. A slice of string IDs. A maximum of 100 messages. -func (s *Session) ChannelMessagesBulkDelete(channelID string, messages []string) (err error) { - - if len(messages) == 0 { - return - } - - if len(messages) == 1 { - err = s.ChannelMessageDelete(channelID, messages[0]) - return - } - - if len(messages) > 100 { - messages = messages[:100] - } - - data := struct { - Messages []string `json:"messages"` - }{messages} - - _, err = s.RequestWithBucketID("POST", EndpointChannelMessagesBulkDelete(channelID), data, EndpointChannelMessagesBulkDelete(channelID)) - return -} - -// ChannelMessagePin pins a message within a given channel. -// channelID: The ID of a channel. -// messageID: The ID of a message. -func (s *Session) ChannelMessagePin(channelID, messageID string) (err error) { - - _, err = s.RequestWithBucketID("PUT", EndpointChannelMessagePin(channelID, messageID), nil, EndpointChannelMessagePin(channelID, "")) - return -} - -// ChannelMessageUnpin unpins a message within a given channel. -// channelID: The ID of a channel. -// messageID: The ID of a message. -func (s *Session) ChannelMessageUnpin(channelID, messageID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointChannelMessagePin(channelID, messageID), nil, EndpointChannelMessagePin(channelID, "")) - return -} - -// ChannelMessagesPinned returns an array of Message structures for pinned messages -// within a given channel -// channelID : The ID of a Channel. -func (s *Session) ChannelMessagesPinned(channelID string) (st []*Message, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointChannelMessagesPins(channelID), nil, EndpointChannelMessagesPins(channelID)) - - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelFileSend sends a file to the given channel. -// channelID : The ID of a Channel. -// name: The name of the file. -// io.Reader : A reader for the file contents. -func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (*Message, error) { - return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}}) -} - -// ChannelFileSendWithMessage sends a file to the given channel with an message. -// DEPRECATED. Use ChannelMessageSendComplex instead. -// channelID : The ID of a Channel. -// content: Optional Message content. -// name: The name of the file. -// io.Reader : A reader for the file contents. -func (s *Session) ChannelFileSendWithMessage(channelID, content string, name string, r io.Reader) (*Message, error) { - return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}, Content: content}) -} - -// ChannelInvites returns an array of Invite structures for the given channel -// channelID : The ID of a Channel -func (s *Session) ChannelInvites(channelID string) (st []*Invite, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointChannelInvites(channelID), nil, EndpointChannelInvites(channelID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelInviteCreate creates a new invite for the given channel. -// channelID : The ID of a Channel -// i : An Invite struct with the values MaxAge, MaxUses and Temporary defined. -func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, err error) { - - data := struct { - MaxAge int `json:"max_age"` - MaxUses int `json:"max_uses"` - Temporary bool `json:"temporary"` - Unique bool `json:"unique"` - }{i.MaxAge, i.MaxUses, i.Temporary, i.Unique} - - body, err := s.RequestWithBucketID("POST", EndpointChannelInvites(channelID), data, EndpointChannelInvites(channelID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelPermissionSet creates a Permission Override for the given channel. -// NOTE: This func name may changed. Using Set instead of Create because -// you can both create a new override or update an override with this function. -func (s *Session) ChannelPermissionSet(channelID, targetID string, targetType PermissionOverwriteType, allow, deny int) (err error) { - - data := struct { - ID string `json:"id"` - Type PermissionOverwriteType `json:"type"` - Allow int `json:"allow"` - Deny int `json:"deny"` - }{targetID, targetType, allow, deny} - - _, err = s.RequestWithBucketID("PUT", EndpointChannelPermission(channelID, targetID), data, EndpointChannelPermission(channelID, "")) - return -} - -// ChannelPermissionDelete deletes a specific permission override for the given channel. -// NOTE: Name of this func may change. -func (s *Session) ChannelPermissionDelete(channelID, targetID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointChannelPermission(channelID, targetID), nil, EndpointChannelPermission(channelID, "")) - return -} - -// ChannelMessageCrosspost cross posts a message in a news channel to followers -// of the channel -// channelID : The ID of a Channel -// messageID : The ID of a Message -func (s *Session) ChannelMessageCrosspost(channelID, messageID string) (st *Message, err error) { - - endpoint := EndpointChannelMessageCrosspost(channelID, messageID) - - body, err := s.RequestWithBucketID("POST", endpoint, nil, endpoint) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ChannelNewsFollow follows a news channel in the targetID -// channelID : The ID of a News Channel -// targetID : The ID of a Channel where the News Channel should post to -func (s *Session) ChannelNewsFollow(channelID, targetID string) (st *ChannelFollow, err error) { - - endpoint := EndpointChannelFollow(channelID) - - data := struct { - WebhookChannelID string `json:"webhook_channel_id"` - }{targetID} - - body, err := s.RequestWithBucketID("POST", endpoint, data, endpoint) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Invites -// ------------------------------------------------------------------------------------------------ - -// Invite returns an Invite structure of the given invite -// inviteID : The invite code -func (s *Session) Invite(inviteID string) (st *Invite, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID), nil, EndpointInvite("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// InviteWithCounts returns an Invite structure of the given invite including approximate member counts -// inviteID : The invite code -func (s *Session) InviteWithCounts(inviteID string) (st *Invite, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID)+"?with_counts=true", nil, EndpointInvite("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// InviteDelete deletes an existing invite -// inviteID : the code of an invite -func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { - - body, err := s.RequestWithBucketID("DELETE", EndpointInvite(inviteID), nil, EndpointInvite("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// InviteAccept accepts an Invite to a Guild or Channel -// inviteID : The invite code -func (s *Session) InviteAccept(inviteID string) (st *Invite, err error) { - - body, err := s.RequestWithBucketID("POST", EndpointInvite(inviteID), nil, EndpointInvite("")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Voice -// ------------------------------------------------------------------------------------------------ - -// VoiceRegions returns the voice server regions -func (s *Session) VoiceRegions() (st []*VoiceRegion, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointVoiceRegions, nil, EndpointVoiceRegions) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// VoiceICE returns the voice server ICE information -func (s *Session) VoiceICE() (st *VoiceICE, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointVoiceIce, nil, EndpointVoiceIce) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Websockets -// ------------------------------------------------------------------------------------------------ - -// Gateway returns the websocket Gateway address -func (s *Session) Gateway() (gateway string, err error) { - - response, err := s.RequestWithBucketID("GET", EndpointGateway, nil, EndpointGateway) - if err != nil { - return - } - - temp := struct { - URL string `json:"url"` - }{} - - err = unmarshal(response, &temp) - if err != nil { - return - } - - gateway = temp.URL - - // Ensure the gateway always has a trailing slash. - // MacOS will fail to connect if we add query params without a trailing slash on the base domain. - if !strings.HasSuffix(gateway, "/") { - gateway += "/" - } - - return -} - -// GatewayBot returns the websocket Gateway address and the recommended number of shards -func (s *Session) GatewayBot() (st *GatewayBotResponse, err error) { - - response, err := s.RequestWithBucketID("GET", EndpointGatewayBot, nil, EndpointGatewayBot) - if err != nil { - return - } - - err = unmarshal(response, &st) - if err != nil { - return - } - - // Ensure the gateway always has a trailing slash. - // MacOS will fail to connect if we add query params without a trailing slash on the base domain. - if !strings.HasSuffix(st.URL, "/") { - st.URL += "/" - } - - return -} - -// Functions specific to Webhooks - -// WebhookCreate returns a new Webhook. -// channelID: The ID of a Channel. -// name : The name of the webhook. -// avatar : The avatar of the webhook. -func (s *Session) WebhookCreate(channelID, name, avatar string) (st *Webhook, err error) { - - data := struct { - Name string `json:"name"` - Avatar string `json:"avatar,omitempty"` - }{name, avatar} - - body, err := s.RequestWithBucketID("POST", EndpointChannelWebhooks(channelID), data, EndpointChannelWebhooks(channelID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// ChannelWebhooks returns all webhooks for a given channel. -// channelID: The ID of a channel. -func (s *Session) ChannelWebhooks(channelID string) (st []*Webhook, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointChannelWebhooks(channelID), nil, EndpointChannelWebhooks(channelID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// GuildWebhooks returns all webhooks for a given guild. -// guildID: The ID of a Guild. -func (s *Session) GuildWebhooks(guildID string) (st []*Webhook, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointGuildWebhooks(guildID), nil, EndpointGuildWebhooks(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// Webhook returns a webhook for a given ID -// webhookID: The ID of a webhook. -func (s *Session) Webhook(webhookID string) (st *Webhook, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointWebhook(webhookID), nil, EndpointWebhooks) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// WebhookWithToken returns a webhook for a given ID -// webhookID: The ID of a webhook. -// token : The auth token for the webhook. -func (s *Session) WebhookWithToken(webhookID, token string) (st *Webhook, err error) { - - body, err := s.RequestWithBucketID("GET", EndpointWebhookToken(webhookID, token), nil, EndpointWebhookToken("", "")) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// WebhookEdit updates an existing Webhook. -// webhookID: The ID of a webhook. -// name : The name of the webhook. -// avatar : The avatar of the webhook. -func (s *Session) WebhookEdit(webhookID, name, avatar, channelID string) (st *Role, err error) { - - data := struct { - Name string `json:"name,omitempty"` - Avatar string `json:"avatar,omitempty"` - ChannelID string `json:"channel_id,omitempty"` - }{name, avatar, channelID} - - body, err := s.RequestWithBucketID("PATCH", EndpointWebhook(webhookID), data, EndpointWebhooks) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// WebhookEditWithToken updates an existing Webhook with an auth token. -// webhookID: The ID of a webhook. -// token : The auth token for the webhook. -// name : The name of the webhook. -// avatar : The avatar of the webhook. -func (s *Session) WebhookEditWithToken(webhookID, token, name, avatar string) (st *Role, err error) { - - data := struct { - Name string `json:"name,omitempty"` - Avatar string `json:"avatar,omitempty"` - }{name, avatar} - - body, err := s.RequestWithBucketID("PATCH", EndpointWebhookToken(webhookID, token), data, EndpointWebhookToken("", "")) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// WebhookDelete deletes a webhook for a given ID -// webhookID: The ID of a webhook. -func (s *Session) WebhookDelete(webhookID string) (err error) { - - _, err = s.RequestWithBucketID("DELETE", EndpointWebhook(webhookID), nil, EndpointWebhooks) - - return -} - -// WebhookDeleteWithToken deletes a webhook for a given ID with an auth token. -// webhookID: The ID of a webhook. -// token : The auth token for the webhook. -func (s *Session) WebhookDeleteWithToken(webhookID, token string) (st *Webhook, err error) { - - body, err := s.RequestWithBucketID("DELETE", EndpointWebhookToken(webhookID, token), nil, EndpointWebhookToken("", "")) - if err != nil { - return - } - - err = unmarshal(body, &st) - - return -} - -// WebhookExecute executes a webhook. -// webhookID: The ID of a webhook. -// token : The auth token for the webhook -// wait : Waits for server confirmation of message send and ensures that the return struct is populated (it is nil otherwise) -// -// If `wait` is `false`, the returned *Message is always empty, because server -// does not provide the response data. -func (s *Session) WebhookExecute(webhookID, token string, wait bool, data *WebhookParams) (st *Message, err error) { - uri := EndpointWebhookToken(webhookID, token) - - if wait { - uri += "?wait=true" - } - - var response []byte - if data.File != nil { - body := &bytes.Buffer{} - bodywriter := multipart.NewWriter(body) - - var payload []byte - payload, err = json.Marshal(data) - if err != nil { - return - } - - var p io.Writer - - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", `form-data; name="payload_json"`) - h.Set("Content-Type", "application/json") - - p, err = bodywriter.CreatePart(h) - if err != nil { - return - } - - if _, err = p.Write(payload); err != nil { - return - } - - { - file := data.File - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file"; filename="%s"`, quoteEscaper.Replace(file.Name))) - contentType := file.ContentType - if contentType == "" { - contentType = "application/octet-stream" - } - h.Set("Content-Type", contentType) - - p, err = bodywriter.CreatePart(h) - if err != nil { - return - } - - if _, err = io.Copy(p, file.Reader); err != nil { - return - } - } - - err = bodywriter.Close() - if err != nil { - return - } - - response, err = s.request("POST", uri, bodywriter.FormDataContentType(), body.Bytes(), EndpointWebhookToken("", ""), 0) - } else { - response, err = s.RequestWithBucketID("POST", uri, data, EndpointWebhookToken("", "")) - } - if err != nil { - return - } - if !wait { - return - } - - err = unmarshal(response, &st) - return -} - -// MessageReactionAdd creates an emoji reaction to a message. -// channelID : The channel ID. -// messageID : The message ID. -// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. -func (s *Session) MessageReactionAdd(channelID, messageID, emojiID string) error { - - // emoji such as #⃣ need to have # escaped - emojiID = strings.Replace(emojiID, "#", "%23", -1) - _, err := s.RequestWithBucketID("PUT", EndpointMessageReaction(channelID, messageID, emojiID, "@me"), nil, EndpointMessageReaction(channelID, "", "", "")) - - return err -} - -// MessageReactionRemove deletes an emoji reaction to a message. -// channelID : The channel ID. -// messageID : The message ID. -// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. -// userID : @me or ID of the user to delete the reaction for. -func (s *Session) MessageReactionRemove(channelID, messageID, emojiID, userID string) error { - - // emoji such as #⃣ need to have # escaped - emojiID = strings.Replace(emojiID, "#", "%23", -1) - _, err := s.RequestWithBucketID("DELETE", EndpointMessageReaction(channelID, messageID, emojiID, userID), nil, EndpointMessageReaction(channelID, "", "", "")) - - return err -} - -// MessageReactionsRemoveAll deletes all reactions from a message -// channelID : The channel ID -// messageID : The message ID. -func (s *Session) MessageReactionsRemoveAll(channelID, messageID string) error { - - _, err := s.RequestWithBucketID("DELETE", EndpointMessageReactionsAll(channelID, messageID), nil, EndpointMessageReactionsAll(channelID, messageID)) - - return err -} - -// MessageReactions gets all the users reactions for a specific emoji. -// channelID : The channel ID. -// messageID : The message ID. -// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. -// limit : max number of users to return (max 100) -// beforeID : If provided all reactions returned will be before given ID. -// afterID : If provided all reactions returned will be after given ID. -func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit int, beforeID, afterID string) (st []*User, err error) { - // emoji such as #⃣ need to have # escaped - emojiID = strings.Replace(emojiID, "#", "%23", -1) - uri := EndpointMessageReactions(channelID, messageID, emojiID) - - v := url.Values{} - - if limit > 0 { - v.Set("limit", strconv.Itoa(limit)) - } - - if afterID != "" { - v.Set("after", afterID) - } - if beforeID != "" { - v.Set("before", beforeID) - } - - if len(v) > 0 { - uri += "?" + v.Encode() - } - - body, err := s.RequestWithBucketID("GET", uri, nil, EndpointMessageReaction(channelID, "", "", "")) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to user notes -// ------------------------------------------------------------------------------------------------ - -// UserNoteSet sets the note for a specific user. -func (s *Session) UserNoteSet(userID string, message string) (err error) { - data := struct { - Note string `json:"note"` - }{message} - - _, err = s.RequestWithBucketID("PUT", EndpointUserNotes(userID), data, EndpointUserNotes("")) - return -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Relationships (Friends list) -// ------------------------------------------------------------------------------------------------ - -// RelationshipsGet returns an array of all the relationships of the user. -func (s *Session) RelationshipsGet() (r []*Relationship, err error) { - body, err := s.RequestWithBucketID("GET", EndpointRelationships(), nil, EndpointRelationships()) - if err != nil { - return - } - - err = unmarshal(body, &r) - return -} - -// relationshipCreate creates a new relationship. (I.e. send or accept a friend request, block a user.) -// relationshipType : 1 = friend, 2 = blocked, 3 = incoming friend req, 4 = sent friend req -func (s *Session) relationshipCreate(userID string, relationshipType int) (err error) { - data := struct { - Type int `json:"type"` - }{relationshipType} - - _, err = s.RequestWithBucketID("PUT", EndpointRelationship(userID), data, EndpointRelationships()) - return -} - -// RelationshipFriendRequestSend sends a friend request to a user. -// userID: ID of the user. -func (s *Session) RelationshipFriendRequestSend(userID string) (err error) { - err = s.relationshipCreate(userID, 4) - return -} - -// RelationshipFriendRequestAccept accepts a friend request from a user. -// userID: ID of the user. -func (s *Session) RelationshipFriendRequestAccept(userID string) (err error) { - err = s.relationshipCreate(userID, 1) - return -} - -// RelationshipUserBlock blocks a user. -// userID: ID of the user. -func (s *Session) RelationshipUserBlock(userID string) (err error) { - err = s.relationshipCreate(userID, 2) - return -} - -// RelationshipDelete removes the relationship with a user. -// userID: ID of the user. -func (s *Session) RelationshipDelete(userID string) (err error) { - _, err = s.RequestWithBucketID("DELETE", EndpointRelationship(userID), nil, EndpointRelationships()) - return -} - -// RelationshipsMutualGet returns an array of all the users both @me and the given user is friends with. -// userID: ID of the user. -func (s *Session) RelationshipsMutualGet(userID string) (mf []*User, err error) { - body, err := s.RequestWithBucketID("GET", EndpointRelationshipsMutual(userID), nil, EndpointRelationshipsMutual(userID)) - if err != nil { - return - } - - err = unmarshal(body, &mf) - return -} diff --git a/vendor/github.com/matterbridge/discordgo/state.go b/vendor/github.com/matterbridge/discordgo/state.go deleted file mode 100644 index 2eeabd80..00000000 --- a/vendor/github.com/matterbridge/discordgo/state.go +++ /dev/null @@ -1,1104 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains code related to state tracking. If enabled, state -// tracking will capture the initial READY packet and many other websocket -// events and maintain an in-memory state of of guilds, channels, users, and -// so forth. This information can be accessed through the Session.State struct. - -package discordgo - -import ( - "errors" - "sort" - "sync" -) - -// ErrNilState is returned when the state is nil. -var ErrNilState = errors.New("state not instantiated, please use discordgo.New() or assign Session.State") - -// ErrStateNotFound is returned when the state cache -// requested is not found -var ErrStateNotFound = errors.New("state cache not found") - -// ErrMessageIncompletePermissions is returned when the message -// requested for permissions does not contain enough data to -// generate the permissions. -var ErrMessageIncompletePermissions = errors.New("message incomplete, unable to determine permissions") - -// A State contains the current known state. -// As discord sends this in a READY blob, it seems reasonable to simply -// use that struct as the data store. -type State struct { - sync.RWMutex - Ready - - // MaxMessageCount represents how many messages per channel the state will store. - MaxMessageCount int - TrackChannels bool - TrackEmojis bool - TrackMembers bool - TrackRoles bool - TrackVoice bool - TrackPresences bool - - guildMap map[string]*Guild - channelMap map[string]*Channel - memberMap map[string]map[string]*Member -} - -// NewState creates an empty state. -func NewState() *State { - return &State{ - Ready: Ready{ - PrivateChannels: []*Channel{}, - Guilds: []*Guild{}, - }, - TrackChannels: true, - TrackEmojis: true, - TrackMembers: true, - TrackRoles: true, - TrackVoice: true, - TrackPresences: true, - guildMap: make(map[string]*Guild), - channelMap: make(map[string]*Channel), - memberMap: make(map[string]map[string]*Member), - } -} - -func (s *State) createMemberMap(guild *Guild) { - members := make(map[string]*Member) - for _, m := range guild.Members { - members[m.User.ID] = m - } - s.memberMap[guild.ID] = members -} - -// GuildAdd adds a guild to the current world state, or -// updates it if it already exists. -func (s *State) GuildAdd(guild *Guild) error { - if s == nil { - return ErrNilState - } - - s.Lock() - defer s.Unlock() - - // Update the channels to point to the right guild, adding them to the channelMap as we go - for _, c := range guild.Channels { - s.channelMap[c.ID] = c - } - - // If this guild contains a new member slice, we must regenerate the member map so the pointers stay valid - if guild.Members != nil { - s.createMemberMap(guild) - } else if _, ok := s.memberMap[guild.ID]; !ok { - // Even if we have no new member slice, we still initialize the member map for this guild if it doesn't exist - s.memberMap[guild.ID] = make(map[string]*Member) - } - - if g, ok := s.guildMap[guild.ID]; ok { - // We are about to replace `g` in the state with `guild`, but first we need to - // make sure we preserve any fields that the `guild` doesn't contain from `g`. - if guild.MemberCount == 0 { - guild.MemberCount = g.MemberCount - } - if guild.Roles == nil { - guild.Roles = g.Roles - } - if guild.Emojis == nil { - guild.Emojis = g.Emojis - } - if guild.Members == nil { - guild.Members = g.Members - } - if guild.Presences == nil { - guild.Presences = g.Presences - } - if guild.Channels == nil { - guild.Channels = g.Channels - } - if guild.VoiceStates == nil { - guild.VoiceStates = g.VoiceStates - } - *g = *guild - return nil - } - - s.Guilds = append(s.Guilds, guild) - s.guildMap[guild.ID] = guild - - return nil -} - -// GuildRemove removes a guild from current world state. -func (s *State) GuildRemove(guild *Guild) error { - if s == nil { - return ErrNilState - } - - _, err := s.Guild(guild.ID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - delete(s.guildMap, guild.ID) - - for i, g := range s.Guilds { - if g.ID == guild.ID { - s.Guilds = append(s.Guilds[:i], s.Guilds[i+1:]...) - return nil - } - } - - return nil -} - -// Guild gets a guild by ID. -// Useful for querying if @me is in a guild: -// _, err := discordgo.Session.State.Guild(guildID) -// isInGuild := err == nil -func (s *State) Guild(guildID string) (*Guild, error) { - if s == nil { - return nil, ErrNilState - } - - s.RLock() - defer s.RUnlock() - - if g, ok := s.guildMap[guildID]; ok { - return g, nil - } - - return nil, ErrStateNotFound -} - -// PresenceAdd adds a presence to the current world state, or -// updates it if it already exists. -func (s *State) PresenceAdd(guildID string, presence *Presence) error { - if s == nil { - return ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - for i, p := range guild.Presences { - if p.User.ID == presence.User.ID { - //guild.Presences[i] = presence - - //Update status - guild.Presences[i].Activities = presence.Activities - if presence.Status != "" { - guild.Presences[i].Status = presence.Status - } - - //Update the optionally sent user information - //ID Is a mandatory field so you should not need to check if it is empty - guild.Presences[i].User.ID = presence.User.ID - - if presence.User.Avatar != "" { - guild.Presences[i].User.Avatar = presence.User.Avatar - } - if presence.User.Discriminator != "" { - guild.Presences[i].User.Discriminator = presence.User.Discriminator - } - if presence.User.Email != "" { - guild.Presences[i].User.Email = presence.User.Email - } - if presence.User.Token != "" { - guild.Presences[i].User.Token = presence.User.Token - } - if presence.User.Username != "" { - guild.Presences[i].User.Username = presence.User.Username - } - - return nil - } - } - - guild.Presences = append(guild.Presences, presence) - return nil -} - -// PresenceRemove removes a presence from the current world state. -func (s *State) PresenceRemove(guildID string, presence *Presence) error { - if s == nil { - return ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - for i, p := range guild.Presences { - if p.User.ID == presence.User.ID { - guild.Presences = append(guild.Presences[:i], guild.Presences[i+1:]...) - return nil - } - } - - return ErrStateNotFound -} - -// Presence gets a presence by ID from a guild. -func (s *State) Presence(guildID, userID string) (*Presence, error) { - if s == nil { - return nil, ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return nil, err - } - - for _, p := range guild.Presences { - if p.User.ID == userID { - return p, nil - } - } - - return nil, ErrStateNotFound -} - -// TODO: Consider moving Guild state update methods onto *Guild. - -// MemberAdd adds a member to the current world state, or -// updates it if it already exists. -func (s *State) MemberAdd(member *Member) error { - if s == nil { - return ErrNilState - } - - guild, err := s.Guild(member.GuildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - members, ok := s.memberMap[member.GuildID] - if !ok { - return ErrStateNotFound - } - - m, ok := members[member.User.ID] - if !ok { - members[member.User.ID] = member - guild.Members = append(guild.Members, member) - } else { - // We are about to replace `m` in the state with `member`, but first we need to - // make sure we preserve any fields that the `member` doesn't contain from `m`. - if member.JoinedAt == "" { - member.JoinedAt = m.JoinedAt - } - *m = *member - } - - return nil -} - -// MemberRemove removes a member from current world state. -func (s *State) MemberRemove(member *Member) error { - if s == nil { - return ErrNilState - } - - guild, err := s.Guild(member.GuildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - members, ok := s.memberMap[member.GuildID] - if !ok { - return ErrStateNotFound - } - - _, ok = members[member.User.ID] - if !ok { - return ErrStateNotFound - } - delete(members, member.User.ID) - - for i, m := range guild.Members { - if m.User.ID == member.User.ID { - guild.Members = append(guild.Members[:i], guild.Members[i+1:]...) - return nil - } - } - - return ErrStateNotFound -} - -// Member gets a member by ID from a guild. -func (s *State) Member(guildID, userID string) (*Member, error) { - if s == nil { - return nil, ErrNilState - } - - s.RLock() - defer s.RUnlock() - - members, ok := s.memberMap[guildID] - if !ok { - return nil, ErrStateNotFound - } - - m, ok := members[userID] - if ok { - return m, nil - } - - return nil, ErrStateNotFound -} - -// RoleAdd adds a role to the current world state, or -// updates it if it already exists. -func (s *State) RoleAdd(guildID string, role *Role) error { - if s == nil { - return ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - for i, r := range guild.Roles { - if r.ID == role.ID { - guild.Roles[i] = role - return nil - } - } - - guild.Roles = append(guild.Roles, role) - return nil -} - -// RoleRemove removes a role from current world state by ID. -func (s *State) RoleRemove(guildID, roleID string) error { - if s == nil { - return ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - for i, r := range guild.Roles { - if r.ID == roleID { - guild.Roles = append(guild.Roles[:i], guild.Roles[i+1:]...) - return nil - } - } - - return ErrStateNotFound -} - -// Role gets a role by ID from a guild. -func (s *State) Role(guildID, roleID string) (*Role, error) { - if s == nil { - return nil, ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return nil, err - } - - s.RLock() - defer s.RUnlock() - - for _, r := range guild.Roles { - if r.ID == roleID { - return r, nil - } - } - - return nil, ErrStateNotFound -} - -// ChannelAdd adds a channel to the current world state, or -// updates it if it already exists. -// Channels may exist either as PrivateChannels or inside -// a guild. -func (s *State) ChannelAdd(channel *Channel) error { - if s == nil { - return ErrNilState - } - - s.Lock() - defer s.Unlock() - - // If the channel exists, replace it - if c, ok := s.channelMap[channel.ID]; ok { - if channel.Messages == nil { - channel.Messages = c.Messages - } - if channel.PermissionOverwrites == nil { - channel.PermissionOverwrites = c.PermissionOverwrites - } - - *c = *channel - return nil - } - - if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM { - s.PrivateChannels = append(s.PrivateChannels, channel) - } else { - guild, ok := s.guildMap[channel.GuildID] - if !ok { - return ErrStateNotFound - } - - guild.Channels = append(guild.Channels, channel) - } - - s.channelMap[channel.ID] = channel - - return nil -} - -// ChannelRemove removes a channel from current world state. -func (s *State) ChannelRemove(channel *Channel) error { - if s == nil { - return ErrNilState - } - - _, err := s.Channel(channel.ID) - if err != nil { - return err - } - - if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM { - s.Lock() - defer s.Unlock() - - for i, c := range s.PrivateChannels { - if c.ID == channel.ID { - s.PrivateChannels = append(s.PrivateChannels[:i], s.PrivateChannels[i+1:]...) - break - } - } - } else { - guild, err := s.Guild(channel.GuildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - for i, c := range guild.Channels { - if c.ID == channel.ID { - guild.Channels = append(guild.Channels[:i], guild.Channels[i+1:]...) - break - } - } - } - - delete(s.channelMap, channel.ID) - - return nil -} - -// GuildChannel gets a channel by ID from a guild. -// This method is Deprecated, use Channel(channelID) -func (s *State) GuildChannel(guildID, channelID string) (*Channel, error) { - return s.Channel(channelID) -} - -// PrivateChannel gets a private channel by ID. -// This method is Deprecated, use Channel(channelID) -func (s *State) PrivateChannel(channelID string) (*Channel, error) { - return s.Channel(channelID) -} - -// Channel gets a channel by ID, it will look in all guilds and private channels. -func (s *State) Channel(channelID string) (*Channel, error) { - if s == nil { - return nil, ErrNilState - } - - s.RLock() - defer s.RUnlock() - - if c, ok := s.channelMap[channelID]; ok { - return c, nil - } - - return nil, ErrStateNotFound -} - -// Emoji returns an emoji for a guild and emoji id. -func (s *State) Emoji(guildID, emojiID string) (*Emoji, error) { - if s == nil { - return nil, ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return nil, err - } - - s.RLock() - defer s.RUnlock() - - for _, e := range guild.Emojis { - if e.ID == emojiID { - return e, nil - } - } - - return nil, ErrStateNotFound -} - -// EmojiAdd adds an emoji to the current world state. -func (s *State) EmojiAdd(guildID string, emoji *Emoji) error { - if s == nil { - return ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - for i, e := range guild.Emojis { - if e.ID == emoji.ID { - guild.Emojis[i] = emoji - return nil - } - } - - guild.Emojis = append(guild.Emojis, emoji) - return nil -} - -// EmojisAdd adds multiple emojis to the world state. -func (s *State) EmojisAdd(guildID string, emojis []*Emoji) error { - for _, e := range emojis { - if err := s.EmojiAdd(guildID, e); err != nil { - return err - } - } - return nil -} - -// MessageAdd adds a message to the current world state, or updates it if it exists. -// If the channel cannot be found, the message is discarded. -// Messages are kept in state up to s.MaxMessageCount per channel. -func (s *State) MessageAdd(message *Message) error { - if s == nil { - return ErrNilState - } - - c, err := s.Channel(message.ChannelID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - // If the message exists, merge in the new message contents. - for _, m := range c.Messages { - if m.ID == message.ID { - if message.Content != "" { - m.Content = message.Content - } - if message.EditedTimestamp != "" { - m.EditedTimestamp = message.EditedTimestamp - } - if message.Mentions != nil { - m.Mentions = message.Mentions - } - if message.Embeds != nil { - m.Embeds = message.Embeds - } - if message.Attachments != nil { - m.Attachments = message.Attachments - } - if message.Timestamp != "" { - m.Timestamp = message.Timestamp - } - if message.Author != nil { - m.Author = message.Author - } - - return nil - } - } - - c.Messages = append(c.Messages, message) - - if len(c.Messages) > s.MaxMessageCount { - c.Messages = c.Messages[len(c.Messages)-s.MaxMessageCount:] - } - return nil -} - -// MessageRemove removes a message from the world state. -func (s *State) MessageRemove(message *Message) error { - if s == nil { - return ErrNilState - } - - return s.messageRemoveByID(message.ChannelID, message.ID) -} - -// messageRemoveByID removes a message by channelID and messageID from the world state. -func (s *State) messageRemoveByID(channelID, messageID string) error { - c, err := s.Channel(channelID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - for i, m := range c.Messages { - if m.ID == messageID { - c.Messages = append(c.Messages[:i], c.Messages[i+1:]...) - return nil - } - } - - return ErrStateNotFound -} - -func (s *State) voiceStateUpdate(update *VoiceStateUpdate) error { - guild, err := s.Guild(update.GuildID) - if err != nil { - return err - } - - s.Lock() - defer s.Unlock() - - // Handle Leaving Channel - if update.ChannelID == "" { - for i, state := range guild.VoiceStates { - if state.UserID == update.UserID { - guild.VoiceStates = append(guild.VoiceStates[:i], guild.VoiceStates[i+1:]...) - return nil - } - } - } else { - for i, state := range guild.VoiceStates { - if state.UserID == update.UserID { - guild.VoiceStates[i] = update.VoiceState - return nil - } - } - - guild.VoiceStates = append(guild.VoiceStates, update.VoiceState) - } - - return nil -} - -// VoiceState gets a VoiceState by guild and user ID. -func (s *State) VoiceState(guildID, userID string) (*VoiceState, error) { - if s == nil { - return nil, ErrNilState - } - - guild, err := s.Guild(guildID) - if err != nil { - return nil, err - } - - for _, state := range guild.VoiceStates { - if state.UserID == userID { - return state, nil - } - } - - return nil, ErrStateNotFound -} - -// Message gets a message by channel and message ID. -func (s *State) Message(channelID, messageID string) (*Message, error) { - if s == nil { - return nil, ErrNilState - } - - c, err := s.Channel(channelID) - if err != nil { - return nil, err - } - - s.RLock() - defer s.RUnlock() - - for _, m := range c.Messages { - if m.ID == messageID { - return m, nil - } - } - - return nil, ErrStateNotFound -} - -// OnReady takes a Ready event and updates all internal state. -func (s *State) onReady(se *Session, r *Ready) (err error) { - if s == nil { - return ErrNilState - } - - s.Lock() - defer s.Unlock() - - // We must track at least the current user for Voice, even - // if state is disabled, store the bare essentials. - if !se.StateEnabled { - ready := Ready{ - Version: r.Version, - SessionID: r.SessionID, - User: r.User, - } - - s.Ready = ready - - return nil - } - - s.Ready = *r - - for _, g := range s.Guilds { - s.guildMap[g.ID] = g - s.createMemberMap(g) - - for _, c := range g.Channels { - s.channelMap[c.ID] = c - } - } - - for _, c := range s.PrivateChannels { - s.channelMap[c.ID] = c - } - - return nil -} - -// OnInterface handles all events related to states. -func (s *State) OnInterface(se *Session, i interface{}) (err error) { - if s == nil { - return ErrNilState - } - - r, ok := i.(*Ready) - if ok { - return s.onReady(se, r) - } - - if !se.StateEnabled { - return nil - } - - switch t := i.(type) { - case *GuildCreate: - err = s.GuildAdd(t.Guild) - case *GuildUpdate: - err = s.GuildAdd(t.Guild) - case *GuildDelete: - err = s.GuildRemove(t.Guild) - case *GuildMemberAdd: - // Updates the MemberCount of the guild. - guild, err := s.Guild(t.Member.GuildID) - if err != nil { - return err - } - guild.MemberCount++ - - // Caches member if tracking is enabled. - if s.TrackMembers { - err = s.MemberAdd(t.Member) - } - case *GuildMemberUpdate: - if s.TrackMembers { - err = s.MemberAdd(t.Member) - } - case *GuildMemberRemove: - // Updates the MemberCount of the guild. - guild, err := s.Guild(t.Member.GuildID) - if err != nil { - return err - } - guild.MemberCount-- - - // Removes member from the cache if tracking is enabled. - if s.TrackMembers { - err = s.MemberRemove(t.Member) - } - case *GuildMembersChunk: - if s.TrackMembers { - for i := range t.Members { - t.Members[i].GuildID = t.GuildID - err = s.MemberAdd(t.Members[i]) - } - } - - if s.TrackPresences { - for _, p := range t.Presences { - err = s.PresenceAdd(t.GuildID, p) - } - } - case *GuildRoleCreate: - if s.TrackRoles { - err = s.RoleAdd(t.GuildID, t.Role) - } - case *GuildRoleUpdate: - if s.TrackRoles { - err = s.RoleAdd(t.GuildID, t.Role) - } - case *GuildRoleDelete: - if s.TrackRoles { - err = s.RoleRemove(t.GuildID, t.RoleID) - } - case *GuildEmojisUpdate: - if s.TrackEmojis { - err = s.EmojisAdd(t.GuildID, t.Emojis) - } - case *ChannelCreate: - if s.TrackChannels { - err = s.ChannelAdd(t.Channel) - } - case *ChannelUpdate: - if s.TrackChannels { - err = s.ChannelAdd(t.Channel) - } - case *ChannelDelete: - if s.TrackChannels { - err = s.ChannelRemove(t.Channel) - } - case *MessageCreate: - if s.MaxMessageCount != 0 { - err = s.MessageAdd(t.Message) - } - case *MessageUpdate: - if s.MaxMessageCount != 0 { - var old *Message - old, err = s.Message(t.ChannelID, t.ID) - if err == nil { - oldCopy := *old - t.BeforeUpdate = &oldCopy - } - - err = s.MessageAdd(t.Message) - } - case *MessageDelete: - if s.MaxMessageCount != 0 { - var old *Message - old, err = s.Message(t.ChannelID, t.ID) - if err == nil { - oldCopy := *old - t.BeforeDelete = &oldCopy - } - - err = s.MessageRemove(t.Message) - } - case *MessageDeleteBulk: - if s.MaxMessageCount != 0 { - for _, mID := range t.Messages { - s.messageRemoveByID(t.ChannelID, mID) - } - } - case *VoiceStateUpdate: - if s.TrackVoice { - var old *VoiceState - old, err = s.VoiceState(t.GuildID, t.UserID) - if err == nil { - oldCopy := *old - t.BeforeUpdate = &oldCopy - } - - err = s.voiceStateUpdate(t) - } - case *PresenceUpdate: - if s.TrackPresences { - s.PresenceAdd(t.GuildID, &t.Presence) - } - if s.TrackMembers { - if t.Status == StatusOffline { - return - } - - var m *Member - m, err = s.Member(t.GuildID, t.User.ID) - - if err != nil { - // Member not found; this is a user coming online - m = &Member{ - GuildID: t.GuildID, - User: t.User, - } - } else { - if t.User.Username != "" { - m.User.Username = t.User.Username - } - } - - err = s.MemberAdd(m) - } - - } - - return -} - -// UserChannelPermissions returns the permission of a user in a channel. -// userID : The ID of the user to calculate permissions for. -// channelID : The ID of the channel to calculate permission for. -func (s *State) UserChannelPermissions(userID, channelID string) (apermissions int64, err error) { - if s == nil { - return 0, ErrNilState - } - - channel, err := s.Channel(channelID) - if err != nil { - return - } - - guild, err := s.Guild(channel.GuildID) - if err != nil { - return - } - - member, err := s.Member(guild.ID, userID) - if err != nil { - return - } - - return memberPermissions(guild, channel, userID, member.Roles), nil -} - -// MessagePermissions returns the permissions of the author of the message -// in the channel in which it was sent. -func (s *State) MessagePermissions(message *Message) (apermissions int64, err error) { - if s == nil { - return 0, ErrNilState - } - - if message.Author == nil || message.Member == nil { - return 0, ErrMessageIncompletePermissions - } - - channel, err := s.Channel(message.ChannelID) - if err != nil { - return - } - - guild, err := s.Guild(channel.GuildID) - if err != nil { - return - } - - return memberPermissions(guild, channel, message.Author.ID, message.Member.Roles), nil -} - -// UserColor returns the color of a user in a channel. -// While colors are defined at a Guild level, determining for a channel is more useful in message handlers. -// 0 is returned in cases of error, which is the color of @everyone. -// userID : The ID of the user to calculate the color for. -// channelID : The ID of the channel to calculate the color for. -func (s *State) UserColor(userID, channelID string) int { - if s == nil { - return 0 - } - - channel, err := s.Channel(channelID) - if err != nil { - return 0 - } - - guild, err := s.Guild(channel.GuildID) - if err != nil { - return 0 - } - - member, err := s.Member(guild.ID, userID) - if err != nil { - return 0 - } - - return firstRoleColorColor(guild, member.Roles) -} - -// MessageColor returns the color of the author's name as displayed -// in the client associated with this message. -func (s *State) MessageColor(message *Message) int { - if s == nil { - return 0 - } - - if message.Member == nil || message.Member.Roles == nil { - return 0 - } - - channel, err := s.Channel(message.ChannelID) - if err != nil { - return 0 - } - - guild, err := s.Guild(channel.GuildID) - if err != nil { - return 0 - } - - return firstRoleColorColor(guild, message.Member.Roles) -} - -func firstRoleColorColor(guild *Guild, memberRoles []string) int { - roles := Roles(guild.Roles) - sort.Sort(roles) - - for _, role := range roles { - for _, roleID := range memberRoles { - if role.ID == roleID { - if role.Color != 0 { - return role.Color - } - } - } - } - - for _, role := range roles { - if role.ID == guild.ID { - return role.Color - } - } - - return 0 -} diff --git a/vendor/github.com/matterbridge/discordgo/structs.go b/vendor/github.com/matterbridge/discordgo/structs.go deleted file mode 100644 index 8cebfdc5..00000000 --- a/vendor/github.com/matterbridge/discordgo/structs.go +++ /dev/null @@ -1,1339 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains all structures for the discordgo package. These -// may be moved about later into separate files but I find it easier to have -// them all located together. - -package discordgo - -import ( - "encoding/json" - "fmt" - "math" - "net/http" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" -) - -// A Session represents a connection to the Discord API. -type Session struct { - sync.RWMutex - - // General configurable settings. - - // Authentication token for this session - // TODO: Remove Below, Deprecated, Use Identify struct - Token string - - MFA bool - - // Debug for printing JSON request/responses - Debug bool // Deprecated, will be removed. - LogLevel int - - // Should the session reconnect the websocket on errors. - ShouldReconnectOnError bool - - // Identify is sent during initial handshake with the discord gateway. - // https://discord.com/developers/docs/topics/gateway#identify - Identify Identify - - // TODO: Remove Below, Deprecated, Use Identify struct - // Should the session request compressed websocket data. - Compress bool - - // Sharding - ShardID int - ShardCount int - - // Should state tracking be enabled. - // State tracking is the best way for getting the the users - // active guilds and the members of the guilds. - StateEnabled bool - - // Whether or not to call event handlers synchronously. - // e.g false = launch event handlers in their own goroutines. - SyncEvents bool - - // Exposed but should not be modified by User. - - // Whether the Data Websocket is ready - DataReady bool // NOTE: Maye be deprecated soon - - // Max number of REST API retries - MaxRestRetries int - - // Status stores the currect status of the websocket connection - // this is being tested, may stay, may go away. - status int32 - - // Whether the Voice Websocket is ready - VoiceReady bool // NOTE: Deprecated. - - // Whether the UDP Connection is ready - UDPReady bool // NOTE: Deprecated - - // Stores a mapping of guild id's to VoiceConnections - VoiceConnections map[string]*VoiceConnection - - // Managed state object, updated internally with events when - // StateEnabled is true. - State *State - - // The http client used for REST requests - Client *http.Client - - // The user agent used for REST APIs - UserAgent string - - // Stores the last HeartbeatAck that was recieved (in UTC) - LastHeartbeatAck time.Time - - // Stores the last Heartbeat sent (in UTC) - LastHeartbeatSent time.Time - - // used to deal with rate limits - Ratelimiter *RateLimiter - - // Event handlers - handlersMu sync.RWMutex - handlers map[string][]*eventHandlerInstance - onceHandlers map[string][]*eventHandlerInstance - - // The websocket connection. - wsConn *websocket.Conn - - // When nil, the session is not listening. - listening chan interface{} - - // sequence tracks the current gateway api websocket sequence number - sequence *int64 - - // stores sessions current Discord Gateway - gateway string - - // stores session ID of current Gateway connection - sessionID string - - // used to make sure gateway websocket writes do not happen concurrently - wsMutex sync.Mutex -} - -// UserConnection is a Connection returned from the UserConnections endpoint -type UserConnection struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Revoked bool `json:"revoked"` - Integrations []*Integration `json:"integrations"` -} - -// Integration stores integration information -type Integration struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Enabled bool `json:"enabled"` - Syncing bool `json:"syncing"` - RoleID string `json:"role_id"` - EnableEmoticons bool `json:"enable_emoticons"` - ExpireBehavior ExpireBehavior `json:"expire_behavior"` - ExpireGracePeriod int `json:"expire_grace_period"` - User *User `json:"user"` - Account IntegrationAccount `json:"account"` - SyncedAt Timestamp `json:"synced_at"` -} - -//ExpireBehavior of Integration -// https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors -type ExpireBehavior int - -// Block of valid ExpireBehaviors -const ( - ExpireBehaviorRemoveRole ExpireBehavior = iota - ExpireBehaviorKick -) - -// IntegrationAccount is integration account information -// sent by the UserConnections endpoint -type IntegrationAccount struct { - ID string `json:"id"` - Name string `json:"name"` -} - -// A VoiceRegion stores data for a specific voice region server. -type VoiceRegion struct { - ID string `json:"id"` - Name string `json:"name"` - Hostname string `json:"sample_hostname"` - Port int `json:"sample_port"` -} - -// A VoiceICE stores data for voice ICE servers. -type VoiceICE struct { - TTL string `json:"ttl"` - Servers []*ICEServer `json:"servers"` -} - -// A ICEServer stores data for a specific voice ICE server. -type ICEServer struct { - URL string `json:"url"` - Username string `json:"username"` - Credential string `json:"credential"` -} - -// A Invite stores all data related to a specific Discord Guild or Channel invite. -type Invite struct { - Guild *Guild `json:"guild"` - Channel *Channel `json:"channel"` - Inviter *User `json:"inviter"` - Code string `json:"code"` - CreatedAt Timestamp `json:"created_at"` - MaxAge int `json:"max_age"` - Uses int `json:"uses"` - MaxUses int `json:"max_uses"` - Revoked bool `json:"revoked"` - Temporary bool `json:"temporary"` - Unique bool `json:"unique"` - TargetUser *User `json:"target_user"` - TargetUserType TargetUserType `json:"target_user_type"` - - // will only be filled when using InviteWithCounts - ApproximatePresenceCount int `json:"approximate_presence_count"` - ApproximateMemberCount int `json:"approximate_member_count"` -} - -// TargetUserType is the type of the target user -// https://discord.com/developers/docs/resources/invite#invite-object-target-user-types -type TargetUserType int - -// Block contains known TargetUserType values -const ( - TargetUserTypeStream TargetUserType = iota -) - -// ChannelType is the type of a Channel -type ChannelType int - -// Block contains known ChannelType values -const ( - ChannelTypeGuildText ChannelType = iota - ChannelTypeDM - ChannelTypeGuildVoice - ChannelTypeGroupDM - ChannelTypeGuildCategory - ChannelTypeGuildNews - ChannelTypeGuildStore -) - -// A Channel holds all data related to an individual Discord channel. -type Channel struct { - // The ID of the channel. - ID string `json:"id"` - - // The ID of the guild to which the channel belongs, if it is in a guild. - // Else, this ID is empty (e.g. DM channels). - GuildID string `json:"guild_id"` - - // The name of the channel. - Name string `json:"name"` - - // The topic of the channel. - Topic string `json:"topic"` - - // The type of the channel. - Type ChannelType `json:"type"` - - // The ID of the last message sent in the channel. This is not - // guaranteed to be an ID of a valid message. - LastMessageID string `json:"last_message_id"` - - // The timestamp of the last pinned message in the channel. - // Empty if the channel has no pinned messages. - LastPinTimestamp Timestamp `json:"last_pin_timestamp"` - - // Whether the channel is marked as NSFW. - NSFW bool `json:"nsfw"` - - // Icon of the group DM channel. - Icon string `json:"icon"` - - // The position of the channel, used for sorting in client. - Position int `json:"position"` - - // The bitrate of the channel, if it is a voice channel. - Bitrate int `json:"bitrate"` - - // The recipients of the channel. This is only populated in DM channels. - Recipients []*User `json:"recipients"` - - // The messages in the channel. This is only present in state-cached channels, - // and State.MaxMessageCount must be non-zero. - Messages []*Message `json:"-"` - - // A list of permission overwrites present for the channel. - PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"` - - // The user limit of the voice channel. - UserLimit int `json:"user_limit"` - - // The ID of the parent channel, if the channel is under a category - ParentID string `json:"parent_id"` - - // Amount of seconds a user has to wait before sending another message (0-21600) - // bots, as well as users with the permission manage_messages or manage_channel, are unaffected - RateLimitPerUser int `json:"rate_limit_per_user"` - - // ID of the DM creator Zeroed if guild channel - OwnerID string `json:"owner_id"` - - // ApplicationID of the DM creator Zeroed if guild channel or not a bot user - ApplicationID string `json:"application_id"` -} - -// Mention returns a string which mentions the channel -func (c *Channel) Mention() string { - return fmt.Sprintf("<#%s>", c.ID) -} - -// A ChannelEdit holds Channel Field data for a channel edit. -type ChannelEdit struct { - Name string `json:"name,omitempty"` - Topic string `json:"topic,omitempty"` - NSFW bool `json:"nsfw,omitempty"` - Position int `json:"position"` - Bitrate int `json:"bitrate,omitempty"` - UserLimit int `json:"user_limit,omitempty"` - PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` - ParentID string `json:"parent_id,omitempty"` - RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` -} - -// A ChannelFollow holds data returned after following a news channel -type ChannelFollow struct { - ChannelID string `json:"channel_id"` - WebhookID string `json:"webhook_id"` -} - -// PermissionOverwriteType represents the type of resource on which -// a permission overwrite acts. -type PermissionOverwriteType int - -// The possible permission overwrite types. -const ( - PermissionOverwriteTypeRole PermissionOverwriteType = iota - PermissionOverwriteTypeMember -) - -// A PermissionOverwrite holds permission overwrite data for a Channel -type PermissionOverwrite struct { - ID string `json:"id"` - Type PermissionOverwriteType `json:"type"` - Deny int64 `json:"deny,string"` - Allow int64 `json:"allow,string"` -} - -// Emoji struct holds data related to Emoji's -type Emoji struct { - ID string `json:"id"` - Name string `json:"name"` - Roles []string `json:"roles"` - User *User `json:"user"` - RequireColons bool `json:"require_colons"` - Managed bool `json:"managed"` - Animated bool `json:"animated"` - Available bool `json:"available"` -} - -// MessageFormat returns a correctly formatted Emoji for use in Message content and embeds -func (e *Emoji) MessageFormat() string { - if e.ID != "" && e.Name != "" { - if e.Animated { - return "" - } - - return "<:" + e.APIName() + ">" - } - - return e.APIName() -} - -// APIName returns an correctly formatted API name for use in the MessageReactions endpoints. -func (e *Emoji) APIName() string { - if e.ID != "" && e.Name != "" { - return e.Name + ":" + e.ID - } - if e.Name != "" { - return e.Name - } - return e.ID -} - -// VerificationLevel type definition -type VerificationLevel int - -// Constants for VerificationLevel levels from 0 to 4 inclusive -const ( - VerificationLevelNone VerificationLevel = iota - VerificationLevelLow - VerificationLevelMedium - VerificationLevelHigh - VerificationLevelVeryHigh -) - -// ExplicitContentFilterLevel type definition -type ExplicitContentFilterLevel int - -// Constants for ExplicitContentFilterLevel levels from 0 to 2 inclusive -const ( - ExplicitContentFilterDisabled ExplicitContentFilterLevel = iota - ExplicitContentFilterMembersWithoutRoles - ExplicitContentFilterAllMembers -) - -// MfaLevel type definition -type MfaLevel int - -// Constants for MfaLevel levels from 0 to 1 inclusive -const ( - MfaLevelNone MfaLevel = iota - MfaLevelElevated -) - -// PremiumTier type definition -type PremiumTier int - -// Constants for PremiumTier levels from 0 to 3 inclusive -const ( - PremiumTierNone PremiumTier = iota - PremiumTier1 - PremiumTier2 - PremiumTier3 -) - -// A Guild holds all data related to a specific Discord Guild. Guilds are also -// sometimes referred to as Servers in the Discord client. -type Guild struct { - // The ID of the guild. - ID string `json:"id"` - - // The name of the guild. (2–100 characters) - Name string `json:"name"` - - // The hash of the guild's icon. Use Session.GuildIcon - // to retrieve the icon itself. - Icon string `json:"icon"` - - // The voice region of the guild. - Region string `json:"region"` - - // The ID of the AFK voice channel. - AfkChannelID string `json:"afk_channel_id"` - - // The user ID of the owner of the guild. - OwnerID string `json:"owner_id"` - - // If we are the owner of the guild - Owner bool `json:"owner"` - - // The time at which the current user joined the guild. - // This field is only present in GUILD_CREATE events and websocket - // update events, and thus is only present in state-cached guilds. - JoinedAt Timestamp `json:"joined_at"` - - // The hash of the guild's discovery splash. - DiscoverySplash string `json:"discovery_splash"` - - // The hash of the guild's splash. - Splash string `json:"splash"` - - // The timeout, in seconds, before a user is considered AFK in voice. - AfkTimeout int `json:"afk_timeout"` - - // The number of members in the guild. - // This field is only present in GUILD_CREATE events and websocket - // update events, and thus is only present in state-cached guilds. - MemberCount int `json:"member_count"` - - // The verification level required for the guild. - VerificationLevel VerificationLevel `json:"verification_level"` - - // Whether the guild is considered large. This is - // determined by a member threshold in the identify packet, - // and is currently hard-coded at 250 members in the library. - Large bool `json:"large"` - - // The default message notification setting for the guild. - DefaultMessageNotifications MessageNotifications `json:"default_message_notifications"` - - // A list of roles in the guild. - Roles []*Role `json:"roles"` - - // A list of the custom emojis present in the guild. - Emojis []*Emoji `json:"emojis"` - - // A list of the members in the guild. - // This field is only present in GUILD_CREATE events and websocket - // update events, and thus is only present in state-cached guilds. - Members []*Member `json:"members"` - - // A list of partial presence objects for members in the guild. - // This field is only present in GUILD_CREATE events and websocket - // update events, and thus is only present in state-cached guilds. - Presences []*Presence `json:"presences"` - - // The maximum number of presences for the guild (the default value, currently 25000, is in effect when null is returned) - MaxPresences int `json:"max_presences"` - - // The maximum number of members for the guild - MaxMembers int `json:"max_members"` - - // A list of channels in the guild. - // This field is only present in GUILD_CREATE events and websocket - // update events, and thus is only present in state-cached guilds. - Channels []*Channel `json:"channels"` - - // A list of voice states for the guild. - // This field is only present in GUILD_CREATE events and websocket - // update events, and thus is only present in state-cached guilds. - VoiceStates []*VoiceState `json:"voice_states"` - - // Whether this guild is currently unavailable (most likely due to outage). - // This field is only present in GUILD_CREATE events and websocket - // update events, and thus is only present in state-cached guilds. - Unavailable bool `json:"unavailable"` - - // The explicit content filter level - ExplicitContentFilter ExplicitContentFilterLevel `json:"explicit_content_filter"` - - // The list of enabled guild features - Features []string `json:"features"` - - // Required MFA level for the guild - MfaLevel MfaLevel `json:"mfa_level"` - - // The application id of the guild if bot created. - ApplicationID string `json:"application_id"` - - // Whether or not the Server Widget is enabled - WidgetEnabled bool `json:"widget_enabled"` - - // The Channel ID for the Server Widget - WidgetChannelID string `json:"widget_channel_id"` - - // The Channel ID to which system messages are sent (eg join and leave messages) - SystemChannelID string `json:"system_channel_id"` - - // The System channel flags - SystemChannelFlags SystemChannelFlag `json:"system_channel_flags"` - - // The ID of the rules channel ID, used for rules. - RulesChannelID string `json:"rules_channel_id"` - - // the vanity url code for the guild - VanityURLCode string `json:"vanity_url_code"` - - // the description for the guild - Description string `json:"description"` - - // The hash of the guild's banner - Banner string `json:"banner"` - - // The premium tier of the guild - PremiumTier PremiumTier `json:"premium_tier"` - - // The total number of users currently boosting this server - PremiumSubscriptionCount int `json:"premium_subscription_count"` - - // The preferred locale of a guild with the "PUBLIC" feature; used in server discovery and notices from Discord; defaults to "en-US" - PreferredLocale string `json:"preferred_locale"` - - // The id of the channel where admins and moderators of guilds with the "PUBLIC" feature receive notices from Discord - PublicUpdatesChannelID string `json:"public_updates_channel_id"` - - // The maximum amount of users in a video channel - MaxVideoChannelUsers int `json:"max_video_channel_users"` - - // Approximate number of members in this guild, returned from the GET /guild/ endpoint when with_counts is true - ApproximateMemberCount int `json:"approximate_member_count"` - - // Approximate number of non-offline members in this guild, returned from the GET /guild/ endpoint when with_counts is true - ApproximatePresenceCount int `json:"approximate_presence_count"` - - // Permissions of our user - Permissions int64 `json:"permissions,string"` -} - -// MessageNotifications is the notification level for a guild -// https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level -type MessageNotifications int - -// Block containing known MessageNotifications values -const ( - MessageNotificationsAllMessages MessageNotifications = iota - MessageNotificationsOnlyMentions -) - -// SystemChannelFlag is the type of flags in the system channel (see SystemChannelFlag* consts) -// https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags -type SystemChannelFlag int - -// Block containing known SystemChannelFlag values -const ( - SystemChannelFlagsSuppressJoin SystemChannelFlag = 1 << iota - SystemChannelFlagsSuppressPremium -) - -// IconURL returns a URL to the guild's icon. -func (g *Guild) IconURL() string { - if g.Icon == "" { - return "" - } - - if strings.HasPrefix(g.Icon, "a_") { - return EndpointGuildIconAnimated(g.ID, g.Icon) - } - - return EndpointGuildIcon(g.ID, g.Icon) -} - -// A UserGuild holds a brief version of a Guild -type UserGuild struct { - ID string `json:"id"` - Name string `json:"name"` - Icon string `json:"icon"` - Owner bool `json:"owner"` - Permissions int64 `json:"permissions,string"` -} - -// A GuildParams stores all the data needed to update discord guild settings -type GuildParams struct { - Name string `json:"name,omitempty"` - Region string `json:"region,omitempty"` - VerificationLevel *VerificationLevel `json:"verification_level,omitempty"` - DefaultMessageNotifications int `json:"default_message_notifications,omitempty"` // TODO: Separate type? - AfkChannelID string `json:"afk_channel_id,omitempty"` - AfkTimeout int `json:"afk_timeout,omitempty"` - Icon string `json:"icon,omitempty"` - OwnerID string `json:"owner_id,omitempty"` - Splash string `json:"splash,omitempty"` - Banner string `json:"banner,omitempty"` -} - -// A Role stores information about Discord guild member roles. -type Role struct { - // The ID of the role. - ID string `json:"id"` - - // The name of the role. - Name string `json:"name"` - - // Whether this role is managed by an integration, and - // thus cannot be manually added to, or taken from, members. - Managed bool `json:"managed"` - - // Whether this role is mentionable. - Mentionable bool `json:"mentionable"` - - // Whether this role is hoisted (shows up separately in member list). - Hoist bool `json:"hoist"` - - // The hex color of this role. - Color int `json:"color"` - - // The position of this role in the guild's role hierarchy. - Position int `json:"position"` - - // The permissions of the role on the guild (doesn't include channel overrides). - // This is a combination of bit masks; the presence of a certain permission can - // be checked by performing a bitwise AND between this int and the permission. - Permissions int64 `json:"permissions,string"` -} - -// Mention returns a string which mentions the role -func (r *Role) Mention() string { - return fmt.Sprintf("<@&%s>", r.ID) -} - -// Roles are a collection of Role -type Roles []*Role - -func (r Roles) Len() int { - return len(r) -} - -func (r Roles) Less(i, j int) bool { - return r[i].Position > r[j].Position -} - -func (r Roles) Swap(i, j int) { - r[i], r[j] = r[j], r[i] -} - -// A VoiceState stores the voice states of Guilds -type VoiceState struct { - UserID string `json:"user_id"` - SessionID string `json:"session_id"` - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id"` - Suppress bool `json:"suppress"` - SelfMute bool `json:"self_mute"` - SelfDeaf bool `json:"self_deaf"` - Mute bool `json:"mute"` - Deaf bool `json:"deaf"` -} - -// A Presence stores the online, offline, or idle and game status of Guild members. -type Presence struct { - User *User `json:"user"` - Status Status `json:"status"` - Activities []*Activity `json:"activities"` - Since *int `json:"since"` -} - -// A TimeStamps struct contains start and end times used in the rich presence "playing .." Game -type TimeStamps struct { - EndTimestamp int64 `json:"end,omitempty"` - StartTimestamp int64 `json:"start,omitempty"` -} - -// UnmarshalJSON unmarshals JSON into TimeStamps struct -func (t *TimeStamps) UnmarshalJSON(b []byte) error { - temp := struct { - End float64 `json:"end,omitempty"` - Start float64 `json:"start,omitempty"` - }{} - err := json.Unmarshal(b, &temp) - if err != nil { - return err - } - t.EndTimestamp = int64(temp.End) - t.StartTimestamp = int64(temp.Start) - return nil -} - -// An Assets struct contains assets and labels used in the rich presence "playing .." Game -type Assets struct { - LargeImageID string `json:"large_image,omitempty"` - SmallImageID string `json:"small_image,omitempty"` - LargeText string `json:"large_text,omitempty"` - SmallText string `json:"small_text,omitempty"` -} - -// A Member stores user information for Guild members. A guild -// member represents a certain user's presence in a guild. -type Member struct { - // The guild ID on which the member exists. - GuildID string `json:"guild_id"` - - // The time at which the member joined the guild, in ISO8601. - JoinedAt Timestamp `json:"joined_at"` - - // The nickname of the member, if they have one. - Nick string `json:"nick"` - - // Whether the member is deafened at a guild level. - Deaf bool `json:"deaf"` - - // Whether the member is muted at a guild level. - Mute bool `json:"mute"` - - // The underlying user on which the member is based. - User *User `json:"user"` - - // A list of IDs of the roles which are possessed by the member. - Roles []string `json:"roles"` - - // When the user used their Nitro boost on the server - PremiumSince Timestamp `json:"premium_since"` - - // Is true while the member hasn't accepted the membership screen. - Pending bool `json:"pending"` -} - -// Mention creates a member mention -func (m *Member) Mention() string { - return "<@!" + m.User.ID + ">" -} - -// A Settings stores data for a specific users Discord client settings. -type Settings struct { - RenderEmbeds bool `json:"render_embeds"` - InlineEmbedMedia bool `json:"inline_embed_media"` - InlineAttachmentMedia bool `json:"inline_attachment_media"` - EnableTTSCommand bool `json:"enable_tts_command"` - MessageDisplayCompact bool `json:"message_display_compact"` - ShowCurrentGame bool `json:"show_current_game"` - ConvertEmoticons bool `json:"convert_emoticons"` - Locale string `json:"locale"` - Theme string `json:"theme"` - GuildPositions []string `json:"guild_positions"` - RestrictedGuilds []string `json:"restricted_guilds"` - FriendSourceFlags *FriendSourceFlags `json:"friend_source_flags"` - Status Status `json:"status"` - DetectPlatformAccounts bool `json:"detect_platform_accounts"` - DeveloperMode bool `json:"developer_mode"` -} - -// Status type definition -type Status string - -// Constants for Status with the different current available status -const ( - StatusOnline Status = "online" - StatusIdle Status = "idle" - StatusDoNotDisturb Status = "dnd" - StatusInvisible Status = "invisible" - StatusOffline Status = "offline" -) - -// FriendSourceFlags stores ... TODO :) -type FriendSourceFlags struct { - All bool `json:"all"` - MutualGuilds bool `json:"mutual_guilds"` - MutualFriends bool `json:"mutual_friends"` -} - -// A Relationship between the logged in user and Relationship.User -type Relationship struct { - User *User `json:"user"` - Type int `json:"type"` // 1 = friend, 2 = blocked, 3 = incoming friend req, 4 = sent friend req - ID string `json:"id"` -} - -// A TooManyRequests struct holds information received from Discord -// when receiving a HTTP 429 response. -type TooManyRequests struct { - Bucket string `json:"bucket"` - Message string `json:"message"` - RetryAfter time.Duration `json:"retry_after"` -} - -// UnmarshalJSON helps support translation of a milliseconds-based float -// into a time.Duration on TooManyRequests. -func (t *TooManyRequests) UnmarshalJSON(b []byte) error { - u := struct { - Bucket string `json:"bucket"` - Message string `json:"message"` - RetryAfter float64 `json:"retry_after"` - }{} - err := json.Unmarshal(b, &u) - if err != nil { - return err - } - - t.Bucket = u.Bucket - t.Message = u.Message - whole, frac := math.Modf(u.RetryAfter) - t.RetryAfter = time.Duration(whole)*time.Second + time.Duration(frac*1000)*time.Millisecond - return nil -} - -// A ReadState stores data on the read state of channels. -type ReadState struct { - MentionCount int `json:"mention_count"` - LastMessageID string `json:"last_message_id"` - ID string `json:"id"` -} - -// An Ack is used to ack messages -type Ack struct { - Token string `json:"token"` -} - -// A GuildRole stores data for guild roles. -type GuildRole struct { - Role *Role `json:"role"` - GuildID string `json:"guild_id"` -} - -// A GuildBan stores data for a guild ban. -type GuildBan struct { - Reason string `json:"reason"` - User *User `json:"user"` -} - -// A GuildEmbed stores data for a guild embed. -type GuildEmbed struct { - Enabled bool `json:"enabled"` - ChannelID string `json:"channel_id"` -} - -// A GuildAuditLog stores data for a guild audit log. -// https://discord.com/developers/docs/resources/audit-log#audit-log-object-audit-log-structure -type GuildAuditLog struct { - Webhooks []*Webhook `json:"webhooks,omitempty"` - Users []*User `json:"users,omitempty"` - AuditLogEntries []*AuditLogEntry `json:"audit_log_entries"` - Integrations []*Integration `json:"integrations"` -} - -// AuditLogEntry for a GuildAuditLog -// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-entry-structure -type AuditLogEntry struct { - TargetID string `json:"target_id"` - Changes []*AuditLogChange `json:"changes"` - UserID string `json:"user_id"` - ID string `json:"id"` - ActionType *AuditLogAction `json:"action_type"` - Options *AuditLogOptions `json:"options"` - Reason string `json:"reason"` -} - -// AuditLogChange for an AuditLogEntry -type AuditLogChange struct { - NewValue interface{} `json:"new_value"` - OldValue interface{} `json:"old_value"` - Key *AuditLogChangeKey `json:"key"` -} - -// AuditLogChangeKey value for AuditLogChange -// https://discord.com/developers/docs/resources/audit-log#audit-log-change-object-audit-log-change-key -type AuditLogChangeKey string - -// Block of valid AuditLogChangeKey -const ( - AuditLogChangeKeyName AuditLogChangeKey = "name" - AuditLogChangeKeyIconHash AuditLogChangeKey = "icon_hash" - AuditLogChangeKeySplashHash AuditLogChangeKey = "splash_hash" - AuditLogChangeKeyOwnerID AuditLogChangeKey = "owner_id" - AuditLogChangeKeyRegion AuditLogChangeKey = "region" - AuditLogChangeKeyAfkChannelID AuditLogChangeKey = "afk_channel_id" - AuditLogChangeKeyAfkTimeout AuditLogChangeKey = "afk_timeout" - AuditLogChangeKeyMfaLevel AuditLogChangeKey = "mfa_level" - AuditLogChangeKeyVerificationLevel AuditLogChangeKey = "verification_level" - AuditLogChangeKeyExplicitContentFilter AuditLogChangeKey = "explicit_content_filter" - AuditLogChangeKeyDefaultMessageNotification AuditLogChangeKey = "default_message_notifications" - AuditLogChangeKeyVanityURLCode AuditLogChangeKey = "vanity_url_code" - AuditLogChangeKeyRoleAdd AuditLogChangeKey = "$add" - AuditLogChangeKeyRoleRemove AuditLogChangeKey = "$remove" - AuditLogChangeKeyPruneDeleteDays AuditLogChangeKey = "prune_delete_days" - AuditLogChangeKeyWidgetEnabled AuditLogChangeKey = "widget_enabled" - AuditLogChangeKeyWidgetChannelID AuditLogChangeKey = "widget_channel_id" - AuditLogChangeKeySystemChannelID AuditLogChangeKey = "system_channel_id" - AuditLogChangeKeyPosition AuditLogChangeKey = "position" - AuditLogChangeKeyTopic AuditLogChangeKey = "topic" - AuditLogChangeKeyBitrate AuditLogChangeKey = "bitrate" - AuditLogChangeKeyPermissionOverwrite AuditLogChangeKey = "permission_overwrites" - AuditLogChangeKeyNSFW AuditLogChangeKey = "nsfw" - AuditLogChangeKeyApplicationID AuditLogChangeKey = "application_id" - AuditLogChangeKeyRateLimitPerUser AuditLogChangeKey = "rate_limit_per_user" - AuditLogChangeKeyPermissions AuditLogChangeKey = "permissions" - AuditLogChangeKeyColor AuditLogChangeKey = "color" - AuditLogChangeKeyHoist AuditLogChangeKey = "hoist" - AuditLogChangeKeyMentionable AuditLogChangeKey = "mentionable" - AuditLogChangeKeyAllow AuditLogChangeKey = "allow" - AuditLogChangeKeyDeny AuditLogChangeKey = "deny" - AuditLogChangeKeyCode AuditLogChangeKey = "code" - AuditLogChangeKeyChannelID AuditLogChangeKey = "channel_id" - AuditLogChangeKeyInviterID AuditLogChangeKey = "inviter_id" - AuditLogChangeKeyMaxUses AuditLogChangeKey = "max_uses" - AuditLogChangeKeyUses AuditLogChangeKey = "uses" - AuditLogChangeKeyMaxAge AuditLogChangeKey = "max_age" - AuditLogChangeKeyTempoary AuditLogChangeKey = "temporary" - AuditLogChangeKeyDeaf AuditLogChangeKey = "deaf" - AuditLogChangeKeyMute AuditLogChangeKey = "mute" - AuditLogChangeKeyNick AuditLogChangeKey = "nick" - AuditLogChangeKeyAvatarHash AuditLogChangeKey = "avatar_hash" - AuditLogChangeKeyID AuditLogChangeKey = "id" - AuditLogChangeKeyType AuditLogChangeKey = "type" - AuditLogChangeKeyEnableEmoticons AuditLogChangeKey = "enable_emoticons" - AuditLogChangeKeyExpireBehavior AuditLogChangeKey = "expire_behavior" - AuditLogChangeKeyExpireGracePeriod AuditLogChangeKey = "expire_grace_period" -) - -// AuditLogOptions optional data for the AuditLog -// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info -type AuditLogOptions struct { - DeleteMemberDays string `json:"delete_member_days"` - MembersRemoved string `json:"members_removed"` - ChannelID string `json:"channel_id"` - MessageID string `json:"message_id"` - Count string `json:"count"` - ID string `json:"id"` - Type *AuditLogOptionsType `json:"type"` - RoleName string `json:"role_name"` -} - -// AuditLogOptionsType of the AuditLogOption -// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info -type AuditLogOptionsType string - -// Valid Types for AuditLogOptionsType -const ( - AuditLogOptionsTypeMember AuditLogOptionsType = "member" - AuditLogOptionsTypeRole AuditLogOptionsType = "role" -) - -// AuditLogAction is the Action of the AuditLog (see AuditLogAction* consts) -// https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events -type AuditLogAction int - -// Block contains Discord Audit Log Action Types -const ( - AuditLogActionGuildUpdate AuditLogAction = 1 - - AuditLogActionChannelCreate AuditLogAction = 10 - AuditLogActionChannelUpdate AuditLogAction = 11 - AuditLogActionChannelDelete AuditLogAction = 12 - AuditLogActionChannelOverwriteCreate AuditLogAction = 13 - AuditLogActionChannelOverwriteUpdate AuditLogAction = 14 - AuditLogActionChannelOverwriteDelete AuditLogAction = 15 - - AuditLogActionMemberKick AuditLogAction = 20 - AuditLogActionMemberPrune AuditLogAction = 21 - AuditLogActionMemberBanAdd AuditLogAction = 22 - AuditLogActionMemberBanRemove AuditLogAction = 23 - AuditLogActionMemberUpdate AuditLogAction = 24 - AuditLogActionMemberRoleUpdate AuditLogAction = 25 - - AuditLogActionRoleCreate AuditLogAction = 30 - AuditLogActionRoleUpdate AuditLogAction = 31 - AuditLogActionRoleDelete AuditLogAction = 32 - - AuditLogActionInviteCreate AuditLogAction = 40 - AuditLogActionInviteUpdate AuditLogAction = 41 - AuditLogActionInviteDelete AuditLogAction = 42 - - AuditLogActionWebhookCreate AuditLogAction = 50 - AuditLogActionWebhookUpdate AuditLogAction = 51 - AuditLogActionWebhookDelete AuditLogAction = 52 - - AuditLogActionEmojiCreate AuditLogAction = 60 - AuditLogActionEmojiUpdate AuditLogAction = 61 - AuditLogActionEmojiDelete AuditLogAction = 62 - - AuditLogActionMessageDelete AuditLogAction = 72 - AuditLogActionMessageBulkDelete AuditLogAction = 73 - AuditLogActionMessagePin AuditLogAction = 74 - AuditLogActionMessageUnpin AuditLogAction = 75 - - AuditLogActionIntegrationCreate AuditLogAction = 80 - AuditLogActionIntegrationUpdate AuditLogAction = 81 - AuditLogActionIntegrationDelete AuditLogAction = 82 -) - -// A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings. -type UserGuildSettingsChannelOverride struct { - Muted bool `json:"muted"` - MessageNotifications int `json:"message_notifications"` - ChannelID string `json:"channel_id"` -} - -// A UserGuildSettings stores data for a users guild settings. -type UserGuildSettings struct { - SupressEveryone bool `json:"suppress_everyone"` - Muted bool `json:"muted"` - MobilePush bool `json:"mobile_push"` - MessageNotifications int `json:"message_notifications"` - GuildID string `json:"guild_id"` - ChannelOverrides []*UserGuildSettingsChannelOverride `json:"channel_overrides"` -} - -// A UserGuildSettingsEdit stores data for editing UserGuildSettings -type UserGuildSettingsEdit struct { - SupressEveryone bool `json:"suppress_everyone"` - Muted bool `json:"muted"` - MobilePush bool `json:"mobile_push"` - MessageNotifications int `json:"message_notifications"` - ChannelOverrides map[string]*UserGuildSettingsChannelOverride `json:"channel_overrides"` -} - -// An APIErrorMessage is an api error message returned from discord -type APIErrorMessage struct { - Code int `json:"code"` - Message string `json:"message"` -} - -// Webhook stores the data for a webhook. -type Webhook struct { - ID string `json:"id"` - Type WebhookType `json:"type"` - GuildID string `json:"guild_id"` - ChannelID string `json:"channel_id"` - User *User `json:"user"` - Name string `json:"name"` - Avatar string `json:"avatar"` - Token string `json:"token"` - - // ApplicationID is the bot/OAuth2 application that created this webhook - ApplicationID string `json:"application_id,omitempty"` -} - -// WebhookType is the type of Webhook (see WebhookType* consts) in the Webhook struct -// https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types -type WebhookType int - -// Valid WebhookType values -const ( - WebhookTypeIncoming WebhookType = iota - WebhookTypeChannelFollower -) - -// WebhookParams is a struct for webhook params, used in the WebhookExecute command. -type WebhookParams struct { - Content string `json:"content,omitempty"` - Username string `json:"username,omitempty"` - AvatarURL string `json:"avatar_url,omitempty"` - TTS bool `json:"tts,omitempty"` - File *File `json:"-"` - Embeds []*MessageEmbed `json:"embeds,omitempty"` - AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` -} - -// MessageReaction stores the data for a message reaction. -type MessageReaction struct { - UserID string `json:"user_id"` - MessageID string `json:"message_id"` - Emoji Emoji `json:"emoji"` - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id,omitempty"` -} - -// GatewayBotResponse stores the data for the gateway/bot response -type GatewayBotResponse struct { - URL string `json:"url"` - Shards int `json:"shards"` -} - -// GatewayStatusUpdate is sent by the client to indicate a presence or status update -// https://discord.com/developers/docs/topics/gateway#update-status-gateway-status-update-structure -type GatewayStatusUpdate struct { - Since int `json:"since"` - Game Activity `json:"game"` - Status string `json:"status"` - AFK bool `json:"afk"` -} - -// Activity defines the Activity sent with GatewayStatusUpdate -// https://discord.com/developers/docs/topics/gateway#activity-object -type Activity struct { - Name string `json:"name"` - Type ActivityType `json:"type"` - URL string `json:"url,omitempty"` -} - -// ActivityType is the type of Activity (see ActivityType* consts) in the Activity struct -// https://discord.com/developers/docs/topics/gateway#activity-object-activity-types -type ActivityType int - -// Valid ActivityType values -const ( - ActivityTypeGame ActivityType = iota - ActivityTypeStreaming - ActivityTypeListening - // ActivityTypeWatching // not valid in this use case? - ActivityTypeCustom = 4 -) - -// Identify is sent during initial handshake with the discord gateway. -// https://discord.com/developers/docs/topics/gateway#identify -type Identify struct { - Token string `json:"token"` - Properties IdentifyProperties `json:"properties"` - Compress bool `json:"compress"` - LargeThreshold int `json:"large_threshold"` - Shard *[2]int `json:"shard,omitempty"` - Presence GatewayStatusUpdate `json:"presence,omitempty"` - GuildSubscriptions bool `json:"guild_subscriptions"` - Intents Intent `json:"intents"` -} - -// IdentifyProperties contains the "properties" portion of an Identify packet -// https://discord.com/developers/docs/topics/gateway#identify-identify-connection-properties -type IdentifyProperties struct { - OS string `json:"$os"` - Browser string `json:"$browser"` - Device string `json:"$device"` - Referer string `json:"$referer"` - ReferringDomain string `json:"$referring_domain"` -} - -// Constants for the different bit offsets of text channel permissions -const ( - // Deprecated: PermissionReadMessages has been replaced with PermissionViewChannel for text and voice channels - PermissionReadMessages = 1 << (iota + 10) - PermissionSendMessages - PermissionSendTTSMessages - PermissionManageMessages - PermissionEmbedLinks - PermissionAttachFiles - PermissionReadMessageHistory - PermissionMentionEveryone - PermissionUseExternalEmojis -) - -// Constants for the different bit offsets of voice permissions -const ( - PermissionVoiceConnect = 1 << (iota + 20) - PermissionVoiceSpeak - PermissionVoiceMuteMembers - PermissionVoiceDeafenMembers - PermissionVoiceMoveMembers - PermissionVoiceUseVAD - PermissionVoicePrioritySpeaker = 1 << (iota + 2) -) - -// Constants for general management. -const ( - PermissionChangeNickname = 1 << (iota + 26) - PermissionManageNicknames - PermissionManageRoles - PermissionManageWebhooks - PermissionManageEmojis -) - -// Constants for the different bit offsets of general permissions -const ( - PermissionCreateInstantInvite = 1 << iota - PermissionKickMembers - PermissionBanMembers - PermissionAdministrator - PermissionManageChannels - PermissionManageServer - PermissionAddReactions - PermissionViewAuditLogs - PermissionViewChannel = 1 << (iota + 2) - - PermissionAllText = PermissionViewChannel | - PermissionSendMessages | - PermissionSendTTSMessages | - PermissionManageMessages | - PermissionEmbedLinks | - PermissionAttachFiles | - PermissionReadMessageHistory | - PermissionMentionEveryone - PermissionAllVoice = PermissionViewChannel | - PermissionVoiceConnect | - PermissionVoiceSpeak | - PermissionVoiceMuteMembers | - PermissionVoiceDeafenMembers | - PermissionVoiceMoveMembers | - PermissionVoiceUseVAD | - PermissionVoicePrioritySpeaker - PermissionAllChannel = PermissionAllText | - PermissionAllVoice | - PermissionCreateInstantInvite | - PermissionManageRoles | - PermissionManageChannels | - PermissionAddReactions | - PermissionViewAuditLogs - PermissionAll = PermissionAllChannel | - PermissionKickMembers | - PermissionBanMembers | - PermissionManageServer | - PermissionAdministrator | - PermissionManageWebhooks | - PermissionManageEmojis -) - -// Block contains Discord JSON Error Response codes -const ( - ErrCodeUnknownAccount = 10001 - ErrCodeUnknownApplication = 10002 - ErrCodeUnknownChannel = 10003 - ErrCodeUnknownGuild = 10004 - ErrCodeUnknownIntegration = 10005 - ErrCodeUnknownInvite = 10006 - ErrCodeUnknownMember = 10007 - ErrCodeUnknownMessage = 10008 - ErrCodeUnknownOverwrite = 10009 - ErrCodeUnknownProvider = 10010 - ErrCodeUnknownRole = 10011 - ErrCodeUnknownToken = 10012 - ErrCodeUnknownUser = 10013 - ErrCodeUnknownEmoji = 10014 - ErrCodeUnknownWebhook = 10015 - ErrCodeUnknownBan = 10026 - - ErrCodeBotsCannotUseEndpoint = 20001 - ErrCodeOnlyBotsCanUseEndpoint = 20002 - - ErrCodeMaximumGuildsReached = 30001 - ErrCodeMaximumFriendsReached = 30002 - ErrCodeMaximumPinsReached = 30003 - ErrCodeMaximumGuildRolesReached = 30005 - ErrCodeTooManyReactions = 30010 - - ErrCodeUnauthorized = 40001 - - ErrCodeMissingAccess = 50001 - ErrCodeInvalidAccountType = 50002 - ErrCodeCannotExecuteActionOnDMChannel = 50003 - ErrCodeEmbedDisabled = 50004 - ErrCodeCannotEditFromAnotherUser = 50005 - ErrCodeCannotSendEmptyMessage = 50006 - ErrCodeCannotSendMessagesToThisUser = 50007 - ErrCodeCannotSendMessagesInVoiceChannel = 50008 - ErrCodeChannelVerificationLevelTooHigh = 50009 - ErrCodeOAuth2ApplicationDoesNotHaveBot = 50010 - ErrCodeOAuth2ApplicationLimitReached = 50011 - ErrCodeInvalidOAuthState = 50012 - ErrCodeMissingPermissions = 50013 - ErrCodeInvalidAuthenticationToken = 50014 - ErrCodeNoteTooLong = 50015 - ErrCodeTooFewOrTooManyMessagesToDelete = 50016 - ErrCodeCanOnlyPinMessageToOriginatingChannel = 50019 - ErrCodeCannotExecuteActionOnSystemMessage = 50021 - ErrCodeMessageProvidedTooOldForBulkDelete = 50034 - ErrCodeInvalidFormBody = 50035 - ErrCodeInviteAcceptedToGuildApplicationsBotNotIn = 50036 - - ErrCodeReactionBlocked = 90001 -) - -// Intent is the type of a Gateway Intent -// https://discord.com/developers/docs/topics/gateway#gateway-intents -type Intent int - -// Constants for the different bit offsets of intents -const ( - IntentsGuilds Intent = 1 << iota - IntentsGuildMembers - IntentsGuildBans - IntentsGuildEmojis - IntentsGuildIntegrations - IntentsGuildWebhooks - IntentsGuildInvites - IntentsGuildVoiceStates - IntentsGuildPresences - IntentsGuildMessages - IntentsGuildMessageReactions - IntentsGuildMessageTyping - IntentsDirectMessages - IntentsDirectMessageReactions - IntentsDirectMessageTyping - - IntentsAllWithoutPrivileged = IntentsGuilds | - IntentsGuildBans | - IntentsGuildEmojis | - IntentsGuildIntegrations | - IntentsGuildWebhooks | - IntentsGuildInvites | - IntentsGuildVoiceStates | - IntentsGuildMessages | - IntentsGuildMessageReactions | - IntentsGuildMessageTyping | - IntentsDirectMessages | - IntentsDirectMessageReactions | - IntentsDirectMessageTyping - IntentsAll = IntentsAllWithoutPrivileged | - IntentsGuildMembers | - IntentsGuildPresences - IntentsNone Intent = 0 -) - -// MakeIntent used to help convert a gateway intent value for use in the Identify structure; -// this was useful to help support the use of a pointer type when intents were optional. -// This is now a no-op, and is not necessary to use. -func MakeIntent(intents Intent) Intent { - return intents -} diff --git a/vendor/github.com/matterbridge/discordgo/types.go b/vendor/github.com/matterbridge/discordgo/types.go deleted file mode 100644 index c0ce0131..00000000 --- a/vendor/github.com/matterbridge/discordgo/types.go +++ /dev/null @@ -1,57 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains custom types, currently only a timestamp wrapper. - -package discordgo - -import ( - "encoding/json" - "net/http" - "time" -) - -// Timestamp stores a timestamp, as sent by the Discord API. -type Timestamp string - -// Parse parses a timestamp string into a time.Time object. -// The only time this can fail is if Discord changes their timestamp format. -func (t Timestamp) Parse() (time.Time, error) { - return time.Parse(time.RFC3339, string(t)) -} - -// RESTError stores error information about a request with a bad response code. -// Message is not always present, there are cases where api calls can fail -// without returning a json message. -type RESTError struct { - Request *http.Request - Response *http.Response - ResponseBody []byte - - Message *APIErrorMessage // Message may be nil. -} - -func newRestError(req *http.Request, resp *http.Response, body []byte) *RESTError { - restErr := &RESTError{ - Request: req, - Response: resp, - ResponseBody: body, - } - - // Attempt to decode the error and assume no message was provided if it fails - var msg *APIErrorMessage - err := json.Unmarshal(body, &msg) - if err == nil { - restErr.Message = msg - } - - return restErr -} - -func (r RESTError) Error() string { - return "HTTP " + r.Response.Status + ", " + string(r.ResponseBody) -} diff --git a/vendor/github.com/matterbridge/discordgo/user.go b/vendor/github.com/matterbridge/discordgo/user.go deleted file mode 100644 index b2894d5d..00000000 --- a/vendor/github.com/matterbridge/discordgo/user.go +++ /dev/null @@ -1,106 +0,0 @@ -package discordgo - -import "strings" - -// UserFlags is the flags of "user" (see UserFlags* consts) -// https://discord.com/developers/docs/resources/user#user-object-user-flags -type UserFlags int - -// Valid UserFlags values -const ( - UserFlagDiscordEmployee UserFlags = 1 << 0 - UserFlagDiscordPartner = 1 << 1 - UserFlagHypeSquadEvents = 1 << 2 - UserFlagBugHunterLevel1 = 1 << 3 - UserFlagHouseBravery = 1 << 6 - UserFlagHouseBrilliance = 1 << 7 - UserFlagHouseBalance = 1 << 8 - UserFlagEarlySupporter = 1 << 9 - UserFlagTeamUser = 1 << 10 - UserFlagSystem = 1 << 12 - UserFlagBugHunterLevel2 = 1 << 14 - UserFlagVerifiedBot = 1 << 16 - UserFlagVerifiedBotDeveloper = 1 << 17 -) - -// A User stores all data for an individual Discord user. -type User struct { - // The ID of the user. - ID string `json:"id"` - - // The email of the user. This is only present when - // the application possesses the email scope for the user. - Email string `json:"email"` - - // The user's username. - Username string `json:"username"` - - // The hash of the user's avatar. Use Session.UserAvatar - // to retrieve the avatar itself. - Avatar string `json:"avatar"` - - // The user's chosen language option. - Locale string `json:"locale"` - - // The discriminator of the user (4 numbers after name). - Discriminator string `json:"discriminator"` - - // The token of the user. This is only present for - // the user represented by the current session. - Token string `json:"token"` - - // Whether the user's email is verified. - Verified bool `json:"verified"` - - // Whether the user has multi-factor authentication enabled. - MFAEnabled bool `json:"mfa_enabled"` - - // Whether the user is a bot. - Bot bool `json:"bot"` - - // The public flags on a user's account. - // This is a combination of bit masks; the presence of a certain flag can - // be checked by performing a bitwise AND between this int and the flag. - PublicFlags UserFlags `json:"public_flags"` - - // The type of Nitro subscription on a user's account. - // Only available when the request is authorized via a Bearer token. - PremiumType int `json:"premium_type"` - - // Whether the user is an Official Discord System user (part of the urgent message system). - System bool `json:"system"` - - // The flags on a user's account. - // Only available when the request is authorized via a Bearer token. - Flags int `json:"flags"` -} - -// String returns a unique identifier of the form username#discriminator -func (u *User) String() string { - return u.Username + "#" + u.Discriminator -} - -// Mention return a string which mentions the user -func (u *User) Mention() string { - return "<@" + u.ID + ">" -} - -// AvatarURL returns a URL to the user's avatar. -// size: The size of the user's avatar as a power of two -// if size is an empty string, no size parameter will -// be added to the URL. -func (u *User) AvatarURL(size string) string { - var URL string - if u.Avatar == "" { - URL = EndpointDefaultUserAvatar(u.Discriminator) - } else if strings.HasPrefix(u.Avatar, "a_") { - URL = EndpointUserAvatarAnimated(u.ID, u.Avatar) - } else { - URL = EndpointUserAvatar(u.ID, u.Avatar) - } - - if size != "" { - return URL + "?size=" + size - } - return URL -} diff --git a/vendor/github.com/matterbridge/discordgo/util.go b/vendor/github.com/matterbridge/discordgo/util.go deleted file mode 100644 index 8a2b2e01..00000000 --- a/vendor/github.com/matterbridge/discordgo/util.go +++ /dev/null @@ -1,17 +0,0 @@ -package discordgo - -import ( - "strconv" - "time" -) - -// SnowflakeTimestamp returns the creation time of a Snowflake ID relative to the creation of Discord. -func SnowflakeTimestamp(ID string) (t time.Time, err error) { - i, err := strconv.ParseInt(ID, 10, 64) - if err != nil { - return - } - timestamp := (i >> 22) + 1420070400000 - t = time.Unix(0, timestamp*1000000) - return -} diff --git a/vendor/github.com/matterbridge/discordgo/voice.go b/vendor/github.com/matterbridge/discordgo/voice.go deleted file mode 100644 index dbafd837..00000000 --- a/vendor/github.com/matterbridge/discordgo/voice.go +++ /dev/null @@ -1,908 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains code related to Discord voice suppport - -package discordgo - -import ( - "encoding/binary" - "encoding/json" - "fmt" - "net" - "strconv" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" - "golang.org/x/crypto/nacl/secretbox" -) - -// ------------------------------------------------------------------------------------------------ -// Code related to both VoiceConnection Websocket and UDP connections. -// ------------------------------------------------------------------------------------------------ - -// A VoiceConnection struct holds all the data and functions related to a Discord Voice Connection. -type VoiceConnection struct { - sync.RWMutex - - Debug bool // If true, print extra logging -- DEPRECATED - LogLevel int - Ready bool // If true, voice is ready to send/receive audio - UserID string - GuildID string - ChannelID string - deaf bool - mute bool - speaking bool - reconnecting bool // If true, voice connection is trying to reconnect - - OpusSend chan []byte // Chan for sending opus audio - OpusRecv chan *Packet // Chan for receiving opus audio - - wsConn *websocket.Conn - wsMutex sync.Mutex - udpConn *net.UDPConn - session *Session - - sessionID string - token string - endpoint string - - // Used to send a close signal to goroutines - close chan struct{} - - // Used to allow blocking until connected - connected chan bool - - // Used to pass the sessionid from onVoiceStateUpdate - // sessionRecv chan string UNUSED ATM - - op4 voiceOP4 - op2 voiceOP2 - - voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler -} - -// VoiceSpeakingUpdateHandler type provides a function definition for the -// VoiceSpeakingUpdate event -type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate) - -// Speaking sends a speaking notification to Discord over the voice websocket. -// This must be sent as true prior to sending audio and should be set to false -// once finished sending audio. -// b : Send true if speaking, false if not. -func (v *VoiceConnection) Speaking(b bool) (err error) { - - v.log(LogDebug, "called (%t)", b) - - type voiceSpeakingData struct { - Speaking bool `json:"speaking"` - Delay int `json:"delay"` - } - - type voiceSpeakingOp struct { - Op int `json:"op"` // Always 5 - Data voiceSpeakingData `json:"d"` - } - - if v.wsConn == nil { - return fmt.Errorf("no VoiceConnection websocket") - } - - data := voiceSpeakingOp{5, voiceSpeakingData{b, 0}} - v.wsMutex.Lock() - err = v.wsConn.WriteJSON(data) - v.wsMutex.Unlock() - - v.Lock() - defer v.Unlock() - if err != nil { - v.speaking = false - v.log(LogError, "Speaking() write json error, %s", err) - return - } - - v.speaking = b - - return -} - -// ChangeChannel sends Discord a request to change channels within a Guild -// !!! NOTE !!! This function may be removed in favour of just using ChannelVoiceJoin -func (v *VoiceConnection) ChangeChannel(channelID string, mute, deaf bool) (err error) { - - v.log(LogInformational, "called") - - data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, mute, deaf}} - v.wsMutex.Lock() - err = v.session.wsConn.WriteJSON(data) - v.wsMutex.Unlock() - if err != nil { - return - } - v.ChannelID = channelID - v.deaf = deaf - v.mute = mute - v.speaking = false - - return -} - -// Disconnect disconnects from this voice channel and closes the websocket -// and udp connections to Discord. -func (v *VoiceConnection) Disconnect() (err error) { - - // Send a OP4 with a nil channel to disconnect - v.Lock() - if v.sessionID != "" { - data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} - v.session.wsMutex.Lock() - err = v.session.wsConn.WriteJSON(data) - v.session.wsMutex.Unlock() - v.sessionID = "" - } - v.Unlock() - - // Close websocket and udp connections - v.Close() - - v.log(LogInformational, "Deleting VoiceConnection %s", v.GuildID) - - v.session.Lock() - delete(v.session.VoiceConnections, v.GuildID) - v.session.Unlock() - - return -} - -// Close closes the voice ws and udp connections -func (v *VoiceConnection) Close() { - - v.log(LogInformational, "called") - - v.Lock() - defer v.Unlock() - - v.Ready = false - v.speaking = false - - if v.close != nil { - v.log(LogInformational, "closing v.close") - close(v.close) - v.close = nil - } - - if v.udpConn != nil { - v.log(LogInformational, "closing udp") - err := v.udpConn.Close() - if err != nil { - v.log(LogError, "error closing udp connection, %s", err) - } - v.udpConn = nil - } - - if v.wsConn != nil { - v.log(LogInformational, "sending close frame") - - // To cleanly close a connection, a client should send a close - // frame and wait for the server to close the connection. - v.wsMutex.Lock() - err := v.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) - v.wsMutex.Unlock() - if err != nil { - v.log(LogError, "error closing websocket, %s", err) - } - - // TODO: Wait for Discord to actually close the connection. - time.Sleep(1 * time.Second) - - v.log(LogInformational, "closing websocket") - err = v.wsConn.Close() - if err != nil { - v.log(LogError, "error closing websocket, %s", err) - } - - v.wsConn = nil - } -} - -// AddHandler adds a Handler for VoiceSpeakingUpdate events. -func (v *VoiceConnection) AddHandler(h VoiceSpeakingUpdateHandler) { - v.Lock() - defer v.Unlock() - - v.voiceSpeakingUpdateHandlers = append(v.voiceSpeakingUpdateHandlers, h) -} - -// VoiceSpeakingUpdate is a struct for a VoiceSpeakingUpdate event. -type VoiceSpeakingUpdate struct { - UserID string `json:"user_id"` - SSRC int `json:"ssrc"` - Speaking bool `json:"speaking"` -} - -// ------------------------------------------------------------------------------------------------ -// Unexported Internal Functions Below. -// ------------------------------------------------------------------------------------------------ - -// A voiceOP4 stores the data for the voice operation 4 websocket event -// which provides us with the NaCl SecretBox encryption key -type voiceOP4 struct { - SecretKey [32]byte `json:"secret_key"` - Mode string `json:"mode"` -} - -// A voiceOP2 stores the data for the voice operation 2 websocket event -// which is sort of like the voice READY packet -type voiceOP2 struct { - SSRC uint32 `json:"ssrc"` - Port int `json:"port"` - Modes []string `json:"modes"` - HeartbeatInterval time.Duration `json:"heartbeat_interval"` - IP string `json:"ip"` -} - -// WaitUntilConnected waits for the Voice Connection to -// become ready, if it does not become ready it returns an err -func (v *VoiceConnection) waitUntilConnected() error { - - v.log(LogInformational, "called") - - i := 0 - for { - v.RLock() - ready := v.Ready - v.RUnlock() - if ready { - return nil - } - - if i > 10 { - return fmt.Errorf("timeout waiting for voice") - } - - time.Sleep(1 * time.Second) - i++ - } -} - -// Open opens a voice connection. This should be called -// after VoiceChannelJoin is used and the data VOICE websocket events -// are captured. -func (v *VoiceConnection) open() (err error) { - - v.log(LogInformational, "called") - - v.Lock() - defer v.Unlock() - - // Don't open a websocket if one is already open - if v.wsConn != nil { - v.log(LogWarning, "refusing to overwrite non-nil websocket") - return - } - - // TODO temp? loop to wait for the SessionID - i := 0 - for { - if v.sessionID != "" { - break - } - if i > 20 { // only loop for up to 1 second total - return fmt.Errorf("did not receive voice Session ID in time") - } - time.Sleep(50 * time.Millisecond) - i++ - } - - // Connect to VoiceConnection Websocket - vg := "wss://" + strings.TrimSuffix(v.endpoint, ":80") - v.log(LogInformational, "connecting to voice endpoint %s", vg) - v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil) - if err != nil { - v.log(LogWarning, "error connecting to voice endpoint %s, %s", vg, err) - v.log(LogDebug, "voice struct: %#v\n", v) - return - } - - type voiceHandshakeData struct { - ServerID string `json:"server_id"` - UserID string `json:"user_id"` - SessionID string `json:"session_id"` - Token string `json:"token"` - } - type voiceHandshakeOp struct { - Op int `json:"op"` // Always 0 - Data voiceHandshakeData `json:"d"` - } - data := voiceHandshakeOp{0, voiceHandshakeData{v.GuildID, v.UserID, v.sessionID, v.token}} - - err = v.wsConn.WriteJSON(data) - if err != nil { - v.log(LogWarning, "error sending init packet, %s", err) - return - } - - v.close = make(chan struct{}) - go v.wsListen(v.wsConn, v.close) - - // add loop/check for Ready bool here? - // then return false if not ready? - // but then wsListen will also err. - - return -} - -// wsListen listens on the voice websocket for messages and passes them -// to the voice event handler. This is automatically called by the Open func -func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}) { - - v.log(LogInformational, "called") - - for { - _, message, err := v.wsConn.ReadMessage() - if err != nil { - // 4014 indicates a manual disconnection by someone in the guild; - // we shouldn't reconnect. - if websocket.IsCloseError(err, 4014) { - v.log(LogInformational, "received 4014 manual disconnection") - - // Abandon the voice WS connection - v.Lock() - v.wsConn = nil - v.Unlock() - - v.session.Lock() - delete(v.session.VoiceConnections, v.GuildID) - v.session.Unlock() - - v.Close() - - return - } - - // Detect if we have been closed manually. If a Close() has already - // happened, the websocket we are listening on will be different to the - // current session. - v.RLock() - sameConnection := v.wsConn == wsConn - v.RUnlock() - if sameConnection { - - v.log(LogError, "voice endpoint %s websocket closed unexpectantly, %s", v.endpoint, err) - - // Start reconnect goroutine then exit. - go v.reconnect() - } - return - } - - // Pass received message to voice event handler - select { - case <-close: - return - default: - go v.onEvent(message) - } - } -} - -// wsEvent handles any voice websocket events. This is only called by the -// wsListen() function. -func (v *VoiceConnection) onEvent(message []byte) { - - v.log(LogDebug, "received: %s", string(message)) - - var e Event - if err := json.Unmarshal(message, &e); err != nil { - v.log(LogError, "unmarshall error, %s", err) - return - } - - switch e.Operation { - - case 2: // READY - - if err := json.Unmarshal(e.RawData, &v.op2); err != nil { - v.log(LogError, "OP2 unmarshall error, %s, %s", err, string(e.RawData)) - return - } - - // Start the voice websocket heartbeat to keep the connection alive - go v.wsHeartbeat(v.wsConn, v.close, v.op2.HeartbeatInterval) - // TODO monitor a chan/bool to verify this was successful - - // Start the UDP connection - err := v.udpOpen() - if err != nil { - v.log(LogError, "error opening udp connection, %s", err) - return - } - - // Start the opusSender. - // TODO: Should we allow 48000/960 values to be user defined? - if v.OpusSend == nil { - v.OpusSend = make(chan []byte, 2) - } - go v.opusSender(v.udpConn, v.close, v.OpusSend, 48000, 960) - - // Start the opusReceiver - if !v.deaf { - if v.OpusRecv == nil { - v.OpusRecv = make(chan *Packet, 2) - } - - go v.opusReceiver(v.udpConn, v.close, v.OpusRecv) - } - - return - - case 3: // HEARTBEAT response - // add code to use this to track latency? - return - - case 4: // udp encryption secret key - v.Lock() - defer v.Unlock() - - v.op4 = voiceOP4{} - if err := json.Unmarshal(e.RawData, &v.op4); err != nil { - v.log(LogError, "OP4 unmarshall error, %s, %s", err, string(e.RawData)) - return - } - return - - case 5: - if len(v.voiceSpeakingUpdateHandlers) == 0 { - return - } - - voiceSpeakingUpdate := &VoiceSpeakingUpdate{} - if err := json.Unmarshal(e.RawData, voiceSpeakingUpdate); err != nil { - v.log(LogError, "OP5 unmarshall error, %s, %s", err, string(e.RawData)) - return - } - - for _, h := range v.voiceSpeakingUpdateHandlers { - h(v, voiceSpeakingUpdate) - } - - default: - v.log(LogDebug, "unknown voice operation, %d, %s", e.Operation, string(e.RawData)) - } - - return -} - -type voiceHeartbeatOp struct { - Op int `json:"op"` // Always 3 - Data int `json:"d"` -} - -// NOTE :: When a guild voice server changes how do we shut this down -// properly, so a new connection can be setup without fuss? -// -// wsHeartbeat sends regular heartbeats to voice Discord so it knows the client -// is still connected. If you do not send these heartbeats Discord will -// disconnect the websocket connection after a few seconds. -func (v *VoiceConnection) wsHeartbeat(wsConn *websocket.Conn, close <-chan struct{}, i time.Duration) { - - if close == nil || wsConn == nil { - return - } - - var err error - ticker := time.NewTicker(i * time.Millisecond) - defer ticker.Stop() - for { - v.log(LogDebug, "sending heartbeat packet") - v.wsMutex.Lock() - err = wsConn.WriteJSON(voiceHeartbeatOp{3, int(time.Now().Unix())}) - v.wsMutex.Unlock() - if err != nil { - v.log(LogError, "error sending heartbeat to voice endpoint %s, %s", v.endpoint, err) - return - } - - select { - case <-ticker.C: - // continue loop and send heartbeat - case <-close: - return - } - } -} - -// ------------------------------------------------------------------------------------------------ -// Code related to the VoiceConnection UDP connection -// ------------------------------------------------------------------------------------------------ - -type voiceUDPData struct { - Address string `json:"address"` // Public IP of machine running this code - Port uint16 `json:"port"` // UDP Port of machine running this code - Mode string `json:"mode"` // always "xsalsa20_poly1305" -} - -type voiceUDPD struct { - Protocol string `json:"protocol"` // Always "udp" ? - Data voiceUDPData `json:"data"` -} - -type voiceUDPOp struct { - Op int `json:"op"` // Always 1 - Data voiceUDPD `json:"d"` -} - -// udpOpen opens a UDP connection to the voice server and completes the -// initial required handshake. This connection is left open in the session -// and can be used to send or receive audio. This should only be called -// from voice.wsEvent OP2 -func (v *VoiceConnection) udpOpen() (err error) { - - v.Lock() - defer v.Unlock() - - if v.wsConn == nil { - return fmt.Errorf("nil voice websocket") - } - - if v.udpConn != nil { - return fmt.Errorf("udp connection already open") - } - - if v.close == nil { - return fmt.Errorf("nil close channel") - } - - if v.endpoint == "" { - return fmt.Errorf("empty endpoint") - } - - host := v.op2.IP + ":" + strconv.Itoa(v.op2.Port) - addr, err := net.ResolveUDPAddr("udp", host) - if err != nil { - v.log(LogWarning, "error resolving udp host %s, %s", host, err) - return - } - - v.log(LogInformational, "connecting to udp addr %s", addr.String()) - v.udpConn, err = net.DialUDP("udp", nil, addr) - if err != nil { - v.log(LogWarning, "error connecting to udp addr %s, %s", addr.String(), err) - return - } - - // Create a 70 byte array and put the SSRC code from the Op 2 VoiceConnection event - // into it. Then send that over the UDP connection to Discord - sb := make([]byte, 70) - binary.BigEndian.PutUint32(sb, v.op2.SSRC) - _, err = v.udpConn.Write(sb) - if err != nil { - v.log(LogWarning, "udp write error to %s, %s", addr.String(), err) - return - } - - // Create a 70 byte array and listen for the initial handshake response - // from Discord. Once we get it parse the IP and PORT information out - // of the response. This should be our public IP and PORT as Discord - // saw us. - rb := make([]byte, 70) - rlen, _, err := v.udpConn.ReadFromUDP(rb) - if err != nil { - v.log(LogWarning, "udp read error, %s, %s", addr.String(), err) - return - } - - if rlen < 70 { - v.log(LogWarning, "received udp packet too small") - return fmt.Errorf("received udp packet too small") - } - - // Loop over position 4 through 20 to grab the IP address - // Should never be beyond position 20. - var ip string - for i := 4; i < 20; i++ { - if rb[i] == 0 { - break - } - ip += string(rb[i]) - } - - // Grab port from position 68 and 69 - port := binary.BigEndian.Uint16(rb[68:70]) - - // Take the data from above and send it back to Discord to finalize - // the UDP connection handshake. - data := voiceUDPOp{1, voiceUDPD{"udp", voiceUDPData{ip, port, "xsalsa20_poly1305"}}} - - v.wsMutex.Lock() - err = v.wsConn.WriteJSON(data) - v.wsMutex.Unlock() - if err != nil { - v.log(LogWarning, "udp write error, %#v, %s", data, err) - return - } - - // start udpKeepAlive - go v.udpKeepAlive(v.udpConn, v.close, 5*time.Second) - // TODO: find a way to check that it fired off okay - - return -} - -// udpKeepAlive sends a udp packet to keep the udp connection open -// This is still a bit of a "proof of concept" -func (v *VoiceConnection) udpKeepAlive(udpConn *net.UDPConn, close <-chan struct{}, i time.Duration) { - - if udpConn == nil || close == nil { - return - } - - var err error - var sequence uint64 - - packet := make([]byte, 8) - - ticker := time.NewTicker(i) - defer ticker.Stop() - for { - - binary.LittleEndian.PutUint64(packet, sequence) - sequence++ - - _, err = udpConn.Write(packet) - if err != nil { - v.log(LogError, "write error, %s", err) - return - } - - select { - case <-ticker.C: - // continue loop and send keepalive - case <-close: - return - } - } -} - -// opusSender will listen on the given channel and send any -// pre-encoded opus audio to Discord. Supposedly. -func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}, opus <-chan []byte, rate, size int) { - - if udpConn == nil || close == nil { - return - } - - // VoiceConnection is now ready to receive audio packets - // TODO: this needs reviewed as I think there must be a better way. - v.Lock() - v.Ready = true - v.Unlock() - defer func() { - v.Lock() - v.Ready = false - v.Unlock() - }() - - var sequence uint16 - var timestamp uint32 - var recvbuf []byte - var ok bool - udpHeader := make([]byte, 12) - var nonce [24]byte - - // build the parts that don't change in the udpHeader - udpHeader[0] = 0x80 - udpHeader[1] = 0x78 - binary.BigEndian.PutUint32(udpHeader[8:], v.op2.SSRC) - - // start a send loop that loops until buf chan is closed - ticker := time.NewTicker(time.Millisecond * time.Duration(size/(rate/1000))) - defer ticker.Stop() - for { - - // Get data from chan. If chan is closed, return. - select { - case <-close: - return - case recvbuf, ok = <-opus: - if !ok { - return - } - // else, continue loop - } - - v.RLock() - speaking := v.speaking - v.RUnlock() - if !speaking { - err := v.Speaking(true) - if err != nil { - v.log(LogError, "error sending speaking packet, %s", err) - } - } - - // Add sequence and timestamp to udpPacket - binary.BigEndian.PutUint16(udpHeader[2:], sequence) - binary.BigEndian.PutUint32(udpHeader[4:], timestamp) - - // encrypt the opus data - copy(nonce[:], udpHeader) - v.RLock() - sendbuf := secretbox.Seal(udpHeader, recvbuf, &nonce, &v.op4.SecretKey) - v.RUnlock() - - // block here until we're exactly at the right time :) - // Then send rtp audio packet to Discord over UDP - select { - case <-close: - return - case <-ticker.C: - // continue - } - _, err := udpConn.Write(sendbuf) - - if err != nil { - v.log(LogError, "udp write error, %s", err) - v.log(LogDebug, "voice struct: %#v\n", v) - return - } - - if (sequence) == 0xFFFF { - sequence = 0 - } else { - sequence++ - } - - if (timestamp + uint32(size)) >= 0xFFFFFFFF { - timestamp = 0 - } else { - timestamp += uint32(size) - } - } -} - -// A Packet contains the headers and content of a received voice packet. -type Packet struct { - SSRC uint32 - Sequence uint16 - Timestamp uint32 - Type []byte - Opus []byte - PCM []int16 -} - -// opusReceiver listens on the UDP socket for incoming packets -// and sends them across the given channel -// NOTE :: This function may change names later. -func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct{}, c chan *Packet) { - - if udpConn == nil || close == nil { - return - } - - recvbuf := make([]byte, 1024) - var nonce [24]byte - - for { - rlen, err := udpConn.Read(recvbuf) - if err != nil { - // Detect if we have been closed manually. If a Close() has already - // happened, the udp connection we are listening on will be different - // to the current session. - v.RLock() - sameConnection := v.udpConn == udpConn - v.RUnlock() - if sameConnection { - - v.log(LogError, "udp read error, %s, %s", v.endpoint, err) - v.log(LogDebug, "voice struct: %#v\n", v) - - go v.reconnect() - } - return - } - - select { - case <-close: - return - default: - // continue loop - } - - // For now, skip anything except audio. - if rlen < 12 || (recvbuf[0] != 0x80 && recvbuf[0] != 0x90) { - continue - } - - // build a audio packet struct - p := Packet{} - p.Type = recvbuf[0:2] - p.Sequence = binary.BigEndian.Uint16(recvbuf[2:4]) - p.Timestamp = binary.BigEndian.Uint32(recvbuf[4:8]) - p.SSRC = binary.BigEndian.Uint32(recvbuf[8:12]) - // decrypt opus data - copy(nonce[:], recvbuf[0:12]) - p.Opus, _ = secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey) - - if len(p.Opus) > 8 && recvbuf[0] == 0x90 { - // Extension bit is set, first 8 bytes is the extended header - p.Opus = p.Opus[8:] - } - - if c != nil { - select { - case c <- &p: - case <-close: - return - } - } - } -} - -// Reconnect will close down a voice connection then immediately try to -// reconnect to that session. -// NOTE : This func is messy and a WIP while I find what works. -// It will be cleaned up once a proven stable option is flushed out. -// aka: this is ugly shit code, please don't judge too harshly. -func (v *VoiceConnection) reconnect() { - - v.log(LogInformational, "called") - - v.Lock() - if v.reconnecting { - v.log(LogInformational, "already reconnecting to channel %s, exiting", v.ChannelID) - v.Unlock() - return - } - v.reconnecting = true - v.Unlock() - - defer func() { v.reconnecting = false }() - - // Close any currently open connections - v.Close() - - wait := time.Duration(1) - for { - - <-time.After(wait * time.Second) - wait *= 2 - if wait > 600 { - wait = 600 - } - - if v.session.DataReady == false || v.session.wsConn == nil { - v.log(LogInformational, "cannot reconnect to channel %s with unready session", v.ChannelID) - continue - } - - v.log(LogInformational, "trying to reconnect to channel %s", v.ChannelID) - - _, err := v.session.ChannelVoiceJoin(v.GuildID, v.ChannelID, v.mute, v.deaf) - if err == nil { - v.log(LogInformational, "successfully reconnected to channel %s", v.ChannelID) - return - } - - v.log(LogInformational, "error reconnecting to channel %s, %s", v.ChannelID, err) - - // if the reconnect above didn't work lets just send a disconnect - // packet to reset things. - // Send a OP4 with a nil channel to disconnect - data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} - v.session.wsMutex.Lock() - err = v.session.wsConn.WriteJSON(data) - v.session.wsMutex.Unlock() - if err != nil { - v.log(LogError, "error sending disconnect packet, %s", err) - } - - } -} diff --git a/vendor/github.com/matterbridge/discordgo/wsapi.go b/vendor/github.com/matterbridge/discordgo/wsapi.go deleted file mode 100644 index 29a4f613..00000000 --- a/vendor/github.com/matterbridge/discordgo/wsapi.go +++ /dev/null @@ -1,901 +0,0 @@ -// Discordgo - Discord bindings for Go -// Available at https://github.com/bwmarrin/discordgo - -// Copyright 2015-2016 Bruce Marriner . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains low level functions for interacting with the Discord -// data websocket interface. - -package discordgo - -import ( - "bytes" - "compress/zlib" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "sync/atomic" - "time" - - "github.com/gorilla/websocket" -) - -// ErrWSAlreadyOpen is thrown when you attempt to open -// a websocket that already is open. -var ErrWSAlreadyOpen = errors.New("web socket already opened") - -// ErrWSNotFound is thrown when you attempt to use a websocket -// that doesn't exist -var ErrWSNotFound = errors.New("no websocket connection exists") - -// ErrWSShardBounds is thrown when you try to use a shard ID that is -// less than the total shard count -var ErrWSShardBounds = errors.New("ShardID must be less than ShardCount") - -type resumePacket struct { - Op int `json:"op"` - Data struct { - Token string `json:"token"` - SessionID string `json:"session_id"` - Sequence int64 `json:"seq"` - } `json:"d"` -} - -// Open creates a websocket connection to Discord. -// See: https://discord.com/developers/docs/topics/gateway#connecting -func (s *Session) Open() error { - s.log(LogInformational, "called") - - var err error - - // Prevent Open or other major Session functions from - // being called while Open is still running. - s.Lock() - defer s.Unlock() - - // If the websock is already open, bail out here. - if s.wsConn != nil { - return ErrWSAlreadyOpen - } - - // Get the gateway to use for the Websocket connection - if s.gateway == "" { - s.gateway, err = s.Gateway() - if err != nil { - return err - } - - // Add the version and encoding to the URL - s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json" - } - - // Connect to the Gateway - s.log(LogInformational, "connecting to gateway %s", s.gateway) - header := http.Header{} - header.Add("accept-encoding", "zlib") - s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header) - if err != nil { - s.log(LogError, "error connecting to gateway %s, %s", s.gateway, err) - s.gateway = "" // clear cached gateway - s.wsConn = nil // Just to be safe. - return err - } - - s.wsConn.SetCloseHandler(func(code int, text string) error { - return nil - }) - - defer func() { - // because of this, all code below must set err to the error - // when exiting with an error :) Maybe someone has a better - // way :) - if err != nil { - s.wsConn.Close() - s.wsConn = nil - } - }() - - // The first response from Discord should be an Op 10 (Hello) Packet. - // When processed by onEvent the heartbeat goroutine will be started. - mt, m, err := s.wsConn.ReadMessage() - if err != nil { - return err - } - e, err := s.onEvent(mt, m) - if err != nil { - return err - } - if e.Operation != 10 { - err = fmt.Errorf("expecting Op 10, got Op %d instead", e.Operation) - return err - } - s.log(LogInformational, "Op 10 Hello Packet received from Discord") - s.LastHeartbeatAck = time.Now().UTC() - var h helloOp - if err = json.Unmarshal(e.RawData, &h); err != nil { - err = fmt.Errorf("error unmarshalling helloOp, %s", err) - return err - } - - // Now we send either an Op 2 Identity if this is a brand new - // connection or Op 6 Resume if we are resuming an existing connection. - sequence := atomic.LoadInt64(s.sequence) - if s.sessionID == "" && sequence == 0 { - - // Send Op 2 Identity Packet - err = s.identify() - if err != nil { - err = fmt.Errorf("error sending identify packet to gateway, %s, %s", s.gateway, err) - return err - } - - } else { - - // Send Op 6 Resume Packet - p := resumePacket{} - p.Op = 6 - p.Data.Token = s.Token - p.Data.SessionID = s.sessionID - p.Data.Sequence = sequence - - s.log(LogInformational, "sending resume packet to gateway") - s.wsMutex.Lock() - err = s.wsConn.WriteJSON(p) - s.wsMutex.Unlock() - if err != nil { - err = fmt.Errorf("error sending gateway resume packet, %s, %s", s.gateway, err) - return err - } - - } - - // A basic state is a hard requirement for Voice. - // We create it here so the below READY/RESUMED packet can populate - // the state :) - // XXX: Move to New() func? - if s.State == nil { - state := NewState() - state.TrackChannels = false - state.TrackEmojis = false - state.TrackMembers = false - state.TrackRoles = false - state.TrackVoice = false - s.State = state - } - - // Now Discord should send us a READY or RESUMED packet. - mt, m, err = s.wsConn.ReadMessage() - if err != nil { - return err - } - e, err = s.onEvent(mt, m) - if err != nil { - return err - } - if e.Type != `READY` && e.Type != `RESUMED` { - // This is not fatal, but it does not follow their API documentation. - s.log(LogWarning, "Expected READY/RESUMED, instead got:\n%#v\n", e) - } - s.log(LogInformational, "First Packet:\n%#v\n", e) - - s.log(LogInformational, "We are now connected to Discord, emitting connect event") - s.handleEvent(connectEventType, &Connect{}) - - // A VoiceConnections map is a hard requirement for Voice. - // XXX: can this be moved to when opening a voice connection? - if s.VoiceConnections == nil { - s.log(LogInformational, "creating new VoiceConnections map") - s.VoiceConnections = make(map[string]*VoiceConnection) - } - - // Create listening chan outside of listen, as it needs to happen inside the - // mutex lock and needs to exist before calling heartbeat and listen - // go rountines. - s.listening = make(chan interface{}) - - // Start sending heartbeats and reading messages from Discord. - go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval) - go s.listen(s.wsConn, s.listening) - - s.log(LogInformational, "exiting") - return nil -} - -// listen polls the websocket connection for events, it will stop when the -// listening channel is closed, or an error occurs. -func (s *Session) listen(wsConn *websocket.Conn, listening <-chan interface{}) { - - s.log(LogInformational, "called") - - for { - - messageType, message, err := wsConn.ReadMessage() - - if err != nil { - - // Detect if we have been closed manually. If a Close() has already - // happened, the websocket we are listening on will be different to - // the current session. - s.RLock() - sameConnection := s.wsConn == wsConn - s.RUnlock() - - if sameConnection { - - s.log(LogWarning, "error reading from gateway %s websocket, %s", s.gateway, err) - // There has been an error reading, close the websocket so that - // OnDisconnect event is emitted. - err := s.Close() - if err != nil { - s.log(LogWarning, "error closing session connection, %s", err) - } - - s.log(LogInformational, "calling reconnect() now") - s.reconnect() - } - - return - } - - select { - - case <-listening: - return - - default: - s.onEvent(messageType, message) - - } - } -} - -type heartbeatOp struct { - Op int `json:"op"` - Data int64 `json:"d"` -} - -type helloOp struct { - HeartbeatInterval time.Duration `json:"heartbeat_interval"` -} - -// FailedHeartbeatAcks is the Number of heartbeat intervals to wait until forcing a connection restart. -const FailedHeartbeatAcks time.Duration = 5 * time.Millisecond - -// HeartbeatLatency returns the latency between heartbeat acknowledgement and heartbeat send. -func (s *Session) HeartbeatLatency() time.Duration { - - return s.LastHeartbeatAck.Sub(s.LastHeartbeatSent) - -} - -// heartbeat sends regular heartbeats to Discord so it knows the client -// is still connected. If you do not send these heartbeats Discord will -// disconnect the websocket connection after a few seconds. -func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}, heartbeatIntervalMsec time.Duration) { - - s.log(LogInformational, "called") - - if listening == nil || wsConn == nil { - return - } - - var err error - ticker := time.NewTicker(heartbeatIntervalMsec * time.Millisecond) - defer ticker.Stop() - - for { - s.RLock() - last := s.LastHeartbeatAck - s.RUnlock() - sequence := atomic.LoadInt64(s.sequence) - s.log(LogDebug, "sending gateway websocket heartbeat seq %d", sequence) - s.wsMutex.Lock() - s.LastHeartbeatSent = time.Now().UTC() - err = wsConn.WriteJSON(heartbeatOp{1, sequence}) - s.wsMutex.Unlock() - if err != nil || time.Now().UTC().Sub(last) > (heartbeatIntervalMsec*FailedHeartbeatAcks) { - if err != nil { - s.log(LogError, "error sending heartbeat to gateway %s, %s", s.gateway, err) - } else { - s.log(LogError, "haven't gotten a heartbeat ACK in %v, triggering a reconnection", time.Now().UTC().Sub(last)) - } - s.Close() - s.reconnect() - return - } - s.Lock() - s.DataReady = true - s.Unlock() - - select { - case <-ticker.C: - // continue loop and send heartbeat - case <-listening: - return - } - } -} - -// UpdateStatusData ia provided to UpdateStatusComplex() -type UpdateStatusData struct { - IdleSince *int `json:"since"` - Activities []*Activity `json:"activities"` - AFK bool `json:"afk"` - Status string `json:"status"` -} - -type updateStatusOp struct { - Op int `json:"op"` - Data UpdateStatusData `json:"d"` -} - -func newUpdateStatusData(idle int, activityType ActivityType, name, url string) *UpdateStatusData { - usd := &UpdateStatusData{ - Status: "online", - } - - if idle > 0 { - usd.IdleSince = &idle - } - - if name != "" { - usd.Activities = []*Activity{{ - Name: name, - Type: activityType, - URL: url, - }} - } - - return usd -} - -// UpdateGameStatus is used to update the user's status. -// If idle>0 then set status to idle. -// If name!="" then set game. -// if otherwise, set status to active, and no activity. -func (s *Session) UpdateGameStatus(idle int, name string) (err error) { - return s.UpdateStatusComplex(*newUpdateStatusData(idle, ActivityTypeGame, name, "")) -} - -// UpdateStreamingStatus is used to update the user's streaming status. -// If idle>0 then set status to idle. -// If name!="" then set game. -// If name!="" and url!="" then set the status type to streaming with the URL set. -// if otherwise, set status to active, and no game. -func (s *Session) UpdateStreamingStatus(idle int, name string, url string) (err error) { - gameType := ActivityTypeGame - if url != "" { - gameType = ActivityTypeStreaming - } - return s.UpdateStatusComplex(*newUpdateStatusData(idle, gameType, name, url)) -} - -// UpdateListeningStatus is used to set the user to "Listening to..." -// If name!="" then set to what user is listening to -// Else, set user to active and no activity. -func (s *Session) UpdateListeningStatus(name string) (err error) { - return s.UpdateStatusComplex(*newUpdateStatusData(0, ActivityTypeListening, name, "")) -} - -// UpdateStatusComplex allows for sending the raw status update data untouched by discordgo. -func (s *Session) UpdateStatusComplex(usd UpdateStatusData) (err error) { - - s.RLock() - defer s.RUnlock() - if s.wsConn == nil { - return ErrWSNotFound - } - - s.wsMutex.Lock() - err = s.wsConn.WriteJSON(updateStatusOp{3, usd}) - s.wsMutex.Unlock() - - return -} - -type requestGuildMembersData struct { - GuildIDs []string `json:"guild_id"` - Query string `json:"query"` - Limit int `json:"limit"` - Presences bool `json:"presences"` -} - -type requestGuildMembersOp struct { - Op int `json:"op"` - Data requestGuildMembersData `json:"d"` -} - -// RequestGuildMembers requests guild members from the gateway -// The gateway responds with GuildMembersChunk events -// guildID : Single Guild ID to request members of -// query : String that username starts with, leave empty to return all members -// limit : Max number of items to return, or 0 to request all members matched -// presences : Whether to request presences of guild members -func (s *Session) RequestGuildMembers(guildID string, query string, limit int, presences bool) (err error) { - data := requestGuildMembersData{ - GuildIDs: []string{guildID}, - Query: query, - Limit: limit, - Presences: presences, - } - err = s.requestGuildMembers(data) - return -} - -// RequestGuildMembersBatch requests guild members from the gateway -// The gateway responds with GuildMembersChunk events -// guildID : Slice of guild IDs to request members of -// query : String that username starts with, leave empty to return all members -// limit : Max number of items to return, or 0 to request all members matched -// presences : Whether to request presences of guild members -func (s *Session) RequestGuildMembersBatch(guildIDs []string, query string, limit int, presences bool) (err error) { - data := requestGuildMembersData{ - GuildIDs: guildIDs, - Query: query, - Limit: limit, - Presences: presences, - } - err = s.requestGuildMembers(data) - return -} - -func (s *Session) requestGuildMembers(data requestGuildMembersData) (err error) { - s.log(LogInformational, "called") - - s.RLock() - defer s.RUnlock() - if s.wsConn == nil { - return ErrWSNotFound - } - - s.wsMutex.Lock() - err = s.wsConn.WriteJSON(requestGuildMembersOp{8, data}) - s.wsMutex.Unlock() - - return -} - -// onEvent is the "event handler" for all messages received on the -// Discord Gateway API websocket connection. -// -// If you use the AddHandler() function to register a handler for a -// specific event this function will pass the event along to that handler. -// -// If you use the AddHandler() function to register a handler for the -// "OnEvent" event then all events will be passed to that handler. -func (s *Session) onEvent(messageType int, message []byte) (*Event, error) { - - var err error - var reader io.Reader - reader = bytes.NewBuffer(message) - - // If this is a compressed message, uncompress it. - if messageType == websocket.BinaryMessage { - - z, err2 := zlib.NewReader(reader) - if err2 != nil { - s.log(LogError, "error uncompressing websocket message, %s", err) - return nil, err2 - } - - defer func() { - err3 := z.Close() - if err3 != nil { - s.log(LogWarning, "error closing zlib, %s", err) - } - }() - - reader = z - } - - // Decode the event into an Event struct. - var e *Event - decoder := json.NewDecoder(reader) - if err = decoder.Decode(&e); err != nil { - s.log(LogError, "error decoding websocket message, %s", err) - return e, err - } - - s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s\n\n", e.Operation, e.Sequence, e.Type, string(e.RawData)) - - // Ping request. - // Must respond with a heartbeat packet within 5 seconds - if e.Operation == 1 { - s.log(LogInformational, "sending heartbeat in response to Op1") - s.wsMutex.Lock() - err = s.wsConn.WriteJSON(heartbeatOp{1, atomic.LoadInt64(s.sequence)}) - s.wsMutex.Unlock() - if err != nil { - s.log(LogError, "error sending heartbeat in response to Op1") - return e, err - } - - return e, nil - } - - // Reconnect - // Must immediately disconnect from gateway and reconnect to new gateway. - if e.Operation == 7 { - s.log(LogInformational, "Closing and reconnecting in response to Op7") - s.CloseWithCode(websocket.CloseServiceRestart) - s.reconnect() - return e, nil - } - - // Invalid Session - // Must respond with a Identify packet. - if e.Operation == 9 { - - s.log(LogInformational, "sending identify packet to gateway in response to Op9") - - err = s.identify() - if err != nil { - s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err) - return e, err - } - - return e, nil - } - - if e.Operation == 10 { - // Op10 is handled by Open() - return e, nil - } - - if e.Operation == 11 { - s.Lock() - s.LastHeartbeatAck = time.Now().UTC() - s.Unlock() - s.log(LogDebug, "got heartbeat ACK") - return e, nil - } - - // Do not try to Dispatch a non-Dispatch Message - if e.Operation != 0 { - // But we probably should be doing something with them. - // TEMP - s.log(LogWarning, "unknown Op: %d, Seq: %d, Type: %s, Data: %s, message: %s", e.Operation, e.Sequence, e.Type, string(e.RawData), string(message)) - return e, nil - } - - // Store the message sequence - atomic.StoreInt64(s.sequence, e.Sequence) - - // Map event to registered event handlers and pass it along to any registered handlers. - if eh, ok := registeredInterfaceProviders[e.Type]; ok { - e.Struct = eh.New() - - // Attempt to unmarshal our event. - if err = json.Unmarshal(e.RawData, e.Struct); err != nil { - s.log(LogError, "error unmarshalling %s event, %s", e.Type, err) - } - - // Send event to any registered event handlers for it's type. - // Because the above doesn't cancel this, in case of an error - // the struct could be partially populated or at default values. - // However, most errors are due to a single field and I feel - // it's better to pass along what we received than nothing at all. - // TODO: Think about that decision :) - // Either way, READY events must fire, even with errors. - s.handleEvent(e.Type, e.Struct) - } else { - s.log(LogWarning, "unknown event: Op: %d, Seq: %d, Type: %s, Data: %s", e.Operation, e.Sequence, e.Type, string(e.RawData)) - } - - // For legacy reasons, we send the raw event also, this could be useful for handling unknown events. - s.handleEvent(eventEventType, e) - - return e, nil -} - -// ------------------------------------------------------------------------------------------------ -// Code related to voice connections that initiate over the data websocket -// ------------------------------------------------------------------------------------------------ - -type voiceChannelJoinData struct { - GuildID *string `json:"guild_id"` - ChannelID *string `json:"channel_id"` - SelfMute bool `json:"self_mute"` - SelfDeaf bool `json:"self_deaf"` -} - -type voiceChannelJoinOp struct { - Op int `json:"op"` - Data voiceChannelJoinData `json:"d"` -} - -// ChannelVoiceJoin joins the session user to a voice channel. -// -// gID : Guild ID of the channel to join. -// cID : Channel ID of the channel to join. -// mute : If true, you will be set to muted upon joining. -// deaf : If true, you will be set to deafened upon joining. -func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *VoiceConnection, err error) { - - s.log(LogInformational, "called") - - s.RLock() - voice, _ = s.VoiceConnections[gID] - s.RUnlock() - - if voice == nil { - voice = &VoiceConnection{} - s.Lock() - s.VoiceConnections[gID] = voice - s.Unlock() - } - - voice.Lock() - voice.GuildID = gID - voice.ChannelID = cID - voice.deaf = deaf - voice.mute = mute - voice.session = s - voice.Unlock() - - err = s.ChannelVoiceJoinManual(gID, cID, mute, deaf) - if err != nil { - return - } - - // doesn't exactly work perfect yet.. TODO - err = voice.waitUntilConnected() - if err != nil { - s.log(LogWarning, "error waiting for voice to connect, %s", err) - voice.Close() - return - } - - return -} - -// ChannelVoiceJoinManual initiates a voice session to a voice channel, but does not complete it. -// -// This should only be used when the VoiceServerUpdate will be intercepted and used elsewhere. -// -// gID : Guild ID of the channel to join. -// cID : Channel ID of the channel to join, leave empty to disconnect. -// mute : If true, you will be set to muted upon joining. -// deaf : If true, you will be set to deafened upon joining. -func (s *Session) ChannelVoiceJoinManual(gID, cID string, mute, deaf bool) (err error) { - - s.log(LogInformational, "called") - - var channelID *string - if cID == "" { - channelID = nil - } else { - channelID = &cID - } - - // Send the request to Discord that we want to join the voice channel - data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, channelID, mute, deaf}} - s.wsMutex.Lock() - err = s.wsConn.WriteJSON(data) - s.wsMutex.Unlock() - return -} - -// onVoiceStateUpdate handles Voice State Update events on the data websocket. -func (s *Session) onVoiceStateUpdate(st *VoiceStateUpdate) { - - // If we don't have a connection for the channel, don't bother - if st.ChannelID == "" { - return - } - - // Check if we have a voice connection to update - s.RLock() - voice, exists := s.VoiceConnections[st.GuildID] - s.RUnlock() - if !exists { - return - } - - // We only care about events that are about us. - if s.State.User.ID != st.UserID { - return - } - - // Store the SessionID for later use. - voice.Lock() - voice.UserID = st.UserID - voice.sessionID = st.SessionID - voice.ChannelID = st.ChannelID - voice.Unlock() -} - -// onVoiceServerUpdate handles the Voice Server Update data websocket event. -// -// This is also fired if the Guild's voice region changes while connected -// to a voice channel. In that case, need to re-establish connection to -// the new region endpoint. -func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) { - - s.log(LogInformational, "called") - - s.RLock() - voice, exists := s.VoiceConnections[st.GuildID] - s.RUnlock() - - // If no VoiceConnection exists, just skip this - if !exists { - return - } - - // If currently connected to voice ws/udp, then disconnect. - // Has no effect if not connected. - voice.Close() - - // Store values for later use - voice.Lock() - voice.token = st.Token - voice.endpoint = st.Endpoint - voice.GuildID = st.GuildID - voice.Unlock() - - // Open a connection to the voice server - err := voice.open() - if err != nil { - s.log(LogError, "onVoiceServerUpdate voice.open, %s", err) - } -} - -type identifyOp struct { - Op int `json:"op"` - Data Identify `json:"d"` -} - -// identify sends the identify packet to the gateway -func (s *Session) identify() error { - s.log(LogDebug, "called") - - // TODO: This is a temporary block of code to help - // maintain backwards compatability - if s.Compress == false { - s.Identify.Compress = false - } - - // TODO: This is a temporary block of code to help - // maintain backwards compatability - if s.Token != "" && s.Identify.Token == "" { - s.Identify.Token = s.Token - } - - // TODO: Below block should be refactored so ShardID and ShardCount - // can be deprecated and their usage moved to the Session.Identify - // struct - if s.ShardCount > 1 { - - if s.ShardID >= s.ShardCount { - return ErrWSShardBounds - } - - s.Identify.Shard = &[2]int{s.ShardID, s.ShardCount} - } - - // Send Identify packet to Discord - op := identifyOp{2, s.Identify} - s.log(LogDebug, "Identify Packet: \n%#v", op) - s.wsMutex.Lock() - err := s.wsConn.WriteJSON(op) - s.wsMutex.Unlock() - - return err -} - -func (s *Session) reconnect() { - - s.log(LogInformational, "called") - - var err error - - if s.ShouldReconnectOnError { - - wait := time.Duration(1) - - for { - s.log(LogInformational, "trying to reconnect to gateway") - - err = s.Open() - if err == nil { - s.log(LogInformational, "successfully reconnected to gateway") - - // I'm not sure if this is actually needed. - // if the gw reconnect works properly, voice should stay alive - // However, there seems to be cases where something "weird" - // happens. So we're doing this for now just to improve - // stability in those edge cases. - s.RLock() - defer s.RUnlock() - for _, v := range s.VoiceConnections { - - s.log(LogInformational, "reconnecting voice connection to guild %s", v.GuildID) - go v.reconnect() - - // This is here just to prevent violently spamming the - // voice reconnects - time.Sleep(1 * time.Second) - - } - return - } - - // Certain race conditions can call reconnect() twice. If this happens, we - // just break out of the reconnect loop - if err == ErrWSAlreadyOpen { - s.log(LogInformational, "Websocket already exists, no need to reconnect") - return - } - - s.log(LogError, "error reconnecting to gateway, %s", err) - - <-time.After(wait * time.Second) - wait *= 2 - if wait > 600 { - wait = 600 - } - } - } -} - -// Close closes a websocket and stops all listening/heartbeat goroutines. -// TODO: Add support for Voice WS/UDP -func (s *Session) Close() error { - return s.CloseWithCode(websocket.CloseNormalClosure) -} - -// CloseWithCode closes a websocket using the provided closeCode and stops all -// listening/heartbeat goroutines. -// TODO: Add support for Voice WS/UDP connections -func (s *Session) CloseWithCode(closeCode int) (err error) { - - s.log(LogInformational, "called") - s.Lock() - - s.DataReady = false - - if s.listening != nil { - s.log(LogInformational, "closing listening channel") - close(s.listening) - s.listening = nil - } - - // TODO: Close all active Voice Connections too - // this should force stop any reconnecting voice channels too - - if s.wsConn != nil { - - s.log(LogInformational, "sending close frame") - // To cleanly close a connection, a client should send a close - // frame and wait for the server to close the connection. - s.wsMutex.Lock() - err := s.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(closeCode, "")) - s.wsMutex.Unlock() - if err != nil { - s.log(LogInformational, "error closing websocket, %s", err) - } - - // TODO: Wait for Discord to actually close the connection. - time.Sleep(1 * time.Second) - - s.log(LogInformational, "closing gateway websocket") - err = s.wsConn.Close() - if err != nil { - s.log(LogInformational, "error closing websocket, %s", err) - } - - s.wsConn = nil - } - - s.Unlock() - - s.log(LogInformational, "emit disconnect event") - s.handleEvent(disconnectEventType, &Disconnect{}) - - return -} diff --git a/vendor/modules.txt b/vendor/modules.txt index a129a55f..030df42b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -51,6 +51,9 @@ github.com/av-elier/go-decimal-to-rational # github.com/blang/semver v3.5.1+incompatible ## explicit github.com/blang/semver +# github.com/bwmarrin/discordgo v0.24.0 +## explicit; go 1.13 +github.com/bwmarrin/discordgo # github.com/d5/tengo/v2 v2.10.0 ## explicit; go 1.13 github.com/d5/tengo/v2 @@ -194,9 +197,6 @@ github.com/matrix-org/gomatrix github.com/matterbridge/Rocket.Chat.Go.SDK/models github.com/matterbridge/Rocket.Chat.Go.SDK/realtime github.com/matterbridge/Rocket.Chat.Go.SDK/rest -# github.com/matterbridge/discordgo v0.21.2-0.20210201201054-fb39a175b4f7 -## explicit; go 1.10 -github.com/matterbridge/discordgo # github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be ## explicit github.com/matterbridge/go-xmpp -- cgit v1.2.3