From d7793363509830fd2715df62e1f416fc7b8ba4dd Mon Sep 17 00:00:00 2001 From: Sean Hinchee Date: Wed, 24 Jan 2018 11:52:12 -0600 Subject: [PATCH] removing vendoring --- DiscordGo/LICENSE | 28 - DiscordGo/README.md | 130 -- DiscordGo/discord.go | 146 -- DiscordGo/discord_test.go | 276 --- DiscordGo/docs/GettingStarted.md | 142 -- DiscordGo/docs/img/discordgo.png | Bin 34331 -> 0 bytes DiscordGo/docs/index.md | 33 - DiscordGo/endpoints.go | 135 -- DiscordGo/event.go | 238 --- DiscordGo/eventhandlers.go | 1030 ----------- DiscordGo/events.go | 253 --- DiscordGo/examples/README.md | 11 - DiscordGo/examples/airhorn/README.md | 51 - DiscordGo/examples/airhorn/airhorn.dca | Bin 16300 -> 0 bytes DiscordGo/examples/airhorn/main.go | 211 --- DiscordGo/examples/api_basic/api_basic.go | 56 - DiscordGo/examples/appmaker/README.md | 57 - DiscordGo/examples/appmaker/main.go | 103 -- DiscordGo/examples/avatar/README.md | 49 - DiscordGo/examples/avatar/main.go | 89 - DiscordGo/examples/mytoken/README.md | 47 - DiscordGo/examples/mytoken/main.go | 40 - DiscordGo/examples/new_basic/new_basic.go | 49 - DiscordGo/examples/pingpong/README.md | 42 - DiscordGo/examples/pingpong/main.go | 71 - DiscordGo/logging.go | 95 - DiscordGo/message.go | 252 --- DiscordGo/message_test.go | 41 - DiscordGo/mkdocs.yml | 17 - DiscordGo/oauth2.go | 126 -- DiscordGo/oauth2_test.go | 57 - DiscordGo/ratelimit.go | 183 -- DiscordGo/ratelimit_test.go | 112 -- DiscordGo/restapi.go | 2041 --------------------- DiscordGo/restapi_test.go | 287 --- DiscordGo/state.go | 983 ---------- DiscordGo/structs.go | 660 ------- DiscordGo/tools/cmd/eventhandlers/main.go | 124 -- DiscordGo/types.go | 58 - DiscordGo/types_test.go | 24 - DiscordGo/user.go | 42 - DiscordGo/user_test.go | 16 - DiscordGo/util.go | 34 - DiscordGo/voice.go | 887 --------- DiscordGo/wsapi.go | 782 -------- websocket/AUTHORS | 8 - websocket/LICENSE | 22 - websocket/README.md | 64 - websocket/client.go | 392 ---- websocket/client_clone.go | 16 - websocket/client_clone_legacy.go | 38 - websocket/client_server_test.go | 512 ------ websocket/client_test.go | 72 - websocket/compression.go | 148 -- websocket/compression_test.go | 80 - websocket/conn.go | 1149 ------------ websocket/conn_broadcast_test.go | 134 -- websocket/conn_read.go | 18 - websocket/conn_read_legacy.go | 21 - websocket/conn_test.go | 497 ----- websocket/doc.go | 179 -- websocket/example_test.go | 46 - websocket/f.zip | Bin 63895 -> 0 bytes websocket/json.go | 60 - websocket/json_test.go | 119 -- websocket/mask.go | 55 - websocket/mask_safe.go | 15 - websocket/mask_test.go | 73 - websocket/prepared.go | 103 -- websocket/prepared_test.go | 74 - websocket/server.go | 292 --- websocket/server_test.go | 51 - websocket/util.go | 214 --- websocket/util_test.go | 74 - 74 files changed, 14634 deletions(-) delete mode 100644 DiscordGo/LICENSE delete mode 100644 DiscordGo/README.md delete mode 100644 DiscordGo/discord.go delete mode 100644 DiscordGo/discord_test.go delete mode 100644 DiscordGo/docs/GettingStarted.md delete mode 100644 DiscordGo/docs/img/discordgo.png delete mode 100644 DiscordGo/docs/index.md delete mode 100644 DiscordGo/endpoints.go delete mode 100644 DiscordGo/event.go delete mode 100644 DiscordGo/eventhandlers.go delete mode 100644 DiscordGo/events.go delete mode 100644 DiscordGo/examples/README.md delete mode 100644 DiscordGo/examples/airhorn/README.md delete mode 100644 DiscordGo/examples/airhorn/airhorn.dca delete mode 100644 DiscordGo/examples/airhorn/main.go delete mode 100644 DiscordGo/examples/api_basic/api_basic.go delete mode 100644 DiscordGo/examples/appmaker/README.md delete mode 100644 DiscordGo/examples/appmaker/main.go delete mode 100644 DiscordGo/examples/avatar/README.md delete mode 100644 DiscordGo/examples/avatar/main.go delete mode 100644 DiscordGo/examples/mytoken/README.md delete mode 100644 DiscordGo/examples/mytoken/main.go delete mode 100644 DiscordGo/examples/new_basic/new_basic.go delete mode 100644 DiscordGo/examples/pingpong/README.md delete mode 100644 DiscordGo/examples/pingpong/main.go delete mode 100644 DiscordGo/logging.go delete mode 100644 DiscordGo/message.go delete mode 100644 DiscordGo/message_test.go delete mode 100644 DiscordGo/mkdocs.yml delete mode 100644 DiscordGo/oauth2.go delete mode 100644 DiscordGo/oauth2_test.go delete mode 100644 DiscordGo/ratelimit.go delete mode 100644 DiscordGo/ratelimit_test.go delete mode 100644 DiscordGo/restapi.go delete mode 100644 DiscordGo/restapi_test.go delete mode 100644 DiscordGo/state.go delete mode 100644 DiscordGo/structs.go delete mode 100644 DiscordGo/tools/cmd/eventhandlers/main.go delete mode 100644 DiscordGo/types.go delete mode 100644 DiscordGo/types_test.go delete mode 100644 DiscordGo/user.go delete mode 100644 DiscordGo/user_test.go delete mode 100644 DiscordGo/util.go delete mode 100644 DiscordGo/voice.go delete mode 100644 DiscordGo/wsapi.go delete mode 100644 websocket/AUTHORS delete mode 100644 websocket/LICENSE delete mode 100644 websocket/README.md delete mode 100644 websocket/client.go delete mode 100644 websocket/client_clone.go delete mode 100644 websocket/client_clone_legacy.go delete mode 100644 websocket/client_server_test.go delete mode 100644 websocket/client_test.go delete mode 100644 websocket/compression.go delete mode 100644 websocket/compression_test.go delete mode 100644 websocket/conn.go delete mode 100644 websocket/conn_broadcast_test.go delete mode 100644 websocket/conn_read.go delete mode 100644 websocket/conn_read_legacy.go delete mode 100644 websocket/conn_test.go delete mode 100644 websocket/doc.go delete mode 100644 websocket/example_test.go delete mode 100644 websocket/f.zip delete mode 100644 websocket/json.go delete mode 100644 websocket/json_test.go delete mode 100644 websocket/mask.go delete mode 100644 websocket/mask_safe.go delete mode 100644 websocket/mask_test.go delete mode 100644 websocket/prepared.go delete mode 100644 websocket/prepared_test.go delete mode 100644 websocket/server.go delete mode 100644 websocket/server_test.go delete mode 100644 websocket/util.go delete mode 100644 websocket/util_test.go diff --git a/DiscordGo/LICENSE b/DiscordGo/LICENSE deleted file mode 100644 index 8d062ea57d85f00ce7dc583f833dbe4e93018d6d..0000000000000000000000000000000000000000 --- a/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/DiscordGo/README.md b/DiscordGo/README.md deleted file mode 100644 index eb9f14f041cd4900d40335f6c783426e6a68d201..0000000000000000000000000000000000000000 --- a/DiscordGo/README.md +++ /dev/null @@ -1,130 +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.gg/0SBTUU1wZTWT6sqd) - - - -DiscordGo is a [Go](https://golang.org/) package that provides low level -bindings to the [Discord](https://discordapp.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://discordapp.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 - -### master vs develop Branch -* The master branch represents the latest released version of DiscordGo. This -branch will always have a stable and tested version of the library. Each release -is tagged and you can easily download a specific release and view release notes -on the github [releases](https://github.com/bwmarrin/discordgo/releases) page. - -* The develop branch is where all development happens and almost always has -new features over the master branch. However breaking changes are frequently -added to develop and even sometimes bugs are introduced. Bugs get fixed and -the breaking changes get documented before pushing to master. - -*So, what should you use?* - -If you can accept the constant changing nature of *develop* then it is the -recommended branch to use. Otherwise, if you want to tail behind development -slightly and have a more stable package with documented releases then use *master* - -### 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 released version from the master branch.* - -```sh -go get github.com/bwmarrin/discordgo -``` - -If you want to use the develop branch, follow these steps next. - -```sh -cd $GOPATH/src/github.com/bwmarrin/discordgo -git checkout develop -``` - -### 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("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. -- Fork the develop branch and make your changes. -- 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 develop 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/DiscordGo/discord.go b/DiscordGo/discord.go deleted file mode 100644 index 40eabe183fcb9453c364c58e5f2cd6e4332a8c1f..0000000000000000000000000000000000000000 --- a/DiscordGo/discord.go +++ /dev/null @@ -1,146 +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 high level helper functions and easy entry points for the -// entire discordgo package. These functions are beling developed and are very -// experimental at this point. They will most likley 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" - "time" -) - -// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) -const VERSION = "0.17.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, -// 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 "` -// 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)}, - sequence: new(int64), - LastHeartbeatAck: time.Now().UTC(), - } - - // 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.Token = v[2] - } - - 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.Token = v - } 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.Token = auth - } else { - err = s.Login(auth, pass) - if err != nil || s.Token == "" { - if s.MFA { - err = ErrMFA - } else { - err = fmt.Errorf("Unable to fetch discord authentication token. %v", err) - } - return - } - } - - // The Session is now able to have RestAPI methods called on it. - // It is recommended that you now call Open() so that events will trigger. - - return -} diff --git a/DiscordGo/discord_test.go b/DiscordGo/discord_test.go deleted file mode 100644 index ca4472ada61b292277fb08842825e1098a8f1bfd..0000000000000000000000000000000000000000 --- a/DiscordGo/discord_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package discordgo - -import ( - "os" - "runtime" - "sync/atomic" - "testing" - "time" -) - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////// VARS NEEDED FOR TESTING -var ( - dg *Session // Stores a global discordgo user session - dgBot *Session // Stores a global discordgo bot session - - envToken = os.Getenv("DG_TOKEN") // Token to use when authenticating the user account - envBotToken = os.Getenv("DGB_TOKEN") // Token to use when authenticating the bot account - envEmail = os.Getenv("DG_EMAIL") // Email to use when authenticating - envPassword = os.Getenv("DG_PASSWORD") // Password to use when authenticating - envGuild = os.Getenv("DG_GUILD") // Guild ID to use for tests - envChannel = os.Getenv("DG_CHANNEL") // Channel ID to use for tests - // envUser = os.Getenv("DG_USER") // User ID to use for tests - envAdmin = os.Getenv("DG_ADMIN") // User ID of admin user to use for tests -) - -func init() { - if envBotToken != "" { - if d, err := New(envBotToken); err == nil { - dgBot = d - } - } - - if envEmail == "" || envPassword == "" || envToken == "" { - return - } - - if d, err := New(envEmail, envPassword, envToken); err == nil { - dg = d - } -} - -////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////// START OF TESTS - -// TestNew tests the New() function without any arguments. This should return -// a valid Session{} struct and no errors. -func TestNew(t *testing.T) { - - _, err := New() - if err != nil { - t.Errorf("New() returned error: %+v", err) - } -} - -// TestInvalidToken tests the New() function with an invalid token -func TestInvalidToken(t *testing.T) { - d, err := New("asjkldhflkjasdh") - if err != nil { - t.Fatalf("New(InvalidToken) returned error: %+v", err) - } - - // New with just a token does not do any communication, so attempt an api call. - _, err = d.UserSettings() - if err == nil { - t.Errorf("New(InvalidToken), d.UserSettings returned nil error.") - } -} - -// TestInvalidUserPass tests the New() function with an invalid Email and Pass -func TestInvalidEmailPass(t *testing.T) { - - _, err := New("invalidemail", "invalidpassword") - if err == nil { - t.Errorf("New(InvalidEmail, InvalidPass) returned nil error.") - } - -} - -// TestInvalidPass tests the New() function with an invalid Password -func TestInvalidPass(t *testing.T) { - - if envEmail == "" { - t.Skip("Skipping New(username,InvalidPass), DG_EMAIL not set") - return - } - _, err := New(envEmail, "invalidpassword") - if err == nil { - t.Errorf("New(Email, InvalidPass) returned nil error.") - } -} - -// TestNewUserPass tests the New() function with a username and password. -// This should return a valid Session{}, a valid Session.Token. -func TestNewUserPass(t *testing.T) { - - if envEmail == "" || envPassword == "" { - t.Skip("Skipping New(username,password), DG_EMAIL or DG_PASSWORD not set") - return - } - - d, err := New(envEmail, envPassword) - if err != nil { - t.Fatalf("New(user,pass) returned error: %+v", err) - } - - if d == nil { - t.Fatal("New(user,pass), d is nil, should be Session{}") - } - - if d.Token == "" { - t.Fatal("New(user,pass), d.Token is empty, should be a valid Token.") - } -} - -// TestNewToken tests the New() function with a Token. This should return -// the same as the TestNewUserPass function. -func TestNewToken(t *testing.T) { - - if envToken == "" { - t.Skip("Skipping New(token), DG_TOKEN not set") - } - - d, err := New(envToken) - if err != nil { - t.Fatalf("New(envToken) returned error: %+v", err) - } - - if d == nil { - t.Fatal("New(envToken), d is nil, should be Session{}") - } - - if d.Token == "" { - t.Fatal("New(envToken), d.Token is empty, should be a valid Token.") - } -} - -// TestNewUserPassToken tests the New() function with a username, password and token. -// This should return the same as the TestNewUserPass function. -func TestNewUserPassToken(t *testing.T) { - - if envEmail == "" || envPassword == "" || envToken == "" { - t.Skip("Skipping New(username,password,token), DG_EMAIL, DG_PASSWORD or DG_TOKEN not set") - return - } - - d, err := New(envEmail, envPassword, envToken) - if err != nil { - t.Fatalf("New(user,pass,token) returned error: %+v", err) - } - - if d == nil { - t.Fatal("New(user,pass,token), d is nil, should be Session{}") - } - - if d.Token == "" { - t.Fatal("New(user,pass,token), d.Token is empty, should be a valid Token.") - } -} - -func TestOpenClose(t *testing.T) { - if envToken == "" { - t.Skip("Skipping TestClose, DG_TOKEN not set") - } - - d, err := New(envToken) - if err != nil { - t.Fatalf("TestClose, New(envToken) returned error: %+v", err) - } - - if err = d.Open(); err != nil { - t.Fatalf("TestClose, d.Open failed: %+v", err) - } - - // We need a better way to know the session is ready for use, - // this is totally gross. - start := time.Now() - for { - d.RLock() - if d.DataReady { - d.RUnlock() - break - } - d.RUnlock() - - if time.Since(start) > 10*time.Second { - t.Fatal("DataReady never became true.yy") - } - runtime.Gosched() - } - - // TODO find a better way - // Add a small sleep here to make sure heartbeat and other events - // have enough time to get fired. Need a way to actually check - // those events. - time.Sleep(2 * time.Second) - - // UpdateStatus - maybe we move this into wsapi_test.go but the websocket - // created here is needed. This helps tests that the websocket was setup - // and it is working. - if err = d.UpdateStatus(0, time.Now().String()); err != nil { - t.Errorf("UpdateStatus error: %+v", err) - } - - if err = d.Close(); err != nil { - t.Fatalf("TestClose, d.Close failed: %+v", err) - } -} - -func TestAddHandler(t *testing.T) { - - testHandlerCalled := int32(0) - testHandler := func(s *Session, m *MessageCreate) { - atomic.AddInt32(&testHandlerCalled, 1) - } - - interfaceHandlerCalled := int32(0) - interfaceHandler := func(s *Session, i interface{}) { - atomic.AddInt32(&interfaceHandlerCalled, 1) - } - - bogusHandlerCalled := int32(0) - bogusHandler := func(s *Session, se *Session) { - atomic.AddInt32(&bogusHandlerCalled, 1) - } - - d := Session{} - d.AddHandler(testHandler) - d.AddHandler(testHandler) - - d.AddHandler(interfaceHandler) - d.AddHandler(bogusHandler) - - d.handleEvent(messageCreateEventType, &MessageCreate{}) - d.handleEvent(messageDeleteEventType, &MessageDelete{}) - - <-time.After(500 * time.Millisecond) - - // testHandler will be called twice because it was added twice. - if atomic.LoadInt32(&testHandlerCalled) != 2 { - t.Fatalf("testHandler was not called twice.") - } - - // interfaceHandler will be called twice, once for each event. - if atomic.LoadInt32(&interfaceHandlerCalled) != 2 { - t.Fatalf("interfaceHandler was not called twice.") - } - - if atomic.LoadInt32(&bogusHandlerCalled) != 0 { - t.Fatalf("bogusHandler was called.") - } -} - -func TestRemoveHandler(t *testing.T) { - - testHandlerCalled := int32(0) - testHandler := func(s *Session, m *MessageCreate) { - atomic.AddInt32(&testHandlerCalled, 1) - } - - d := Session{} - r := d.AddHandler(testHandler) - - d.handleEvent(messageCreateEventType, &MessageCreate{}) - - r() - - d.handleEvent(messageCreateEventType, &MessageCreate{}) - - <-time.After(500 * time.Millisecond) - - // testHandler will be called once, as it was removed in between calls. - if atomic.LoadInt32(&testHandlerCalled) != 1 { - t.Fatalf("testHandler was not called once.") - } -} diff --git a/DiscordGo/docs/GettingStarted.md b/DiscordGo/docs/GettingStarted.md deleted file mode 100644 index efeb3eaacd01cc5f5e3b200abc2c5a648b470fe1..0000000000000000000000000000000000000000 --- a/DiscordGo/docs/GettingStarted.md +++ /dev/null @@ -1,142 +0,0 @@ -# Getting Started - -This page is dedicated to helping you get started on your way to making the -next great Discord bot or client with DiscordGo. Once you've done that please -don't forget to submit it to the -[Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) list :). - - -**First, lets cover a few topics so you can make the best choices on how to -move forward from here.** - - -### Master vs Develop -**When installing DiscordGo you will need to decide if you want to use the current -master branch or the bleeding edge development branch.** - -* The **master** branch represents the latest released version of DiscordGo. This -branch will always have a stable and tested version of the library. Each -release is tagged and you can easily download a specific release and view the -release notes on the github [releases](https://github.com/bwmarrin/discordgo/releases) -page. - -* The **develop** branch is where all development happens and almost always has -new features over the master branch. However breaking changes are frequently -added the develop branch and sometimes bugs are introduced. Bugs get fixed -and the breaking changes get documented before pushing to master. - -*So, what should you use?* - -Due to the how frequently the Discord API is changing there is a high chance -that the *master* branch may be lacking important features. Because of that, if -you can accept the constant changing nature of the *develop* branch and the -chance that it may occasionally contain bugs then it is the recommended branch -to use. Otherwise, if you want to tail behind development slightly and have a -more stable package with documented releases then please use the *master* -branch instead. - - -### Client vs Bot - -You probably already know the answer to this but now is a good time to decide -if your goal is to write a client application or a bot. DiscordGo aims to fully -support both client applications and bots but there are some differences -between the two that you should understand. - -#### Client Application -A client application is a program that is intended to be used by a normal user -as a replacement for the official clients that Discord provides. An example of -this would be a terminal client used to read and send messages with your normal -user account or possibly a new desktop client that provides a different set of -features than the official desktop client that Discord already provides. - -Client applications work with normal user accounts and you can login with an -email address and password or a special authentication token. However, normal -user accounts are not allowed to perform any type of automation and doing so can -cause the account to be banned from Discord. Also normal user accounts do not -support multi-server voice connections and some other features that are -exclusive to Bot accounts only. - -To create a new user account (if you have not done so already) visit the -[Discord](https://discordapp.com/) website and click on the -**Try Discord Now, It's Free** button then follow the steps to setup your -new account. - - -#### Bot Application -A bot application is a special program that interacts with the Discord servers -to perform some form of automation or provide some type of service. Examples -are things like number trivia games, music streaming, channel moderation, -sending reminders, playing loud airhorn sounds, comic generators, YouTube -integration, Twitch integration.. You're *almost* only limited by your imagination. - -Bot applications require the use of a special Bot account. These accounts are -tied to your personal user account. Bot accounts cannot login with the normal -user clients and they cannot join servers the same way a user does. They do not -have access to some user client specific features however they gain access to -many Bot specific features. - -To create a new bot account first create yourself a normal user account on -Discord then visit the [My Applications](https://discordapp.com/developers/applications/me) -page and click on the **New Application** box. Follow the prompts from there -to finish creating your account. - - -**More information about Bots vs Client accounts can be found [here](https://discordapp.com/developers/docs/topics/oauth2#bot-vs-user-accounts)** - -# Requirements - -DiscordGo requires Go version 1.4 or higher. It has been tested to compile and -run successfully on Debian Linux 8, FreeBSD 10, and Windows 7. It is expected -that it should work anywhere Go 1.4 or higher works. If you run into problems -please let us know :) - -You must already have a working Go environment setup to use DiscordGo. If you -are new to Go and have not yet installed and tested it on your computer then -please visit [this page](https://golang.org/doc/install) first then I highly -recommend you walk though [A Tour of Go](https://tour.golang.org/welcome/1) to -help get your familiar with the Go language. Also checkout the relevent Go plugin -for your editor - they are hugely helpful when developing Go code. - -* Vim - [vim-go](https://github.com/fatih/vim-go) -* Sublime - [GoSublime](https://github.com/DisposaBoy/GoSublime) -* Atom - [go-plus](https://atom.io/packages/go-plus) -* Visual Studio - [vscode-go](https://github.com/Microsoft/vscode-go) - - -# Install DiscordGo - -Like any other Go package the fist step is to `go get` the package. This will -always pull the latest released version from the master branch. Then run -`go install` to compile and install the libraries on your system. - -#### Linux/BSD - -Run go get to download the package to your GOPATH/src folder. - -```sh -go get github.com/bwmarrin/discordgo -``` - -If you want to use the develop branch, follow these steps next. - -```sh -cd $GOPATH/src/github.com/bwmarrin/discordgo -git checkout develop -``` - -Finally, compile and install the package into the GOPATH/pkg folder. This isn't -absolutely required but doing this will allow the Go plugin for your editor to -provide autocomplete for all DiscordGo functions. - -```sh -cd $GOPATH/src/github.com/bwmarrin/discordgo -go install -``` - -#### Windows -Placeholder. - - -# Next... -More coming soon. diff --git a/DiscordGo/docs/img/discordgo.png b/DiscordGo/docs/img/discordgo.png deleted file mode 100644 index eb15a3dc0a02cfdce0d3ec413b17841c76c059f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34331 zcmce-Wl&sEw=LQP3lQ8A++BjZ1PBn^-8~T89YP2Yf&~cf?!n#NA-FpPcelIvPMtcp z?z{h9y+;+*y}NtMvN^{XbF2^rISFJ$JVXctg8WHRObG&kihTKh4F^8)Yvmk*KoI3W zi;5~JSlBw*I$GG;k$e&rC9!j`HT(R<6asOZPf<2kQQpN7xL&>xkq-8Yk+xOBeodkz z66%K)MMX!7fF>18nlXp1*pB}3BMg3DdN6c&xZh7~MTR#&5NF{xNi)L(vVwmM+^>0N zSxq-xZx26J&j{`mpQlug!neJC^IMWlk;Cszfd~=$YCvyS&)O!FusECY#juo*?KMbx$Ng2g>goRGU*2xAbd2EQqj2xJWKU zSRBePC7wzdk`4ng7&JE8fG99R45)p#Cm?<)Hwm6l5bYRJY^d}o2nnWXuo%RG4^lj; z_TwW&<2?k+OuCB)GRp{g|4H3U67r`O(l&yIQ~`PY2J&7pIFJ?s;|Ve7BPVx*d`o~} ziJz(SpVF72?a+aqN~qwkr{MYMul<_Q9$sCYiHK(86D~a#r{OEZL}8{5_k?$>{_Lo$ z=b+2eqp(41&mY}JP)bKeIKyjD^cnYB;hxEij5eOOMvLr(A&@mEpYbOK=1M$2J~%(? zr%cKt7z;h5bm!|(vkFw!eiNbS>JUK_K{W9ra4?`P{whNAefSR{odNTtFd=)JqQ&=UI~1l4)jAXb zJJdVF*QxC^P5uZlANpQn8j{WjLmB&ttlo=%Z+ZU%&C#?=lo5u}k8SNB7U0 zSXIFD#4wQ1_XTdIY<6z4ZQ^f|o@pbd7zx{F-zcxsC=FOjz1u?DV&8%@jc5I!nw6-u zl%Ju3&Jm#{Qj$@lRHhh|kIC+w$TJ*(^GQ0VBr|pb&jQ!t`g7Sorv~(;SR%Q&G4;QL zyAuE6{y|^3zCr$u9nc$WPV+A5E&N;acdl>C;$?E*O@zMdRe8_FvLC;bfSO>dszFyn z=Y<_LfEX(kOBIVx$E;FNm|M79h@^V1I;av+pr`CzDz3_=yitHK`MXH9FhyZs8DH7F zfGq}q&!x@fli`!;9UdCa9G*_#ND^R~VckjENt#JosWs4Y(_*N}ucl$xEb0b{y+V0ZMqv+;n?*{L%M*(=`pgEKxLM=ic$0`RbUNzq3#F6A9A@A;s-@DA_ z8lz~VvuqnRn1i%kpHq~RCM3*p&FdWl&Vxfkq`wX_#R)Pc;rA@{HE&8eUE1redHlUh zNvcfhILu!tJK{Y0d=#{pHpHGro2tp?IM&%Gx~jM}E`*d8``&7$*hgMe-ru}>hn(4% zIZC}`=1*;oYvHxwF|Od1phMb*;08a}GV8LY{t#DNZC~x6e5Y-ye&C9zFZr{{v&Zu| zB+ri$${aex|AW8wtNR-dFVfaIYktBSe94?olEKVW}|_*(LHtG$HOqVFKGg!FnR zek5w-GfJR>&%fz0c*AYO&`zH>)PX^P>feRJ?SeJ%##!PxT*nth9aqHe#D0q{Cz-Rz z^OP}4anK1CGFkG^b2YFx2=y?TX%0H$pV`R>c3GJL^jDF%Pb}Frs5>JkXW+{@HVpq{ECXS7bzr_ zjjl1@Gv79I87wvpta$3S39V%&iiPjDs*Eg#)nr7h5y}0hqD3=92VY@ss&3Xg95TT3 zyC{`@_;9FlU?Gw#`8e(Q5TgTQ?N?3CcWHG-LXGX_VoRP1etY)?#0_j%`tNFR#r$dI>{Ypzrq-hEHO7sL+v zQHr&I8;9*XOS9;OQy?klf_N<#{FZ+_of>z2q_+aLBq|Al^pCr7E7sh^x8 z{41cM6fsS|FM-la-13xi?8=DDiL4ry+_O$@C+5DndyTMiO@z!l@AA{LcnyAn*oDxX zkl9is@hnk1NjdRe+eo`Xen@b9dT-g|L1KXzHJP1X(_QLZrT_E%?$?QE^Rv;PDX}Tu zK1{cZ1`C-ro;$`PE0(CX#|>ysYkxmJhwkt-@r~%5)j6G2ENo$_*QqC#@#yPx7bPfw-t2rM<9d)QugUtg}^EcrN4 zKaLDG&bd+_l{CpW>0byRewY(o3Yrqqep;M8Ui7tWb4@=VUzE6p3k(>2in@$I6@`sR zh#35i{Cz)_TabrmUC7~a_aRSo)MS(@756FgX|NO*huP<5YJX;AJbpYQ?kq04)!HlP zaj$LvUD-lUM-Nd;zZ)Bi-i6xzVY}<1E8%h03ab337l|*;v&C)srSe$bee}EN=w}${ z`11#2HpT^jFyV}(CB(oh_`4Nt^aU>nc9NQo5C{_H%Rdw(G5Ia{5Z>vN>__<3SMLy* z*z?KxKR_TPkWXSCRNUqdmYluRww`-WXHxdte_`XZ%)ZqP7=O>31B)&utmr%Y!(Stq zk?sr|I$Py!%3qEKV}@Ys%~v?Zt~0lfEb$~?zKL>PxeF2rdfBb)2=YA&&N!#+^U{4( zL=$;UdK9b0=?~r@ppkwW8~^7<|JN4&R}22cgM7Zx@S4!p+t`wK$U=<}Lrc)-*#p$~5Sm6j&&tev^ix+bYYcAH=>7QM}H3`6flnjlsdMx9WFNjeElh zuOGBgQBl3Ty-&~1GR4F2U|?X-(9ndNJuX$k!^11=17U)1u4Sk&(b3TdTD(HI|MuMVd%}zIn7O7S!$sTx*u~on@s^< zzgjHTv9!0h!@$EUm+1+_R##VlaRNi}!@OLB+w0~}#pemCj?PZaYRf6ciF{|?#W9a)C-K`jIpKs><+lsc0s5(u#ZF zzZ}Ddvq9#cL;_l`Hkkx2CzS^d<|^$A^#c4upG2&zm5t8T7;ye>$VC&_|J>Y=kmU7= zkNGD#1lK7$ak)1YI+DUSQE4t;s8T?7FZW99RW?75=T$MQ?~@m&&0=3q7``nxhDsgW zWCwj6tl+Tr+FbTKH5AlbIexP_5zgjE*3R$9#z%|w4(JU?Y-U3W;t@n))AQf`L#J0& znzsX(Pv3@75b|{eF7egicnH0qHCij3&vr%#^- z9&XPyn>{%2Ils_6=$Kmt+3vgCR38R|m)VAfXX?S1?snwk>>VAAxVdqwKUoyr&Fb8w zg}EZUQ20#>MtR58hTLage7&}~H!?xansk86#`Ey}+cwR>u; z7a(JEvr1X!`d{Htx|?kR;IC>Om6$LapO{CA^*?s_H*47Z6Z^NnFTFoqvO8ITdALy9 zJ2P|bufzTZ4myrmUyVtxNz%xOZ00#FG0|h;8GH(&CFRw1NpbO?%F52Uxr86s^y{0O z-zqEFKsc;xY}{B|^Uuqpwz08sb9Z0c-PO!|q`Nv=Dq_&83O+fp18+T*`?JkPTPFYA zz8!gjLfx5VPvXIE3G9P)M3SP!L4rKqU2X*8>ZOYYpt_kmFBT^b=;-TxcKqp6g%w7V_aMU~Z}Suk9INW2lEIK`!VS@oIIS5PBgmOB48k(o$5s)_G?M2Z|UV1voD`;pi%`)Xucwt~+(b3TCY`)x{&HWU)#vdoyTzFIrIehs z^uF6>Ulg)LIKkRjhA4rcS6yGaf>jl_o9D*rVW_dG5J9syqDPCTdQbZxzBX=cklKu| z?kmz-e<5@k?}dRKLo(B+bqW4UGA?; zLBAf1V^-fDO&bLf7D;I0-=zxjq10Ggh8vevh}7c>%7FFIFzI^496`b@d3^zG;D<91 zu_kh)abHGSQAt(Rw&gb0p}8wVsi8n^93-f4@8`o)rZmA+6`tfgzY=2g}V#v$NU@sT^u*+^R+X${fDQg)?0R zmd?qA_sZo`&XZdf|K3!#3-{c*DUBwR9S+ed$#+M3+)?b>=h<2sHp|f<_myO=hZ!ho&gIB7yGiuife`W~&NqnR1iLanX3%fW1Wscz#ez%Yyx8a&+VT1A|t{?2wKy70AH z&iNX{#MR>8-!cE_HX*xIVUHsxufy>EI!GNJg+b`9yn;mI=9U4{5opg?g{k!3OquTj zXKW}gGhOS`9eom)Ez_a|T^QVo+Tm-w8yFvJQE8LDL{EfYS(Uz9CuI}ww@skmgGZv0 zPi-2TnsSaLfC90G$@_emYhbb7k;h?Evi)O*EPP@s|C4_V_g^DQ7)DZH`6 zNnFsYdTe!vG|uZ=eXaNVpy&RvL9T<6x03eKFZOjKFf`H=m1gcalt*$_pl7z$$ zjO=)UGBs#`gYr&Gk$M@J`K>fo)5W)*t%WLX@IeaKAyF!aV_W|G+4c+gNo>?}Y+H`& zoF%X?0lw*Mfd20o6BOCg?q+}Vz&8X;jXFD02)0)d+F1`wq4U928cgrM@htbPSoI$V ztFga+oz2ZxTlMOrh5;~*)P#0 zFL3l`$JKi0A-qIqv9|F~GZbR#ndMRg2bcv}x{WS$G!|~pYeB>@TZ3`9E-F)6+RByY zLVH3S+d}y0H~$XOcuqE4-#S*Do}R9SaV@WQYy%I$HH50C~1fD3AF<$hl+Y11OrZfYze z0B+=VQ~ynP^ZL)*Fdg1ML3OhG!3#}Kxu*k`Go`WsGrVKBKm(XT9jaKTUMYda7!JhA zR+lXW#T;D;yl@Yc(Vk(Vn=>;?J)?VxW3<*B1NL1&I9N;vunN`7m_Cp}DG7G<^w0rZ z#VZ}h1e>n(C|;HNzQ90b3d3Zme|wBs$j)h+IsOc>$Iatv&iwXfBf=IjJWC=1$A!DG z{JNq#nhgNZT&=vLn{QRWmBe}MDCxe=B3p4cyI}9yn?@JOVjM%SO)v&zhc;g zge5Wwtbu91PxW?q5#MDkxcA%(tBx<)?qiZQ8-JYiA4fUzywld!rjkqU1B8lFj6#^m z8bCND!TSTYipt9JLWQWvnPpOowy|G_uk&cc7&Y6LZH1_Jq*x))@F*sKvp-E1YbwLS zGRNF)LT(pDKVObN#pm2x^!fa`xLqYiuplp!=W#|#u0&IB_6Y);wePa~ zHxL9BL@TR(Ienkot?wLG({>mC6d}o0Fn=!s)eDyE@1YM@6VmKs3Da8Shbq3hbP9Uk zoq*d2PD1fg3U@)w!d zq@-0gONn3|Rv2|7;xOs-0fGi)hg2wC8P~h9(C`SYUD|ZPUej_7SC0Su%AhM4eJGwq zG$#t93Pw^B8&Hj=yS*aN891?8vP*{Y69-$Tl*`oXL-VTNq0U@!;dvWKe$+rX(BkyqJ6FL#Q$UiL(d?*9h5 zi;&7!c|8THGCF$pX`C;Ru*Bo)px<#+pcmu{jS7=bYfgYAzzC|1Wz8;6XuE{d*oOq6 z+8I6R$d&0TYS4XNX%PU^lY)aI_8prU8ZK^-Ru-kj7$!{XO%L?G#4nd^EPOrRRQmBK z4NXmZz_a!y3&=^q`n=O^aQftaRKJ6`B1FV~f}j1_&b5hj{BkQE|VeCf9{{IBlusK7~Not>2;W#hh20~jf_w9FAHY0aY{u;(^+I#$-Q!+MVe_g#`YuPpoJ z&~miq6O;)a-0AYvnR|_x+{*d^6h=xzDIRY|JrsPZ=psIvz3i4CxILt9a6KN`n<}Cd zrVxI@2mD7B1pFR=y@G%IAY);P8XA%rGGpW9646CWQOe z>QUCbDabR{c-}kDPQAOQ#l^=}-xJ{ZF&~ijFbY?NnVD^pEHs#*|15Fw7gpn^I3zha zSyEcMb56*QXu>@1p90VKVwtCZ)8CWn`-hTwqNB(q3eV2Z=URPRD?mODC*TqJ@`Ztx zmUeS{J9FH^&d#n4#15@$OLPJPnS~nb)ARG_lPE}i=Tb|57oeRnfYVh1wTP9K^-t-) zmql`ws;8lvA1qbU6CW+=qGZ%=^@&+mOp#&M(}V3xXDLRJ$Pdl(VC&;nRqi*RU{~na z-}3x!YY7$zIH8> zfX9Uah?rs@KV~Rd{kgvAXdi@clp^}~#afX82P}OcK!p56+Ub*)?;IbGaygu5G3t8l zaed^yfa~xQo!!BJbb6%E95iQO(3^+J!{W;Xv8mo4x41w8XUl%aL61lV`q-?{A--IR zp4zOq(cz{{+DeC1v87tMh6ei|zBuv-+?JKDRx9SFME2hHUR-(!2_O;D#Ukdc!pEPT z^~u(YNXcN8<4>bC+A{*$_p(YqMN^Ff8ciD|>DRV9krxXhaXgRX_Nrbj)CcIawpJ_o zgJv&OD@;22C-$*?!dE<9RMJ?XUkeG;g2viQfSYEVo4d zYsBSY?E!Q;B6synvf10)Ae}|+6LD!zRCzcCoq!Un{-1K*hNCEIKM8(%Q_6q0UJL-4 z9wz{*vR?yD=eZ89P{mRO4;inSC{|o_ItM9PuklA;U~9D3!|To<)Sc$*<<6Cr)%)SbhGYHS z#B}-MZj=vUe<1`!-ZxqmNBYfW4?_)8V$8b2orIe32^=-^@^Gz7v5g?$FLaOB? z0-{D)O{UaXnUa~yZyR7HGtG{lAP4tPq4$YFMx4>U9E8VajJwx6h;(q%h5%$S=rz?X z)V{y1o+sM<)8l$Vze08m?gc9Pk9 z!85yL?nXf<02INqrR90(18K+)1e)>%EZ#zbd{Xb2Z5mbM9V*Hl9rM5m8t!f^ zUN?3|#>RuXpRyNW`T*tV8ye1;P@e#6xyOxo4a&~wLpyoRUux0dio9{Q=O7@+vhAxy zyKSzvBBC41ae_qabZ3c|P+u<-|2|{Udei}(#A>Eg_37bU{%dcu+*aiD+X~iWdVU#q zcQIyU=-8JC8B?hk%-SDtURw33| zU#E%Tt<-X&u>THev_?wa7h=cRp;Slw zc-2oLpl9-64!7vXNOw&fof(^lgeRzLQY5W-hnWukrUjA3t=>YxcT<{#kk?7!N}~I{ z)3D103QD&VhRWCiPo$lj5dMl+R7`vy8EOl1-}g_5#G3pLbcd@g}-+Aixbz1objfer}?Q3Nsi zV;E6{p?ZJTEYY@>mG&=r0^_ZG(vRQag@x0gFo;BA?A5U8l+62Hr>Da|9CkpHP*VMh z&H5e*RBkP+1+pD;mV`e=n;*bM%B^OYjEsy1K5F-o6OsTp)M@Zup;8`;Br@ zdHl~W2`6|w>P)zNqgv442j-}|$kU&cjS=Cbngf#Q=2wNOAadjNT4n0+G>l@&JG~$pv>a1jd1$Uisr2<=`nUhdzNOjeGZDfnf*&=YbjxC3t}3 zWP+CwcEwXc?0IG`wLn~5slF!6Ll=n|R_a@FVwl!iNLMPzsN1%WMQjd$24x5g64Ml^ zoL8Y+D9W_?Rd`JS+E{$51}h&NazV2lu29=*O&9p&b_f?=#j1&(BD~UIfSn;ih-B5{ zceW$p5(wkFbY|JvRAm?ANPEsP5| zw?r;h)aDJ3?=e9-8_La0+g2%7)?V`wQ<-gzFT%F41 zi5S)kuJmn_h5zW+bS@e!ktFpoAj;VGb{62LUNh#dqEC)Ld3dmvi0Ef~yoboDGx;xD?3 z5CC}4PiEUXto%voH^N(Iw6q;RKij;~OmI4wReY6QjD(D=dV70|e!BBTrrzCk{7+6t zd~9G?G}1J7GUT<_m-J*!k(i*+3^|Fh^sg;?)8W{=ryF0sBk0mR==SYK&ff>o>Yv-Xn$L-T5bt4`*>;Mq>JK!k|Joaya`+I4sqB>nBEL za>1ipJ54hJ{FTwbD02-=rc%-P0^c8?s7UgA`IWWYA--INQkaM|03KDX`RQ)|AnA^y zu{Gi6@-mSsIwc5UG)$miP*TJvIIJ}=M*ii?W}mRywL@?HwVlne|qmw+o{)LPOa-Mel9g0CSX4zx20 zf<=k-r~s9dlCPf4a1J+QJM}c<@$@5*ZZ|duy8YNwkw{4Zy@dcehh8l% zd-3-@4)Wdz_!WJfOR@fFR|ux6Wp%@%4f#|G9G+LqyEkxVBgq9`*GsNq5)xVC+J>B) zY#{&8sg*F9N1|;dWWFzRL;@%%>#M#h5C+NLzmJU5^9adH^U=zg*&lqQQi|J&o!P(q zNyw+>4CwRJ`KVCDTx5jyp$AAGuN4Yj*KuM53Q~sx6#Y`hoC5Qgk{=K;GjnqZAcsSq zmOKXG&_y6vZ?X%O>~GIZdw#yl0(w()7#Wm95h0%o1Y{A^xwj&(Aq4dIYg2XaAfODZ zY7#2xw`c?;7%3ecf+Od>jei^T_+qaW^Ucz}&hUU9>+gw#)K6u83`LY8*7fY~K@(Yg z;Q&?BoW+`z-?OtRXam6&WD3i^p?w6?cy%T>AV!2j*aj5v=?YpfM6mWf4!ZzrG&Pp= zdpNu!42%&3zdW_V{Q+Q+^Zs;Hd_2vR1_lW%Vo_Q4qv&fzn3OO^AL|@E=~tU@Kn?!A zM^DhcmGTW1)>T5_wN$B}<{Z7=bpyZM;`0qup4K~#3f zG{axL+zdS2NYC%f{TXQvHFuQAj=drj-OXaqa_o|7~h|&Dnt31=&^6 zIC^CQuDnr(D_T80?LY<277(7tx`3}2#QFsz$x{wGUif7=u19msNC4I(5b{ah=#7Y5 z01u5#Vpf?zDg8Sg)RF#{W+lUl1mCM|X_Z}^Ef)OIf$htcO~Qg)^In$Q+sCpAzqVg_ zn8#WC#-v&C;!LP}x`<7C8NTOVu9Y|z7amu~EAn!3;V^w!M;^zH?srnJPi;S!Sf5zp zy~1k36M40Xj)RMvcd$^43Yb2q*~ew+zdkLyt!A`8ows^0tz10vXB%{%2EpI)7`wZ<%^V*6v9|KRi(Y@5@`@5Un3x7fRbgQ z#NLf^OogX#PEL;V1CUHP8FU+BfC>tHKZq{LVb~uy=r9$!F;Q!OaP!Qc9mN1K`c&n{ z5Twh^K3vn2FRB^idkXu``IC59(Fme0INyifoc*16Z(?pZFes|XkgxEcuvObzn28DU z;n>_l*7V+gF=L!)#hT9uv)oS~cwA#W^;S!@Ae~b7E$c!He0;=LRFyDYh9)Et`UuPFiuZT`Tz$4jlO0L7>h-d6T2Jqg9 zsCqwUoaK`n*O#y60X-^(3lL9vTZJN)t?hIThKh{iX!*+D$vWk4W3`4CHsG+lEEL~w zZaH49V~-@u8!aqL{{AxC3ntub)n`n7uhZ`DK+ie*E6p6cOG4{N8R>q$O^Zn_N5RB| z!iP`qQDfT`-<|<8bKaYgNf?hkB#ucH{cf<@Iq9O6QYN+K=l3jq5+DO^1~Z@H8>@cc z_#y5ns)}gW%=%Fgu~3=846E{8w^0<*v~CP%mD)6D9P3CoX0sQ19@oRivkdRR=o@#B zzg3#=r>rKs@*GTcban0R?1W3yj1{peDp>0sZV_>rbyiKB*x|2^Hx6>XeI?e`+*ruu zsJ<7Fs~%YzpPM%v8vekNWV*1DRs1btbBu4L)bFjGA?90;JyUH1n(n-}YoD%{OyA|J{=Yz8>Cq z%AIrJ*SW8xE5Y@_p-Q#mX%*J`Sl&KJosI6&JnPaV?exlR24acbp6#-)n; zh{NSB5)6rx(^cbv$mQYb2@G0YJ#(ciXanr$(~_4HI5yO5umv0huN zr)@DkuW{eVtB>vkKRpT|sqOeD3jJ@&_fVSDP2R6H;k{-oZbAX8WWQ>$kEck4;~;8<26e-kjc4I-c8G zal(P7WL=48j_+?-w6==EhUrC24*ze3)IL`v- z7-Krla#-T}+#iJ4J&vw{adc%LL9>92MZG4_Yk2hZBgcs5&k-vQHv8gE!mUuyZ#j)% z1U#=8##>$2I$&{_^@fJheAhKhu&!EEEiUKzfL7X-5fJxxFZYQZvRC$@GS#xG%V9$UCns3CY!_s@Dgv_! zh2Hp8YX$laC*I8De+oC{l_5Ph9jI-=%c;(-kJKL0`1|*}PSm zV%bx`aqYzxuKvm4jJ_i+Pl8`dz~}pAYQW^NW5l}%KQg9{{zfQcc_`pFzl{)aYW} z)ZBdRz6{)zDP-j2T;FV0MhdqEXPaG#ancXH8qOY;6htUC_uGPp>Se zG`kh4@nMZx{1*C#h&|Gkx=8{Qyq+gaK_-&(^j#Or+ZB2g3oLwcdT>IF)vjreq$G8q zRv+y-^_)>txi0BCRn=dfur0aZWoU~olQn(X)2*^yNd<_ZsA~T2^H1dhl*s;QYI;yv zXOi7hG45Qvw)<1wV^CKsfvPPi7}Xhd`bMKp+#`L3vLu#+uH-vXV+X>s14cjmwNc&# zt6iT$s^=Q)*MI=&2isI``>(Y6zQbRIe5mPHgtux4KVL6X*TGD8#GhMm9_f|IE!{8S z0!d9;CnY)lX^te?8+e1tEj1xv{innF-euRy<55GUH-y0uk4%VVm4 zc%(7UUFXwaSM4DZ!0@ZPy1l?}?iDChk$W&it-!}a-WaWKD1YslR_-2-NC6C1XqnxiL~V7m3`mHBjOw*2w3 zmyk0Z$n`l!=&r}h!zYHF+}=;GmRfF!p(2oDe)SO+d+xpiF0|oe{fI2vyxi*tPkcTr z6hc1H1!_UDk|{?}`2@B~r9#vjCBEEV+tx>Mad9~EyXqXpti)>qBFMtk$5*R_iy|Ex zOod9t1Oy>^HIY0^^JtaqKWFs3V~wK}o9>SqpFg=lz*Pd7TONJ|^oFC)*D!kKdiAVl zL`229Av8C4lK;uU28j~bT+Me+oq3ah=&_ zXnJ@Fciy76N0)dIyVaY7`#WzN7osq{DHE~Q8WR(f6Hs%Hy>n)&hI()EwBR;{RAs;sfd{eb(eG8)OSR^Pbp5Ueav=w8({UBg-F6UaW|Jw05`=zCpW&Zrcs z>}D{i0>C_1=gtOn8mhH6A{=D*pPQJyV{>zHyCo&#nuY_T8)eU)i9qwZ-HevM*-o;R z+N!v_y)Ad!RnpN-*1VoS&+bl%ni0m$0)=(R0iVk?lu^!1#?+IyW6>tQyO|x08i;b7 z*1sV2`oO+b$IG+6zx%ac6+AYGJ_mv7* z-r(`wMO3@XOjD9)%1V1%M9kKF^yyia)Mi7U|M}s(>2^EG)Tkb~mqzRCHzfJ%;`%=N z3X-XYP|5?pt#p{*SNv6F++&5aF^8wW*mxI2@*vk^fa*?I03)viA5)&$uc+97(9lqr zzDGQ6JCcMscYd}t^J!b2u0i6V^nPPodv4ZLB2YBlJ^DGW;&i-42%l9JE11|%IC1FG zBpzzSS$VW&&O_W1_|l0$HLaUuvIWI4%}N)-y`s;>Zg=($%B3*{av2_co+CCZL(8;j z<2ADQ0H~?uhQ}SZKBwMY?vClT z^VzI+(?k24mnUPx^r2$oi|~hr|NWFShAMcIuEL}N3feB;gMv^|R~&=grE4p%|9!5) zLoSOr>aWEV2eZtG6PJ*L4adfCrJM~0+3OWBTWt2V)Svtu83`{T{z>hz`)WCjJIa+> zhUQZGaA!w?5)~C^DDl(Nl+%MJ|M$5_{ku-BqVCAp{vRx&<*E$D3xE6(vFT#!vwF&; zu+5(Dkj%2spP2*(yK0`xwsQ4Iea<}=Q|QOCytByil(Ar&<)qWX+cmI|JWa__SQt}vY+hHlJ2`uwi#*3mw7Imte?zPP=lXYJBz^o7?PgjjSN5@h3m!gxkx?E; z!qC4eOF=@RpT<@#MvKw9NVVs{Jgj1B7_+RTTdNF;;2szMpoAZ_d~RmW$hqiiXMY9B z6}4xpI=&~v0fv8@>b*WiZ{sNCJo59uur)ppOjAWFhV#!)*IzWXgETA6H#T4u<3WyM* z+V5~D!+i`HnXZ{+%9|cY48C;)+cUQY%UYSvgT?^Z%s<-cJ?%bc+1}1j>Ng1GN-@Hd zHS~=fTO&Syit~+3j0ywm%p;{He*)ty40fgtztkOrgfbm)5jtOAU$rlSeTm5ToPCTXABkB)y$Alw;EF1JznE zYYtkLT_^JuRe;-S_2=%UxmmhWTnj$Xw544fFz)kw-6>wS?RqlI*k%hliR?}DAuHSIcuh2XNvJl4W~!Ki|78NVJD#if3f`e z_VAz#UEJMx!@=R>zyK*LWg#x%mW@d3XNe|W;j!~W@ef5_S@Sl0B!%iR3H3aT=hQulIiD=VdScQ=Mo zr{XJE-wK}XN^EoQE#@F$E_+7QG2#OwE{em}VndXb!Qt1#$$BCu{7s%LIU5<_ko@ri{SgHKz`R?j$S~z;?o;7^u-69NX)= zddgv!+l<9LRd&4J-ij@3Aq8blH36Ytl|^w5bevb=o$W&jY(YSZLdVAso%@ojlq2=x z0YWDi=dVJC`T;W9hngg6d4>hQIg1!lIh#li-tV`~yl7ZhKn0{$0oDw4v*E;U;OzY* zE6bQXGA|X;Zqa3=s8BTZqB?>a-v>RK%XHwq)kZ)N#F_JD*|Hw5xp z!mj1c%2gvZJUNDHywLH!A(ZyX)8a0Fa)}?woc#RBTq|DDfWj7VTiu2tYNoj>uB;6;*$LVX81PGV*JRNqKsUEPOlAVW1TXT2%BOy*ZQt zz@Ro;qNDS4TU2!;5b%hpwwl2J)}elrj+vP_;Na1!VbCpuIT|mbKJV-RTVuWfm#UPk zpM{f$l6)G45Kk0JXAm~eQj`0++ud#+3r^)CF0mEGYI-;YGc6?T&GkbC%2erxj6|A2{AlbZD+Td{#m>15t`<67Xn;QC&1`DQK(AK z>w2W~_HWLIjUHJAs1OOT*%TdqQj76(Ijnb7gWmq&g?w)WvH8JldH46;FksFfK^1yX zpqpm?qs9mU60iiLJnwQxj<~^^V9ShEJWQXc6A*MEi+|JiR97#Nl2d#~36B*Ao`IeCE{8#zWxKsyMmZRCp!C+zvc`a99ORH>+*fG|+6 zgzWvvm2o*Tv0(Cn=a(DbofOyLFz=g`=(5@!V6mYWgD!)FM=h7^R%I(koD*yb1l0CVAHF`{J1RI zK9<+=BIr3dP;z9cj31`>NYFjp4a>}2rro07;>9EJ<1;R;6p*K1^qtODVP3v0xC&-! z^5LD@IYEwNomW7<^UgtqZEKeE-W9$<{cKk&Mw8#b{g{&x@1W!t;0ODj>bLHReQx}LiFoPUOIb-tsyn|zZ&}ZP zRc~KG1Chu?(SOdMEj}s08(lPwn9t>)#oTxIZ%&Yp;on`SfJr=L03SXMkko=&Y41S! zE3kbkJ2zL597+l9MeuytF1Go)Wz8~IgTW*(8%y&}V_im|Rfv#6R{K|zNS z7Hnw$x0>KJdf-Vr^3VhPF%N7K34E^6U~pBPx*^eI_#I9&kaZe<2miAB3Btm{A|z0_ zeWBPp#4A15BcJO&94ob&6;_scdCSIWYqD+n=9pF?%_j?NAe0o|7m67!k zX3XxwRWA~xtb9;szyl5j6~T-%=efMvo~uL9n?h!MQ8VDO@M!VYZM>v7KHMf}&2q>m ze{EyWGb3};An+zEE{-ISL~uH&gpiq_u4HsDlhuzsl~4gW`>s4489UIzzO?vYd=qhu z+{o2>_$otHe{;!wD(rK8J^y*FGk-SxInc0MAZ~#uk8S=N=Oclw+IfI6ZlM$gsxV}v z3#;WfR~nfQmDr*4R?cQ=JxIW~QQ&zuTj%0^lL$n4q8(^Jo?s-n2K zX%5Q!g1O04t^qB5ox$u8?H-1^&&bASgZVwLlPcW>L3jYv8P!WZ1_F=+%0S2e{hu;g z3<8E-SowWhpPd=i?-{g*X`u zmiO)}bGcrGSSdg|qGtJGrFZx8&m43-oZS|6!)K<dDV@%Q-9xK$$ zV(SL}h^q0SW-!LR)^SBDdV762CClb<=?oczgB1R}45fIaA82ZdEhbA8i*@acdy(^_ zyk@Un@Gqg#Nb-s$ea@L>GwBoeiclSQTdo<&qZHfvkgF7+oTZ3GVx>1$c(qwE?0$1~ za&qW5_3M|A$HjJ~PlKiT4V^mq?%xnawulP{fQ0!-P!<(y$?Qz}nQUtyu_^Mr@c`Y5 zG^nq#b?bRxwZJ(UgJ?)X6~1?MG8gUV0GxnFkVm#OYFTL`e?dQ=^`+o;CJeZ=KHkzm z3mMvQ72{eZR{W@BTLFnCanBG&KsIHf%7dsb2c6+DJgkcmDpBRyKq4mtNrZ_ zk`B|KH==6%HJTRdD$1OfjXz9}RLlra* z8zTy`@mx)d^c)htc-5IIz~)3FnZ^(uv7sJE9 zM0ck~SU6rVqj`A`cLJ;`u3LZTurD^3o1*przZiErt%7Q5@KC5ii&W=OGq1c z><4n4pX>KUZ{+eKSdn>SAWP1E2#w9)>0kOrz_ek7zV^Z$y1;LKAA85wJNvc7*#(`Kx9Lm(I&2 zk<;<21=2;@fVY%ByDsv+IJvy|>C$MWxP+X^P&+v%lmF%(KwA%;j!7R2oFi*hN>YyD zYG$aW4Vi+_FYD!x&+VV3)y;nD1 zhs-uk=Gd%e`8F^gOB^xrzj$YKmHTTzQ!A~~NytQN;2vm1sRR$`&NO0U0^gLm^1Gsn zm2p(uoHIuM-m<9seA8He5fqf_dc$=+8`f*-`su#ey?T^;{2>ERktI|bnPPvmkl!1yIBXln$(W0VnH#mf5%q#2NL0w-bcZ~)|Z7JJ9tlR4B zDo6Sir`0-J?j-Xr0kZ+c!K)<~?% z%RQgY`BqYDxi3~}n%~WD0>o=I1c9_KQEEs1fM(s4ia&>?$h#Q1z$8lN~%ZChtqr3r{;erzXRpk>VgDI^jq%M zIIVvci?uCFD#fHGRLlD*Ru`J9m}2Mf?By{P;dsGPR}|KC?Z3q&>>HVYhSuKOZlAVF z@VN6i`@=F`WM=Y@nRH4@{Ine>9B^|fUH1I)e!A$X5(rfwzFw|Qxd5hCcW^uvFW?rH z!+e<0IT1XthBc|rIKQW^jc&y3Oh8V)Yn{Y6=cK=UNWT(+dEaGAwzDVMzBKBqTx{PS zF10gg6~e;9le+K{d7YSacpO_h`;)Nkq83!kPM`G)%V&?SX=|U^oKdJcn6)+0l3Sll z$BO;n4YOzbkv9CZq~?H4d$Nwq#3{J_T6*QszN^f#`yjXSuWAZwVf`_2?+=c%eo4>v zwze16zmWcvy>D5^AVj-q^mKvE)UDpp(+y2AB3-u z*~$)fr;HbAJJvDfJ2sR}6p(S=98>F0;5|{UZ+~I<`ycf57In&bbksb2oT|~)S=q?e z&D3Gr4X*p1uYGi^=Z`-;o$}n|&uE`B=+3?3uB1awh`TUs41$%Bw2!~u;EK_7+o!~7 z&SL`w^js4YDv zyD7O02vr*r#UJ6~rbW;y-91^LTrb@$f~zh^%^GlXw$}&KK>LwlQqRHo&O%hl0%_7P5=K$X>;fU!G&0@j>|zr?5J zm*%+VD;E*y=AR8Jr9xY&X|5be{o}^dpveHbrIwWQTL%vR!Bu%bTgTnD={=x7SXq*J z9e97Ry2j??t}Mlzr~k<2_#@VL_ZEK#UW`-UpRKm@Ss(tn*A_whZUUdUp8MOMFW*vV zBpubdo*lRFR#*KS{`4_#b7q~K$V0P6gZ@vy)~8X6^C_d=^1Fmv#<-lvVz#z}qy}ln zNb9v$McTfq#ib;7MSL?tAPIKnY~AX{F*Pdk%`uSrPA z5F4wQc!gG~#n6+ZOH55o-D6yOr(@UvST~Y4*H_si*}T1i*CNLwjS{OXvMcp>QU!?m zy;UBZo}IF6m5)1nZcTVzTD7;gvu)=V#+>wWPD@Cq+vjqydpIEr;L{m)ihFMHsA-z; z&(%+d$IMLKgr4L(j13!hd(=sy8nDHc;?R|!Ta7N;+B%39YgBulQ4CIT+IcJz52co( z`d6*T(ia}q$SW`kyZgF;|cG|DDZFG%ZyZ)g}kDTCg zkQzOnb~N2KFYf&@(j;4a*k|eiu_YB}f~LZ08;@{z;aw*@7ZemE7dz8FbH$6A7V<3V zS%1Ga&2P$oWZqR_)^d{M**syhsLpQ1^zi~G`2vGkK6@xU|D_|;`KtNqO@6-jc7CSI zXSuR`5IhYi>F4u@P#7O|K2Zq7Lh-Y8o;_;M)Aowk(TLZsmyGURQSEOWy_OPvRdU*E zepJ4D*Z*OURxkA1XpWM}0LxB1|Le zusox@e)m>4I1vi=B(+tc;XV@7M?NweUx{_xHk~MXI{NuDjq285#TDpixLp?RTpaHh z_3!AkUgBX0y^T9&-xv&Dz#^W1XWeP(a2bdjCz&t2I{*65cXAf0*_&047|lS~U}g!} z0gX8hhn#Xf9N}njonIyMf2t?kg(IQ6K~TS^HR6Yesv?C3ZD$PIP-*Q6f&lEVY9+L~ z5;QwSQlH7mJCJ2BK#QhF12Yaw!Gt;%UPQS@Vh`{O5Ym?KpY>_zh* zN+{wn5UqorwX57E$aa3)m)AAs0DK?J<}8Dx0Y`rTVV=# zmvk2{<+Wx}xanp9N68T{4y$%1sMnx#TNn36#p~TTQ@!U+!++ipcgupUv8x|-{4*R$ zw`4_yJj>gXU%whiN>J=|89nSvm>*lmjc;^L{Ic07Dj|Dk8RvXJXNCojNhBrbW!7*$ z=k=)hf>sJE8Kq>3&6pvZk$#`}&D5@18s}SC*#)y!_ks`VQu~3AwBxU?~KEw5QG=2NL>ETCtU7V)A?S)y~D|l-GMaaM)74|>jlOg1@ zOs(hKxF;jBJg@*?5Aqyk-H6JWUE8ihYxZ2#RjGD%EszW3&w5Fw zbvCe8s3)#P7Kg|2N|?ymT1?YC^tcR_#Fu<=8o6ppuIqF1lfh_t<%^tqX~5&LNE@r; zzZ1n$(h0SZ_1({WCmXyq3mZkVv1jDBIJRt;nUPH7&_Xi(22G#S(xL^H+U-O?yG~G* zI41psdT>XXuFUne>|H|bt4lI6<6OQf&5Eg}f_HLAm(-31Uoa{iKP*ptePP;56yTYQ z`jLmDb$A%xi@E)9apww;0!J`bB1{-xa&I;_VSVi9t)C;98+TDkw*1v5%2wI+dZxOX zPUfCoUwph1=uQ~DKv~4|m*@V%;G?=_vJNFmCMMZi*5McAH!hp6iYHGCfA5*~{(%_V$4(Ba-dXYHtBCePoJmHP`I^SODgv8oDjU ztDR4LI<^~KkK8f3XElVf%QdA>c5KbMe5%&irIY?ql$%F1#T@(3=hLyo<(rO}>3Xhn zCt6K~)Qj~!a9JZ=P4%SUxE9D>e2(Pj7&rS9l)R+pKyAZZ>e0U9JZHKXnd1<+my?Q*n9Ka5_Ug63X9M* z|1EJ@*(f9z;^}%fPF^8*ccWc~tXm7|i3q(PER)q`(gQM$(>RMH-jV)5)+DLB+nvD7-lkkhc}EF*g}+?Y#!i_(GcveGR=X;1g$?8N z5Rp-%t2?jRr|Z@>m*>Zs5^H`5$~V*RI^7H}Z8K{z=q19=ZSJ1ICzs2pRBJ3jiUEf zjyl`DVe7y;{5*B;J4gPVn=)1)L1d8YebE&8wroHujrs?U9s+s|Dh&8XRfT%Szkx0lh6H)(Sp`;84{M+?_&9*T~6u)$VPG7ao6*F}`H9+@0~(q|Oid z{JV2hM)QeWcsx^HJW*om+j1cbD%_Pa`%83}h4kTlcD2ZgSjIal44;BC=}+X6LKeJH zUI;CbbqrIk`gfceyt>tWKae<3RqD-8v|5?@_qy+zEY++BH(AykAd=h^Kcrq*&lp@s zV&PRcLPonEdNYAv#$;KA$JEnXRhw?|E@kZUN=GB3tH3Hl0W4Zof@>T{$&>il%@l_M z(lbbGj`z>2Pg7{*Arc@FVY6qm&+T-^;&h3cVlkDN5U}_eh#3wnYLyd@Tg}clmrt$P z2Rj*Rrl}gTa2Z;Urr8Y{6#rrQ!7yQgF009Sm(T>0$u++0N4sE$z3i5tZ14s8x-A9r)vI_|b0+3yjwIs5i#OD=N&)%s%RWIOgt( z%MwPF387QP(PAVKm!p{u1z>4Wyq_xL`Jgr09ASCc>wX_U*qB(|Tf5AWesQY*FTe){=to%DAT zUJ0XFWQNt*=w9P+q_e{10OJm#nZGoCfRe4QB%h{(n|#mIOA++6~bz8 zsch^IH|zNIpT;>orq0tfx^$l@{)4yhFpSG>oeDKrZ^hDpZig)K&Ul_|W3+C{j9tG% zgZtU^_|)l)6O!|lfvNMPtr4wO0Yd_h6BQFjkd<1Y@ywqicMdBI|MAD^uUqKK%;0+? zB^4*89_OpdXc`m1BU!px`LOI09(`|PCfe+oSekl1>(`S9(*ib_8AC?%kIu;@In%?t0eh9c8ubyI zwT>Yk)MSwcV#V}XFNq~1=_CR-guk+*r-1%FqELR`NpBV5DFR9c6R<%6pM*uZo0Y^4 z@o%6y%b#+xM&>)zL_c#~$g`t(!JWX1CQ*eN@zZwhQ^d}`h+`mQ+{mvk|F5N)TpU+y ze&bfJdT$kS^{i@6rl6cK>n%ag7F?t{`9p5}+QM`yniUHB%3W`NFkv#*DGah89*I*7 zjKk$vxy>gP8CBZ7TVzBuNO+vKVV*`aXLUbn>A3;U4)y*OXDpu!>kSDvt6X6?8~5nc zMPV)pyZJ2Xmsz5;+1Q_p0`oh2Y3c24Vfq}4q-(+nnhvxNu1U`+t|tOwC;iR73p(sB zxCF_@H3!z$-Xl4SvF^7z;}To?m)HeWD_cK+!t zibTAA#f!Zs6T^`tD!^XZOr_6&qU7x6Fjt|fSTM|?p0~KbiP~~2_|EI5$zlKF16BD72j0my z;IQ=lo4C-E1d6eF4LT$*0E7%BC05qd!Zn6U1jI6B+9JXjNH)}{={gFgmdf&9O7*z}abm+W||muG!f(sUeqjSHH1>)q$yB`)^-L z5)!t@)>65B6qr|F^^%nuSE`b{4@O~6!LbPBp8Aq9^FBm+Md?Pju|4-_iO`(h8D@`| zCLGatb-wF55bP!*E*RfDAlSmuq#NszR;J^Q@Q_oreMr3`O`WQ8+p*z{?u02ZQ@`aK z9=&P+I1wqr$LpwCwANajAwNh(g60|aLF~IQVSxq3j^H4vu_D7I`soSsPQb2X1U~XW zJMt}5|M4;Wfl3(03Jp49X2+f=bLf%i)_OcP{r#G|cpcZC zfQ|j>Qy2gD>nRej)G>iffY5ya8V)N;TeI;;W#oznT$3o;b9AhGjp zuD(Lu-`?f)9aXqV7pH%lz7V}IxU;jfgZYTX3y|@YQ4-VRL6Zopsgz$IsO?;jJ0x&$ zf=i&T3HJVG9Nl(pbGCr&_BYCE>Qi6$*rGxz9-J#_z~CU|w*4B*W|A{BJgfr38)^oI zfTfsR($jaO{y#2)Bw>UgDHX8_DQ5)cZ2M%UMARFepjl5oB ztMmyI2WhjPbFfgt>$3eDX6H`x_wzOW0Y_Hb>4uMSaIJd_{>*f) z^@-4f%7)iks0r$%e*P2Eu{;K*JdLKxnW^F$a}p2jhK{FVRuIyLt^BfJYXkyYK_b))9CWO5?~2OqA!~Gg$9- zk~8bN$r#xLZe_ndNdg<@yT5;${lS>MR(<)e+34CTM@rbEKn}6TR&Hd51O*zHDqrqm zi4;#x2-vdN_%t#XJzdz^5srIr_nz+<7qT{g0>)Hu3g}wtjl=f>2elmVPR#;Cv4_y* zP6zGIG{4igD_pVCA{&;e8%|$9k?h32bG71^w24&z+u}yCGw#3cUS-y6h<}EPgA9+d zfbS}>78+2No?=WYVWHMON(UyzC88Lrj;2l7XQdtDRidzHW1*`HZX-DvQqei^Dt;m( zy5K`#C1-Sfd1h+QLtM|BSTVesMxShcGF|JOctsv?D;b22*hiE5A6~SPf}f5ucmf9Z z^(L6CGPE@se?Tuu&sCea+PSf<&AyBI=oi9)bVILKNxi|n8evET_Z4#y(^PqX`A=D< zfltAyN^WSQlSL$Yplo{$VJ&W@U|2$}(=hO5=05IJ&Ur`$uO_vZE5DED=ab-Z`i7sY z3T_;$f>$IdSS0&bp=p?0*64(d6cgklrgnFJM!Yj{v|8Q&>CpO^-*-Q`mWhVO7v2L{ zp-RE?_h9?;wkv|R%Wb#bXVT(#u04_FM~*MeDxcA}_Dp4^7N>STNHll^g2iO9^}L7= zaY6L%?K{ap-R=S^(O`A$!F0S4w$k-kabd#;RoUL8w&L|9eYUu4omvjWYXV>7#z~jO z#l=Wq?NnGyHiO38mi&ZA`ME59a^QdgQtaI@lBMQN4EqXIr<{qS{u!cF0q(oMGN)Od=@>)@A@3gq(ti( z#4TfZcmPJPI5{l=ADa$*2C3-i{2)H83hY3Jz?wg$P~o6;`DJq@S7V%q(+Pr8SJ1eR zd`Gu`s8xf6{L#jAjf20D)Uye42c%tFe3zAOjZ+kqG{tP+@6%0VFN(8 z3xm5dg7q?1sMqi3OL=L}n+P92bY2>3dUWnSbrnr;0-*2q%0?gKG>;=!WyY<6hL3JM z_G?0jz2?bLZ3{?r7Z?e3_Vl!7gpkWnOQ*sPMY%qm65P{)U=Rc10=u<=r;* z)!#i;n4YdA@k;f^u)EmNL8_xyOY4&o&HkrtGGg|U!#{S*+wP!UWp6vYy{ecwEbhX| z?qp~go3HlHu?Ah74LZg92|4?Rt=b>B zow|AO!1*_~>t?#7BP4{RudiQbJ{|<7Wx4yTA~ZZannawIq;Q|z*dwgVrkB_4woS*s zykF8!99+jG(^&i|`wrO*w?!`CQhseL2v4 z0(P2TZ&YSf!<`N#GuQ%M5Ico3D3*?#&^*>vCTs03Uf>m2O#Vvg7ofUwJP!NS&v$@n zRkwE+Y@K@!u#;kAI}tXVuu*LRrSIdGlaoe5BB4dh2uS3=7}%Y;^jFN&74;h}@PQs_ zUAJmTPWjC*r&#E=ohr3|7jy}L9}+bLR|8mAz#x4%vawZ2HPu2NlTN$ z(VcFkny20b{+bHeqsm(Yk*+Ost@)-lXV|uy##)Vrt6GJr6Bd}n?7m?5`gXEm=*ZPx`7N##3To%S`f(7cWdm4J7krDhZ1@NE(9=-Vd@83VM*|S2u z#sY-ZE9mlZ@$p~x6mH0KSJx<{yRyJCQ^=CM?@g!DmiCJFjDk#f<*Ig?_vV>Xe*)$8 z2u^Df&vk9MFt5{&G+h1ru{3V{q+YX)o%0cK4vn@S-^w46h;K;9eL9t#!#EegU<2TJ zbb&LCtpSXut4NFS3bTp-sL!;aD8e&I&!(-?opPcgYUP3X?OU!aJ{0vC%=pe!wD z(0Q5s>}4Lv=)3Xd<_~0(TCp?UBP|IFM=}LlNJLBrwvXpUGWh;4cZ5Fc0^$odP%kpU zk6DHu1-8+GMh#{IKHUP*eP6Zp639otmp zQk|WT&LzaGftbPCnguu|PoF>kdKZgWdjII?{@mheOT z#7&Xj>n|9su8)Ip_vbuW6HBECk??%o$WT~?kQNpeB4A$^8Mb4`Yiqwho>%TFf6pw| zv|BD75UZ_S`gz!YHSApJ7;A#~rP%vlR;0=7P8nrZGMLhJd?iod5Dxa zy~1ZNO`LG1r6QVP{Y?82wA7p0Xro`uYqpYqk$b9r<(F({=V?}9_~!Bh>+T%o)iGnh z$!;i_r+-~7_l3!-8nh4gnWa$3y^-^0QngW8~Z1iz~V9MURr&)06MiNaQj9T z1_uYV8{82@#YqI})nwCdIe)w-_^}bk3pUd*IME?j(nx`)e3fB zC|9qtUG}pDYEx=jS~X;j;9b4~PVFvq_F1EwFHZ5ACtGk$`rJ;9m=|4aqb~nr%~0iq(QVe-qWg=mK6Pvju}?$kd&pMrzBFcWra5$qZn5F z=%yy8FgO^;s4LFT$9QSq3cQQXt<5{^KYC$eone%;is|<#B_p1&u&}I(MV2z`2!3e#jM4=MhxPGcGg~ry zVg~Xa`n`1Vqqlxz*~}Qc%Y)0iVDaWWNus%UYGT<5GOhV`^zZGFzo=7ltC?{-C#Qa$ zm9f!Ld=nFsy1F`+N0@6zuJ9ik<*fSx0s@Vf3d?!S`|KUDB%@m`{aMq?D3hV&p)cwE z5$E4!&OM}ad$Z;XQQm_UYX8Ycu`|m&785MMoSEpFnsQSUo@7Fy5(;|E`x9Zc4na(R zU^C{>k@?)gvMc$;T#>DvBhfD1j2rIx&goXC;ewowcBNm(;Iqdi%~{X7-hVPZa^+#3 z5=!YBeV4YR&>WmO$!~TC-t}PCsQZf=W@ZPgpwd3Ha@CfyHZ7u;F0{WH*nqfa1TO## zvMYs1aZwCZ$DtaG1ebhB0L1d?KD}Zh>i&1VZ9SHy@voC!+BKJ&xctXKE`|C`ITl3x z(6kk~=|=lcP#fKzEd9|oTnN=k4!oTQ=Bfl!x+D==gff>eYDc>U-z#Zch`5CnydF;1 zH3}*?^Np@CVa9mNs>MPigSk5w)In*OtwM>2+z-IS8ZH8(HL|*;QV4tslLN>F$-Ej8 zh!2)yJe=KXplV%CJ#?TQ{71iX$lkC$8w3UO(0PXs z!tlokSc(yrV}2ES%pK82=Qmh;nL(vi(^J)LR2NR~yT%vqUd|t26}X0l4S|l@U%k#! z5DC(16IjrSUC;G&`6sr4zOIdM7bXt;fmY~X*fYR1!Jyg`CCF5Nwej_gLA{g^DQQ2_ zl*h*QlV18pv}4^h`k+>08tf&6YNJo!OxnUC52P^&e@U!24WjwNEkUgo~ey;roaz9#r+}f)`L8}oQ!dR}4SaV5Lh-SFti7vbINA|uQGL-Yb zJR~oYvVvz}#zHiBBgp3{)1rmAn?imkS?jnh|DL@psa88@EsUS`-(^k)X#&T~%Dnzx zmc22@*J+HxxvII=j&}1?fv9UI$K&seW7o$E$sym9&n7J@U$wqvP&)aD%R0HlbdbWm zZd#r1Oyy5`fzOMVi=levr>Gb)l{{`$g;M&0!~yQnl8W0mr@XCBnHHA5V` zQT>j)H%G$fT!bKCVm5fMH5l_2yuQS+%-#BT3tRR_N4@`KhT|Xh>`v4|hfK-phryuU zz)|Da<&vDMY@`AZ7%`v#_;QiIn^xkvwY|VDUICH7-Zgm~w-S)*68__n5-ZuoQJ+}* z{`F11!Dhpj%9YUiXljZK(Q<7%!Pmi=)h3CNw5n0 zG2ut8zdI*0+;(Bl^xP%4=C@v2Zj95kha~2&%~CCWuWM);j_mlD>3qB_@kquP;j;;{ z2qT%p%uz^EB4Gvt()||>kNA0B{*H+{d!_3(|L3E7sR3ao^DI)N-~I<)upKvTPx8v5 zvGB-X24>Ar#Rn~Li({Cde-aiJrpaMGX0Zw$KH0M-Pg2sT31Y?$apo?`$DcVN$qW2S z+W8>*h%J2>_eSfk6Jb>0l-w?7>1tv=IPE}O;pus?#BwVvD(Vf`lSIJqo!*0N2e!cXu?iQ9)0^5!++pSygNv{`_kdcwWt?vnV$Alx< zr0<@zqGdR4ZwXTXg1cUIG>_*^a`B9t+ls0(=rM9~OcLkJANIGG|17ySuVanKl)Yw$ zBW=Q?U+itZjG$5Ye6ZS|3#``12BkdGUhW{y>4KplQZpMy-!)%6h)jK3%3o&|-$d#5 zn%nT(p4CNT zZ#d*Z;r{eGY9Ac4x@!Luv9&Y1?wNjC9obEgsgm~OP5tyOtA1*==C;3PNUxh>&i>)u z3WTXIiVu#P)q@hMyTC2FnRmBt@qT-l=pAu547#B|u`Mkw{Felb{Ji8*CU~)rw#SrRJAeg<%&D|J)jCP z{AS3jXl!J+>08Try?H!8ztViLEy5DZ(1;c-Qya85{kIcT@-}-5uYG7-7Vh6Fao#h? z&};PgI#$q;n6X%_kvHMFR~>)E)2CA-(R-XW!1Mm}{TyZ;zfQ40R?+!{8Yz?O&=8x8 zx#AtEF|pA1>_&PvT)axzN={~nABhTom7EopTa^~u{5k+^=M_v$Qm=K)|LXIlbkd^C zVp4Ik)`=MbDIKk{9nAU2<5@lyY=)>-UGq_C$MppG!R_x@ZGb9-)D>~KxV)$+Y zGuj@*2F}Ww`QOqbFeN#N{6-BHM}A8v=c}VDMRA{Sn*l5p9%Wqr+wSivC4jfD<{Ca8 zT-hw&FVzCqBs8J+D2G8FUVc0>I-z{Kg#(nhVtRB&V&#_c_AAaAtzeNf1;c;VMsuNd z>^m;dD_#mz-o|GT3_^yo><69Jm5 zt<1ws1vI7!32PH2JEE%$z&8=Hw`Zq|^b7gB|1Vc##g+4RoD(UfLCdXp2hCZxayGNb zjpEwE!#g*@#Ji?@z5FeWPEDB^;NnQ6@%v4p=D+wp1bjaM zmZ-tl$$E2G6c*R{D4wF_>=OW`K{{b~b?z{hua&tmkn#n+gr4qXZBXEL%wGwv)OSk3+i^9SG30Q^8==GBt^Y%0k`U#Kg*s8UNaI?mhi_D_j)uk*ESW*U z!}I6qF_F(&GaX#YLr>ly@-YvQNLxyl*wPE~g!r=OnMR6*2c`xFxXw=2VgeqSIKRu< z;|L9`Us|(qd=fS^F1`Bo9%Yhr zPZB7li9fbu9O83O1Sug+GSdjc-Bp5r|X-#0G zUI*Y3RC*ErXny^vv?@fn(E*Qcv6@@zCR~LVKQ55hM9#*}^EoHZ*78m@8_jW2@C?A? zgXQLGiJsSI?TPdCAiyvJ%MR^Ej|9aw^cTB}YNnIyF?nS@VmlcFrX%Xx=!~v%OaIpC z>;CY@ujA@9$5^@#xXrh&`*UVZxnM4LMXy5k_wb3_mw5EFO_K%*AJrokV`F{m!P3oN z?-Pmsp+OFofYClsB|JK(Jb%@lAd>%PHOFMWYX&opB?&X_J0Q+c%zColA5xOJpjO$f z20eW6pb6IKB(x6t^%vG(um5C3vv?qzT?88bAJ0(*$hx7Qm8FtP0Fn`h7@LiSJs7_t z22$Hahy^3W?D2P>&7G-TTe@C;07iign;DMayQn5N0BIQh2g) zaHy^irlG<(ykDb#dk$@GJMWzl#IThVO_FaL4FAnBxtX^sbKQGp!NEJV(kRtEKwQ0= zj>Q{1WYeJx?sJnBAL4-h_7|XN1uB^ztx_{T*i822dx+j_Wtom#eUyz4o=*^deEQCQ zPOQKuW7KTG^ku-6EhQw1sJ}ot6vOATkE>C54)|)<{&F{1n+w8SU2&J$^8hj|U!rda zNU1`V#DU!Zlikz0uLh^-74_vVC;BX>Jg%$%4{ z+vQ}j&7H^Xc6rX7{i_@t`Ltxam;d&s*kWpV!Vq5fV4EiC#fOC`eMWNCf|~`%;{R}c zH=w+lD8AsDzb)|dor0dMWz5%A=5vYzk=?u&gUW+0w^Fb7+LH~a{@?{?X^m=g2QeFUupinI1%S^`c^BLNHyO+{5+mtW~KhCPoh$tvkll* zBDB{C5D<>H8?}@6V1aXSm)-gkudSCt#vi%vQJ4Q<2puGA>3Hjwz@B$Dv#Dd8jd{r1cSnRuER=G=Kut6_tNrjjjfM0STaq{)`4f%J!aS+A`A|`R*Eko0J|Zr+=3bTJ|kp5fuY_by$TW6N*trkg%g*}z;)?_lgFSLeG4T!cJUkEmI&6) z_m-Op36FU5FxaBDp9?(w{%p?UmG!7lNy+s&x=6n2{Fd3@i)X9rN9StORSv;kUP$2A z2cE81{f=8pOG~UiIOGkk&`W5zxxVn(N#HxYF4vs)kNj2dEHQoQzp$|yES1s>WUkNf zgR8nG)y|;>uo!4cAImuVV3?~86zPi|Z%w8R@xOl7+&cV>jV&CK4I22^pSID2_uO_x zSsy;^sEZal&sT@UER@(SJc49gqS__l@Kg(DW~)!3%32?$RfvKE6rvdgMgSS6gQ;@M zue-spBmy+>c8kBUkp1ut)>ogIDmSA4#57DC!yDRki02~|y(%#LcXD8C+OH}d-|&qg z+#7+p5)e>jNGEbY5sEPC54pJUXdu&`IOis_yvyJ@7@Y0oD3YQ2;{EZP-=2s?<_%jB z@l5XBbIxh7i0geLEgfLHF+vMUxL!cus;uXc)e5v-md0(t_W*b=%31Qln&b;OIt}g` z2>BjWIIUcNp++ecV6{)7X9u|2Lp;1!AXYvY6>x-$&ww}wy$GcGQrtx`d&80BV;V#U+_|Z{z64L2M}i)8%8MU0^I;;7257h;{!lV9My?y8vb_6M z`Y3$Z;|1LEl|KMP89Y5`72j0?6O{~W}5!p(aIhc1v8_u>szzOrS=s>RA+Sc{} z5G5FxS7|jzB_NO(5P%8UI50e^`1uoxideul75F3jq!eJh4HKBp?{Z(yS==KI z8-bl2GQ|7=GJaRY%)D;bCudC%Pa~nJ2*x z0U5lfuALkYA1cvmmeD|)?k7AsTSa^kB$x`4Qg?Mc1#NQ@L2?}3?E_3vxALDMNu0+F zY^rfs5nI(yh}HOODdYwmPFI0~if96YwCg(guSAS9O=PeB7=)dL~|F5~l|No5>{*RH+ z|6?HX|Ni~ICocon3Pwc#_wWDx=>Ff0fc_sB^1r_5|N6zGGdH)r`RGlky=TM_gSaJL M%f2cQdHebQ0FDv&rT_o{ diff --git a/DiscordGo/docs/index.md b/DiscordGo/docs/index.md deleted file mode 100644 index 9b433daf8418695ed1845e643c34cc0c041ad3dd..0000000000000000000000000000000000000000 --- a/DiscordGo/docs/index.md +++ /dev/null @@ -1,33 +0,0 @@ -## DiscordGo -
- - -[Go](https://golang.org/) (golang) interface for the [Discord](https://discordapp.com/) -chat service. Provides both low-level direct bindings to the -Discord API and helper functions that allow you to make custom clients and chat -bot applications easily. - -[Discord](https://discordapp.com/) is an all-in-one voice and text chat for -gamers that's free, secure, and works on both your desktop and phone. - -### Why DiscordGo? -* High Performance -* Minimal Memory & CPU Load -* Low-level bindings to Discord REST API Endpoints -* Support for the data websocket interface -* Multi-Server voice connections (send and receive) -* State tracking and caching - -### Learn More -* Check out the [Getting Started](GettingStarted) section -* Read the reference docs on [Godoc](https://godoc.org/github.com/bwmarrin/discordgo) or [GoWalker](https://gowalker.org/github.com/bwmarrin/discordgo) -* Try the [examples](https://github.com/bwmarrin/discordgo/tree/master/examples) -* Explore [Awesome DiscordGo](https://github.com/bwmarrin/discordgo/wiki/Awesome-DiscordGo) - -### Join Us! -Both of the below links take you to chat channels where you can get more -information and support for DiscordGo. There's also a chance to make some -friends :) - -* Join the [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) chat server dedicated to Go programming. -* Join the [Discord API](https://discord.gg/0SBTUU1wZTWT6sqd) chat server dedicated to the Discord API. diff --git a/DiscordGo/endpoints.go b/DiscordGo/endpoints.go deleted file mode 100644 index b10f95890b490c4e29b7eea872d9c8ef0e83b6e3..0000000000000000000000000000000000000000 --- a/DiscordGo/endpoints.go +++ /dev/null @@ -1,135 +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 - -// APIVersion is the Discord API version used for the REST and Websocket API. -var APIVersion = "6" - -// Known Discord API Endpoints. -var ( - EndpointStatus = "https://status.discordapp.com/api/v2/" - EndpointSm = EndpointStatus + "scheduled-maintenances/" - EndpointSmActive = EndpointSm + "active.json" - EndpointSmUpcoming = EndpointSm + "upcoming.json" - - EndpointDiscord = "https://discordapp.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/" - - 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" } - 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 } - EndpointGuildInivtes = func(gID string) string { return EndpointGuilds + gID + "/invites" } - 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" } - EndpointGuildEmbed = func(gID string) string { return EndpointGuilds + gID + "/embed" } - EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } - EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } - EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" } - EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" } - - 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 } - - 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" } - - 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 EndpointAPI + "emojis/" + eID + ".png" } - - EndpointOauth2 = EndpointAPI + "oauth2/" - EndpointApplications = EndpointOauth2 + "applications" - EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID } - EndpointApplicationsBot = func(aID string) string { return EndpointApplications + "/" + aID + "/bot" } -) diff --git a/DiscordGo/event.go b/DiscordGo/event.go deleted file mode 100644 index 3a03f46dd6860a31883bd8e9e7a5764e04bbd6d4..0000000000000000000000000000000000000000 --- a/DiscordGo/event.go +++ /dev/null @@ -1,238 +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 recievers 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. -// events.go contains all the Discord WSAPI events that can be fired. -// eg: -// Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { -// }) -// -// or: -// Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) { -// }) -// 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/DiscordGo/eventhandlers.go b/DiscordGo/eventhandlers.go deleted file mode 100644 index 5cc157de2c4a109d917d78ab5b5b806d6580510c..0000000000000000000000000000000000000000 --- a/DiscordGo/eventhandlers.go +++ /dev/null @@ -1,1030 +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" -) - -// 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) - } -} - -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) - } - - 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)) -} diff --git a/DiscordGo/events.go b/DiscordGo/events.go deleted file mode 100644 index c78fbdd2c18162ff0f5fd02aa03df780e31a2920..0000000000000000000000000000000000000000 --- a/DiscordGo/events.go +++ /dev/null @@ -1,253 +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 sythetic event and is not dispatched by Discord. -type Connect struct{} - -// Disconnect is the data for a Disconnect event. -// This is a sythetic event and is not dispatched by Discord. -type Disconnect struct{} - -// RateLimit is the data for a RateLimit event. -// This is a sythetic 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"` -} - -// 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"` -} - -// 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 -} - -// MessageDelete is the data for a MessageDelete event. -type MessageDelete struct { - *Message -} - -// 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"` - Roles []string `json:"roles"` -} - -// 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"` - 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 -} - -// MessageDeleteBulk is the data for a MessageDeleteBulk event -type MessageDeleteBulk struct { - Messages []string `json:"ids"` - ChannelID string `json:"channel_id"` -} diff --git a/DiscordGo/examples/README.md b/DiscordGo/examples/README.md deleted file mode 100644 index df7975a193eb9891c09a4d45ddf133f8c67f41d3..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/README.md +++ /dev/null @@ -1,11 +0,0 @@ - - -# DiscordGo Examples - -These examples demonstrate how to utilize DiscordGo. - -Please explore the individual folders and give them a try! - -**Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) -Discord chat channel for support.** - diff --git a/DiscordGo/examples/airhorn/README.md b/DiscordGo/examples/airhorn/README.md deleted file mode 100644 index 6d00ae3b05cff982cec335606beaa210e3111e4d..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/airhorn/README.md +++ /dev/null @@ -1,51 +0,0 @@ - - -## DiscordGo Airhorn Example - -This example demonstrates how to utilize DiscordGo to listen for an !airhorn -command in a channel and then play a sound to that user's current voice channel. - -**Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) -Discord chat channel for support.** - -### Build - -This assumes you already have a working Go environment setup and that -DiscordGo is correctly installed on your system. - -From within the airhorn example folder, run the below command to compile the -example. - -```sh -go build -``` - -### Usage - -``` -Usage of ./airhorn: - -t string - Bot Token -``` - -The below example shows how to start the bot from the airhorn example folder. - -```sh -./airhorn -t YOUR_BOT_TOKEN -``` - -### Creating sounds - -Airhorn bot uses [DCA](https://github.com/bwmarrin/dca) files, which are -pre-computed files that are easy to send to Discord. - -If you would like to create your own DCA files, please use: -* [dca-rs](https://github.com/nstafie/dca-rs) - -See the below example of creating a DCA file from a WAV file. This also works -with MP3, FLAC, and many other file formats. Of course, you will need to -[install](https://github.com/nstafie/dca-rs#installation) dca-rs first :) - -```sh -./dca-rs -i --raw > -``` diff --git a/DiscordGo/examples/airhorn/airhorn.dca b/DiscordGo/examples/airhorn/airhorn.dca deleted file mode 100644 index 39e71679b6929f719c34c781cc18d4af7b6035a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16300 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 V(c0;3Bu+nw=7h&$c{o0m{{w1h!Dave diff --git a/DiscordGo/examples/airhorn/main.go b/DiscordGo/examples/airhorn/main.go deleted file mode 100644 index 21ceb76b66706e936ecdc7ad49bfbca5f95a999a..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/airhorn/main.go +++ /dev/null @@ -1,211 +0,0 @@ -package main - -import ( - "encoding/binary" - "flag" - "fmt" - "io" - "os" - "os/signal" - "strings" - "syscall" - "time" - - "github.com/bwmarrin/discordgo" -) - -func init() { - flag.StringVar(&token, "t", "", "Bot Token") - flag.Parse() -} - -var token string -var buffer = make([][]byte, 0) - -func main() { - - if token == "" { - fmt.Println("No token provided. Please run: airhorn -t ") - return - } - - // Load the sound file. - err := loadSound() - if err != nil { - fmt.Println("Error loading sound: ", err) - fmt.Println("Please copy $GOPATH/src/github.com/bwmarrin/examples/airhorn/airhorn.dca to this directory.") - return - } - - // Create a new Discord session using the provided bot token. - dg, err := discordgo.New("Bot " + token) - if err != nil { - fmt.Println("Error creating Discord session: ", err) - return - } - - // Register ready as a callback for the ready events. - dg.AddHandler(ready) - - // Register messageCreate as a callback for the messageCreate events. - dg.AddHandler(messageCreate) - - // Register guildCreate as a callback for the guildCreate events. - dg.AddHandler(guildCreate) - - // Open the websocket and begin listening. - err = dg.Open() - if err != nil { - fmt.Println("Error opening Discord session: ", err) - } - - // Wait here until CTRL-C or other term signal is received. - fmt.Println("Airhorn is now running. Press CTRL-C to exit.") - sc := make(chan os.Signal, 1) - signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) - <-sc - - // Cleanly close down the Discord session. - dg.Close() -} - -// This function will be called (due to AddHandler above) when the bot receives -// the "ready" event from Discord. -func ready(s *discordgo.Session, event *discordgo.Ready) { - - // Set the playing status. - s.UpdateStatus(0, "!airhorn") -} - -// This function will be called (due to AddHandler above) every time a new -// message is created on any channel that the autenticated bot has access to. -func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { - - // Ignore all messages created by the bot itself - // This isn't required in this specific example but it's a good practice. - if m.Author.ID == s.State.User.ID { - return - } - - // check if the message is "!airhorn" - if strings.HasPrefix(m.Content, "!airhorn") { - - // Find the channel that the message came from. - c, err := s.State.Channel(m.ChannelID) - if err != nil { - // Could not find channel. - return - } - - // Find the guild for that channel. - g, err := s.State.Guild(c.GuildID) - if err != nil { - // Could not find guild. - return - } - - // Look for the message sender in that guild's current voice states. - for _, vs := range g.VoiceStates { - if vs.UserID == m.Author.ID { - err = playSound(s, g.ID, vs.ChannelID) - if err != nil { - fmt.Println("Error playing sound:", err) - } - - return - } - } - } -} - -// This function will be called (due to AddHandler above) every time a new -// guild is joined. -func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) { - - if event.Guild.Unavailable { - return - } - - for _, channel := range event.Guild.Channels { - if channel.ID == event.Guild.ID { - _, _ = s.ChannelMessageSend(channel.ID, "Airhorn is ready! Type !airhorn while in a voice channel to play a sound.") - return - } - } -} - -// loadSound attempts to load an encoded sound file from disk. -func loadSound() error { - - file, err := os.Open("airhorn.dca") - if err != nil { - fmt.Println("Error opening dca file :", err) - return err - } - - var opuslen int16 - - for { - // Read opus frame length from dca file. - err = binary.Read(file, binary.LittleEndian, &opuslen) - - // If this is the end of the file, just return. - if err == io.EOF || err == io.ErrUnexpectedEOF { - err := file.Close() - if err != nil { - return err - } - return nil - } - - if err != nil { - fmt.Println("Error reading from dca file :", err) - return err - } - - // Read encoded pcm from dca file. - InBuf := make([]byte, opuslen) - err = binary.Read(file, binary.LittleEndian, &InBuf) - - // Should not be any end of file errors - if err != nil { - fmt.Println("Error reading from dca file :", err) - return err - } - - // Append encoded pcm data to the buffer. - buffer = append(buffer, InBuf) - } -} - -// playSound plays the current buffer to the provided channel. -func playSound(s *discordgo.Session, guildID, channelID string) (err error) { - - // Join the provided voice channel. - vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true) - if err != nil { - return err - } - - // Sleep for a specified amount of time before playing the sound - time.Sleep(250 * time.Millisecond) - - // Start speaking. - vc.Speaking(true) - - // Send the buffer data. - for _, buff := range buffer { - vc.OpusSend <- buff - } - - // Stop speaking - vc.Speaking(false) - - // Sleep for a specificed amount of time before ending. - time.Sleep(250 * time.Millisecond) - - // Disconnect from the provided voice channel. - vc.Disconnect() - - return nil -} diff --git a/DiscordGo/examples/api_basic/api_basic.go b/DiscordGo/examples/api_basic/api_basic.go deleted file mode 100644 index 733cece3d80f1758018f5e3d1f9c20937aea58e1..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/api_basic/api_basic.go +++ /dev/null @@ -1,56 +0,0 @@ -// This file provides a basic "quick start" example of using the Discordgo -// package to connect to Discord using the low level API functions. -package main - -import ( - "fmt" - "os" - "time" - - "github.com/bwmarrin/discordgo" -) - -func main() { - - var err error - - // Check for Username and Password CLI arguments. - if len(os.Args) != 3 { - fmt.Println("You must provide username and password as arguments. See below example.") - fmt.Println(os.Args[0], " [username] [password]") - return - } - - // Create a new Discord Session interface and set a handler for the - // OnMessageCreate event that happens for every new message on any channel - dg := discordgo.Session{} - - // Register messageCreate as a callback for the messageCreate events. - dg.AddHandler(messageCreate) - - // Login to the Discord server and store the authentication token - err = dg.Login(os.Args[1], os.Args[2]) - if err != nil { - fmt.Println(err) - return - } - - // Open websocket connection - err = dg.Open() - if err != nil { - fmt.Println(err) - } - - // Simple way to keep program running until any key press. - var input string - fmt.Scanln(&input) - return -} - -// This function will be called (due to AddHandler above) every time a new -// message is created on any channel that the autenticated user has access to. -func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { - - // Print message to stdout. - fmt.Printf("%20s %20s %20s > %s\n", m.ChannelID, time.Now().Format(time.Stamp), m.Author.Username, m.Content) -} diff --git a/DiscordGo/examples/appmaker/README.md b/DiscordGo/examples/appmaker/README.md deleted file mode 100644 index e0cc29a66e9d6dde6e6b13212a36201c1ffa9727..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/appmaker/README.md +++ /dev/null @@ -1,57 +0,0 @@ - - -## DiscordGo AppMaker Example - -This example demonstrates how to utilize DiscordGo to create, view, and delete -Bot Applications on your account. - -These tasks are normally accomplished from the -[Discord Developers](https://discordapp.com/developers/applications/me) site. - -**Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) -Discord chat channel for support.** - -### Build - -This assumes you already have a working Go environment setup and that -DiscordGo is correctly installed on your system. - -From within the appmaker example folder, run the below command to compile the -example. - -```sh -go build -``` - -### Usage - -This example only uses authentication tokens for authentication. While -user email/password is supported by DiscordGo, it is not recommended. - -``` -./appmaker --help -Usage of ./appmaker: - -d string - Application ID to delete - -l List Applications Only - -n string - Name to give App/Bot - -t string - Owner Account Token -``` - -* Account Token is required. The account will be the "owner" of any bot -applications created. - -* If you provide the **-l** flag than appmaker will only display a list of -applications on the provided account. - -* If you provide a **-d** flag with a valid application ID then that application -will be deleted. - -Below example will create a new Bot Application under the given account. -The Bot will be named **DiscordGoRocks** - -```sh -./appmaker -t YOUR_USER_TOKEN -n DiscordGoRocks -``` diff --git a/DiscordGo/examples/appmaker/main.go b/DiscordGo/examples/appmaker/main.go deleted file mode 100644 index 286fe1694c0a19700771e4057a2889b52c9ce067..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/appmaker/main.go +++ /dev/null @@ -1,103 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "os" - - "github.com/bwmarrin/discordgo" -) - -// Variables used for command line options -var ( - Token string - Name string - DeleteID string - ListOnly bool -) - -func init() { - - flag.StringVar(&Token, "t", "", "Owner Account Token") - flag.StringVar(&Name, "n", "", "Name to give App/Bot") - flag.StringVar(&DeleteID, "d", "", "Application ID to delete") - flag.BoolVar(&ListOnly, "l", false, "List Applications Only") - flag.Parse() - - if Token == "" { - flag.Usage() - os.Exit(1) - } -} - -func main() { - - var err error - - // Create a new Discord session using the provided login information. - dg, err := discordgo.New(Token) - if err != nil { - fmt.Println("error creating Discord session,", err) - return - } - - // If -l set, only display a list of existing applications - // for the given account. - if ListOnly { - - aps, err := dg.Applications() - if err != nil { - fmt.Println("error fetching applications,", err) - return - } - - for _, v := range aps { - fmt.Println("-----------------------------------------------------") - b, _ := json.MarshalIndent(v, "", " ") - fmt.Println(string(b)) - } - return - } - - // if -d set, delete the given Application - if DeleteID != "" { - err = dg.ApplicationDelete(DeleteID) - if err != nil { - fmt.Println("error deleting application,", err) - } - return - } - - if Name == "" { - flag.Usage() - os.Exit(1) - } - - // Create a new application. - ap := &discordgo.Application{} - ap.Name = Name - ap, err = dg.ApplicationCreate(ap) - if err != nil { - fmt.Println("error creating new applicaiton,", err) - return - } - - fmt.Printf("Application created successfully:\n") - b, _ := json.MarshalIndent(ap, "", " ") - fmt.Println(string(b)) - - // Create the bot account under the application we just created - bot, err := dg.ApplicationBotCreate(ap.ID) - if err != nil { - fmt.Println("error creating bot account,", err) - return - } - - fmt.Printf("Bot account created successfully.\n") - b, _ = json.MarshalIndent(bot, "", " ") - fmt.Println(string(b)) - - fmt.Println("Please save the above posted info in a secure place.") - fmt.Println("You will need that information to login with your bot account.") -} diff --git a/DiscordGo/examples/avatar/README.md b/DiscordGo/examples/avatar/README.md deleted file mode 100644 index 582a35fe24ec45af2b2f473d5ef078cc640c0923..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/avatar/README.md +++ /dev/null @@ -1,49 +0,0 @@ - - -## DiscordGo Avatar Example - -This example demonstrates how to utilize DiscordGo to change the avatar for -a Discord account. This example works both with a local file or the URL of -an image. - -**Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) -Discord chat channel for support.** - -### Build - -This assumes you already have a working Go environment setup and that -DiscordGo is correctly installed on your system. - -From within the avatar example folder, run the below command to compile the -example. - -```sh -go build -``` - -### Usage - -This example uses bot tokens for authentication only. While email/password is -supported by DiscordGo, it is not recommended to use them. - -``` -./avatar --help -Usage of ./avatar: - -f string - Avatar File Name - -t string - Bot Token - -u string - URL to the avatar image -``` - -The below example shows how to set your Avatar from a local file. - -```sh -./avatar -t TOKEN -f avatar.png -``` -The below example shows how to set your Avatar from a URL. - -```sh -./avatar -t TOKEN -u http://bwmarrin.github.io/discordgo/img/discordgo.png -``` diff --git a/DiscordGo/examples/avatar/main.go b/DiscordGo/examples/avatar/main.go deleted file mode 100644 index e0a9c8802017f35b2c761d2f84a487f9ca27ea05..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/avatar/main.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "encoding/base64" - "flag" - "fmt" - "io/ioutil" - "net/http" - "os" - - "github.com/bwmarrin/discordgo" -) - -// Variables used for command line parameters -var ( - Token string - AvatarFile string - AvatarURL string -) - -func init() { - - flag.StringVar(&Token, "t", "", "Bot Token") - flag.StringVar(&AvatarFile, "f", "", "Avatar File Name") - flag.StringVar(&AvatarURL, "u", "", "URL to the avatar image") - flag.Parse() - - if Token == "" || (AvatarFile == "" && AvatarURL == "") { - flag.Usage() - os.Exit(1) - } -} - -func main() { - - // Create a new Discord session using the provided login information. - dg, err := discordgo.New("Bot " + Token) - if err != nil { - fmt.Println("error creating Discord session,", err) - return - } - - // Declare these here so they can be used in the below two if blocks and - // still carry over to the end of this function. - var base64img string - var contentType string - - // If we're using a URL link for the Avatar - if AvatarURL != "" { - - resp, err := http.Get(AvatarURL) - if err != nil { - fmt.Println("Error retrieving the file, ", err) - return - } - - defer func() { - _ = resp.Body.Close() - }() - - img, err := ioutil.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error reading the response, ", err) - return - } - - contentType = http.DetectContentType(img) - base64img = base64.StdEncoding.EncodeToString(img) - } - - // If we're using a local file for the Avatar - if AvatarFile != "" { - img, err := ioutil.ReadFile(AvatarFile) - if err != nil { - fmt.Println(err) - } - - contentType = http.DetectContentType(img) - base64img = base64.StdEncoding.EncodeToString(img) - } - - // Now lets format our base64 image into the proper format Discord wants - // and then call UserUpdate to set it as our user's Avatar. - avatar := fmt.Sprintf("data:%s;base64,%s", contentType, base64img) - _, err = dg.UserUpdate("", "", "", avatar, "") - if err != nil { - fmt.Println(err) - } -} diff --git a/DiscordGo/examples/mytoken/README.md b/DiscordGo/examples/mytoken/README.md deleted file mode 100644 index 6db8bfbeea9f57bdce14c55275f4c7aec7afe9c1..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/mytoken/README.md +++ /dev/null @@ -1,47 +0,0 @@ - - -## DiscordGo MyToken Example - -This example demonstrates how to utilize DiscordGo to login with an email and -password then to print out the Authentication Token for that user's account. - -Everytime this application is run a new authentication token is generated -for your account. Logging you in via email and password then creating a new -token is a cpu/mem expensive task for Discord. Because of that, it is highly -recommended to avoid doing this very often. Please only use this once to get a -token for your use and then always just your token. - -**Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) -Discord chat channel for support.** - -### Build - -This assumes you already have a working Go environment setup and that -DiscordGo is correctly installed on your system. - -From within the mytoken example folder, run the below command to compile the -example. - -```sh -go build -``` - -### Usage - -You must authenticate using both Email and Password for an account. - -``` -./mytoken --help -Usage of ./mytoken: - -e string - Account Email - -p string - Account Password -``` - -The below example shows how to start the program using an Email and Password for -authentication. - -```sh -./mytoken -e youremail@here.com -p MySecretPassword -``` diff --git a/DiscordGo/examples/mytoken/main.go b/DiscordGo/examples/mytoken/main.go deleted file mode 100644 index 9375eadc9e12d18aa710788ab27721d6d0c06db9..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/mytoken/main.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - - "github.com/bwmarrin/discordgo" -) - -// Variables used for command line parameters -var ( - Email string - Password string -) - -func init() { - - flag.StringVar(&Email, "e", "", "Account Email") - flag.StringVar(&Password, "p", "", "Account Password") - flag.Parse() - - if Email == "" || Password == "" { - flag.Usage() - os.Exit(1) - } -} - -func main() { - - // Create a new Discord session using the provided login information. - dg, err := discordgo.New(Email, Password) - if err != nil { - fmt.Println("error creating Discord session,", err) - return - } - - // Print out your token. - fmt.Printf("Your Authentication Token is:\n\n%s\n", dg.Token) -} diff --git a/DiscordGo/examples/new_basic/new_basic.go b/DiscordGo/examples/new_basic/new_basic.go deleted file mode 100644 index 6d524acdc96a8ad23d4f4179b480bb1669e9cf1d..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/new_basic/new_basic.go +++ /dev/null @@ -1,49 +0,0 @@ -// This file provides a basic "quick start" example of using the Discordgo -// package to connect to Discord using the New() helper function. -package main - -import ( - "fmt" - "os" - "time" - - "github.com/bwmarrin/discordgo" -) - -func main() { - - // Check for Username and Password CLI arguments. - if len(os.Args) != 3 { - fmt.Println("You must provide username and password as arguments. See below example.") - fmt.Println(os.Args[0], " [username] [password]") - return - } - - // Call the helper function New() passing username and password command - // line arguments. This returns a new Discord session, authenticates, - // connects to the Discord data websocket, and listens for events. - dg, err := discordgo.New(os.Args[1], os.Args[2]) - if err != nil { - fmt.Println(err) - return - } - - // Register messageCreate as a callback for the messageCreate events. - dg.AddHandler(messageCreate) - - // Open the websocket and begin listening. - dg.Open() - - // Simple way to keep program running until any key press. - var input string - fmt.Scanln(&input) - return -} - -// This function will be called (due to AddHandler above) every time a new -// message is created on any channel that the autenticated user has access to. -func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { - - // Print message to stdout. - fmt.Printf("%20s %20s %20s > %s\n", m.ChannelID, time.Now().Format(time.Stamp), m.Author.Username, m.Content) -} diff --git a/DiscordGo/examples/pingpong/README.md b/DiscordGo/examples/pingpong/README.md deleted file mode 100644 index 489d384d1a8886a522c8d87133930177a2de628a..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/pingpong/README.md +++ /dev/null @@ -1,42 +0,0 @@ - - -## DiscordGo Ping Pong Example - -This example demonstrates how to utilize DiscordGo to create a Ping Pong Bot. - -This Bot will respond to "ping" with "Pong!" and "pong" with "Ping!". - -**Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) -Discord chat channel for support.** - -### Build - -This assumes you already have a working Go environment setup and that -DiscordGo is correctly installed on your system. - - -From within the pingpong example folder, run the below command to compile the -example. - -```sh -go build -``` - -### Usage - -This example uses bot tokens for authentication only. While user/password is -supported by DiscordGo, it is not recommended for bots. - -``` -./pingpong --help -Usage of ./pingpong: - -t string - Bot Token -``` - -The below example shows how to start the bot - -```sh -./pingpong -t YOUR_BOT_TOKEN -Bot is now running. Press CTRL-C to exit. -``` diff --git a/DiscordGo/examples/pingpong/main.go b/DiscordGo/examples/pingpong/main.go deleted file mode 100644 index 155e782f1dc6a73adcc825345d663ee132f451ec..0000000000000000000000000000000000000000 --- a/DiscordGo/examples/pingpong/main.go +++ /dev/null @@ -1,71 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "os" - "os/signal" - "syscall" - - "github.com/bwmarrin/discordgo" -) - -// Variables used for command line parameters -var ( - Token string -) - -func init() { - - flag.StringVar(&Token, "t", "", "Bot Token") - flag.Parse() -} - -func main() { - - // Create a new Discord session using the provided bot token. - dg, err := discordgo.New("Bot " + Token) - if err != nil { - fmt.Println("error creating Discord session,", err) - return - } - - // Register the messageCreate func as a callback for MessageCreate events. - dg.AddHandler(messageCreate) - - // Open a websocket connection to Discord and begin listening. - err = dg.Open() - if err != nil { - fmt.Println("error opening connection,", err) - return - } - - // Wait here until CTRL-C or other term signal is received. - fmt.Println("Bot is now running. Press CTRL-C to exit.") - sc := make(chan os.Signal, 1) - signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) - <-sc - - // Cleanly close down the Discord session. - dg.Close() -} - -// This function will be called (due to AddHandler above) every time a new -// message is created on any channel that the autenticated bot has access to. -func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { - - // Ignore all messages created by the bot itself - // This isn't required in this specific example but it's a good practice. - if m.Author.ID == s.State.User.ID { - return - } - // If the message is "ping" reply with "Pong!" - if m.Content == "ping" { - s.ChannelMessageSend(m.ChannelID, "Pong!") - } - - // If the message is "pong" reply with "Ping!" - if m.Content == "pong" { - s.ChannelMessageSend(m.ChannelID, "Ping!") - } -} diff --git a/DiscordGo/logging.go b/DiscordGo/logging.go deleted file mode 100644 index 70d78d601c4edbb42e7e90fff741ef673baed447..0000000000000000000000000000000000000000 --- a/DiscordGo/logging.go +++ /dev/null @@ -1,95 +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 returend 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 -) - -// msglog provides package wide logging consistancy 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 seperated list of values to pass -func msglog(msgL, caller int, format string, a ...interface{}) { - - 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/DiscordGo/message.go b/DiscordGo/message.go deleted file mode 100644 index 19345b9538eca7b6dff63220b06e415c5d6b3309..0000000000000000000000000000000000000000 --- a/DiscordGo/message.go +++ /dev/null @@ -1,252 +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 -type MessageType int - -// Block contains the valid known MessageType values -const ( - MessageTypeDefault MessageType = iota - MessageTypeRecipientAdd - MessageTypeRecipientRemove - MessageTypeCall - MessageTypeChannelNameChange - MessageTypeChannelIconChange - MessageTypeChannelPinnedMessage - MessageTypeGuildMemberJoin -) - -// A Message stores all data related to a specific Discord message. -type Message struct { - ID string `json:"id"` - ChannelID string `json:"channel_id"` - Content string `json:"content"` - Timestamp Timestamp `json:"timestamp"` - EditedTimestamp Timestamp `json:"edited_timestamp"` - MentionRoles []string `json:"mention_roles"` - Tts bool `json:"tts"` - MentionEveryone bool `json:"mention_everyone"` - Author *User `json:"author"` - Attachments []*MessageAttachment `json:"attachments"` - Embeds []*MessageEmbed `json:"embeds"` - Mentions []*User `json:"mentions"` - Reactions []*MessageReactions `json:"reactions"` - Type MessageType `json:"type"` -} - -// 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:"-"` - - // 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"` - - 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 -} - -// 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"` - ProxyURL string `json:"proxy_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 string `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"` -} - -// MessageReactions holds a reactions object for a message. -type MessageReactions struct { - Count int `json:"count"` - Me bool `json:"me"` - Emoji *Emoji `json:"emoji"` -} - -// 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/DiscordGo/message_test.go b/DiscordGo/message_test.go deleted file mode 100644 index fd2f133604d397449000bc5459a16240bed3cb18..0000000000000000000000000000000000000000 --- a/DiscordGo/message_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package discordgo - -import ( - "testing" -) - -func TestContentWithMoreMentionsReplaced(t *testing.T) { - s := &Session{StateEnabled: true, State: NewState()} - - user := &User{ - ID: "user", - Username: "User Name", - } - - s.StateEnabled = true - s.State.GuildAdd(&Guild{ID: "guild"}) - s.State.RoleAdd("guild", &Role{ - ID: "role", - Name: "Role Name", - Mentionable: true, - }) - s.State.MemberAdd(&Member{ - User: user, - Nick: "User Nick", - GuildID: "guild", - }) - s.State.ChannelAdd(&Channel{ - Name: "Channel Name", - GuildID: "guild", - ID: "channel", - }) - m := &Message{ - Content: "<&role> <@!user> <@user> <#channel>", - ChannelID: "channel", - MentionRoles: []string{"role"}, - Mentions: []*User{user}, - } - if result, _ := m.ContentWithMoreMentionsReplaced(s); result != "@Role Name @User Nick @User Name #Channel Name" { - t.Error(result) - } -} diff --git a/DiscordGo/mkdocs.yml b/DiscordGo/mkdocs.yml deleted file mode 100644 index 3ee8eb3785b79579dada2b31b85b761c1e007dbf..0000000000000000000000000000000000000000 --- a/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/DiscordGo/oauth2.go b/DiscordGo/oauth2.go deleted file mode 100644 index 108b32fe38ebecc862614f1785980ad2c997e701..0000000000000000000000000000000000000000 --- a/DiscordGo/oauth2.go +++ /dev/null @@ -1,126 +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 -// ------------------------------------------------------------------------------------------------ - -// 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"` -} - -// 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 -} - -// ------------------------------------------------------------------------------------------------ -// 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/DiscordGo/oauth2_test.go b/DiscordGo/oauth2_test.go deleted file mode 100644 index 30526eb20eacf6f856bc86a689fbfca7345d9510..0000000000000000000000000000000000000000 --- a/DiscordGo/oauth2_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package discordgo_test - -import ( - "log" - "os" - - "github.com/bwmarrin/discordgo" -) - -func ExampleApplication() { - - // Authentication Token pulled from environment variable DG_TOKEN - Token := os.Getenv("DG_TOKEN") - if Token == "" { - return - } - - // Create a new Discordgo session - dg, err := discordgo.New(Token) - if err != nil { - log.Println(err) - return - } - - // Create an new Application - ap := &discordgo.Application{} - ap.Name = "TestApp" - ap.Description = "TestDesc" - ap, err = dg.ApplicationCreate(ap) - log.Printf("ApplicationCreate: err: %+v, app: %+v\n", err, ap) - - // Get a specific Application by it's ID - ap, err = dg.Application(ap.ID) - log.Printf("Application: err: %+v, app: %+v\n", err, ap) - - // Update an existing Application with new values - ap.Description = "Whooooa" - ap, err = dg.ApplicationUpdate(ap.ID, ap) - log.Printf("ApplicationUpdate: err: %+v, app: %+v\n", err, ap) - - // create a new bot account for this application - bot, err := dg.ApplicationBotCreate(ap.ID) - log.Printf("BotCreate: err: %+v, bot: %+v\n", err, bot) - - // Get a list of all applications for the authenticated user - apps, err := dg.Applications() - log.Printf("Applications: err: %+v, apps : %+v\n", err, apps) - for k, v := range apps { - log.Printf("Applications: %d : %+v\n", k, v) - } - - // Delete the application we created. - err = dg.ApplicationDelete(ap.ID) - log.Printf("Delete: err: %+v\n", err) - - return -} diff --git a/DiscordGo/ratelimit.go b/DiscordGo/ratelimit.go deleted file mode 100644 index 223c0d04eaa0e109db562a2bcf18af11ecc98aa9..0000000000000000000000000000000000000000 --- a/DiscordGo/ratelimit.go +++ /dev/null @@ -1,183 +0,0 @@ -package discordgo - -import ( - "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 -} - -// LockBucket Locks until a request can be made -func (r *RateLimiter) LockBucket(bucketID string) *Bucket { - - b := r.getBucket(bucketID) - - b.Lock() - - // 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 < 1 && b.reset.After(time.Now()) { - time.Sleep(b.reset.Sub(time.Now())) - - } - - // Check for global ratelimits - sleepTo := time.Unix(0, atomic.LoadInt64(r.global)) - if now := time.Now(); now.Before(sleepTo) { - time.Sleep(sleepTo.Sub(now)) - } - - 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 -} - -// 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") - retryAfter := headers.Get("Retry-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 retryAfter != "" { - parsedAfter, err := strconv.ParseInt(retryAfter, 10, 64) - if err != nil { - return err - } - - resetAt := time.Now().Add(time.Duration(parsedAfter) * 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.ParseInt(reset, 10, 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 - delta := time.Unix(unix, 0).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/DiscordGo/ratelimit_test.go b/DiscordGo/ratelimit_test.go deleted file mode 100644 index db18211a5f390c72ea350ba70f5309ec26fe567b..0000000000000000000000000000000000000000 --- a/DiscordGo/ratelimit_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package discordgo - -import ( - "net/http" - "strconv" - "testing" - "time" -) - -// This test takes ~2 seconds to run -func TestRatelimitReset(t *testing.T) { - rl := NewRatelimiter() - - sendReq := func(endpoint string) { - bucket := rl.LockBucket(endpoint) - - headers := http.Header(make(map[string][]string)) - - headers.Set("X-RateLimit-Remaining", "0") - // Reset for approx 2 seconds from now - headers.Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Add(time.Second*2).Unix(), 10)) - headers.Set("Date", time.Now().Format(time.RFC850)) - - err := bucket.Release(headers) - if err != nil { - t.Errorf("Release returned error: %v", err) - } - } - - sent := time.Now() - sendReq("/guilds/99/channels") - sendReq("/guilds/55/channels") - sendReq("/guilds/66/channels") - - sendReq("/guilds/99/channels") - sendReq("/guilds/55/channels") - sendReq("/guilds/66/channels") - - // We hit the same endpoint 2 times, so we should only be ratelimited 2 second - // And always less than 4 seconds (unless you're on a stoneage computer or using swap or something...) - if time.Since(sent) >= time.Second && time.Since(sent) < time.Second*4 { - t.Log("OK", time.Since(sent)) - } else { - t.Error("Did not ratelimit correctly, got:", time.Since(sent)) - } -} - -// This test takes ~1 seconds to run -func TestRatelimitGlobal(t *testing.T) { - rl := NewRatelimiter() - - sendReq := func(endpoint string) { - bucket := rl.LockBucket(endpoint) - - headers := http.Header(make(map[string][]string)) - - headers.Set("X-RateLimit-Global", "1") - // Reset for approx 1 seconds from now - headers.Set("Retry-After", "1000") - - err := bucket.Release(headers) - if err != nil { - t.Errorf("Release returned error: %v", err) - } - } - - sent := time.Now() - - // This should trigger a global ratelimit - sendReq("/guilds/99/channels") - time.Sleep(time.Millisecond * 100) - - // This shouldn't go through in less than 1 second - sendReq("/guilds/55/channels") - - if time.Since(sent) >= time.Second && time.Since(sent) < time.Second*2 { - t.Log("OK", time.Since(sent)) - } else { - t.Error("Did not ratelimit correctly, got:", time.Since(sent)) - } -} - -func BenchmarkRatelimitSingleEndpoint(b *testing.B) { - rl := NewRatelimiter() - for i := 0; i < b.N; i++ { - sendBenchReq("/guilds/99/channels", rl) - } -} - -func BenchmarkRatelimitParallelMultiEndpoints(b *testing.B) { - rl := NewRatelimiter() - b.RunParallel(func(pb *testing.PB) { - i := 0 - for pb.Next() { - sendBenchReq("/guilds/"+strconv.Itoa(i)+"/channels", rl) - i++ - } - }) -} - -// Does not actually send requests, but locks the bucket and releases it with made-up headers -func sendBenchReq(endpoint string, rl *RateLimiter) { - bucket := rl.LockBucket(endpoint) - - headers := http.Header(make(map[string][]string)) - - headers.Set("X-RateLimit-Remaining", "10") - headers.Set("X-RateLimit-Reset", strconv.FormatInt(time.Now().Unix(), 10)) - headers.Set("Date", time.Now().Format(time.RFC850)) - - bucket.Release(headers) -} diff --git a/DiscordGo/restapi.go b/DiscordGo/restapi.go deleted file mode 100644 index 836e4a41c6d91cd3970a15bc1c2c223b35687d6a..0000000000000000000000000000000000000000 --- a/DiscordGo/restapi.go +++ /dev/null @@ -1,2041 +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") -) - -// 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] - } - - bucket := s.ratelimiter.LockBucket(bucketID) - - 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) - } - - req.Header.Set("Content-Type", contentType) - // TODO: Make a configurable static variable. - req.Header.Set("User-Agent", fmt.Sprintf("DiscordBot (https://github.com/bwmarrin/discordgo, v%s)", VERSION)) - - 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: - - // TODO check for 401 response, invalidate token if we get one. - - 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.request(method, urlStr, contentType, b, bucketID, 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.request(method, urlStr, contentType, b, bucketID, sequence) - - 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, fmt.Sprintf(`{"token": "%s"}`, 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 -} - -// 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 = fmt.Sprintf("%s?%s", 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 int, 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, member), nil -} - -// Calculates the permissions for a member. -// https://support.discordapp.com/hc/en-us/articles/206141927-How-is-the-permission-hierarchy-structured- -func memberPermissions(guild *Guild, channel *Channel, member *Member) (apermissions int) { - userID := member.User.ID - - 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 member.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 - } - } - - denies := 0 - allows := 0 - - // Member overwrites can override role overrides, so do two passes - for _, overwrite := range channel.PermissionOverwrites { - for _, roleID := range member.Roles { - if overwrite.Type == "role" && roleID == overwrite.ID { - denies |= overwrite.Deny - allows |= overwrite.Allow - break - } - } - } - - apermissions &= ^denies - apermissions |= allows - - for _, overwrite := range channel.PermissionOverwrites { - if overwrite.Type == "member" && 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) { - if s.StateEnabled { - // Attempt to grab the guild from State first. - st, err = s.State.Guild(guildID) - if err == nil { - return - } - } - - 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", EndpointGuilds, data, EndpointGuilds) - 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, 3] - if g.VerificationLevel != nil { - val := *g.VerificationLevel - if val < 0 || val > 3 { - 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 User 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) -} - -// 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 = fmt.Sprintf("%s?%s", 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 -} - -// 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, "")) - if err != nil { - return - } - - 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 null? -// 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, userID, channelID string) (err error) { - - data := struct { - ChannelID string `json:"channel_id"` - }{channelID} - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) - if err != nil { - return - } - - 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 -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 -} - -// 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 -} - -// 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 : Tpye of the channel (voice or text) -func (s *Session) GuildChannelCreate(guildID, name, ctype string) (st *Channel, err error) { - - data := struct { - Name string `json:"name"` - Type string `json:"type"` - }{name, ctype} - - body, err := s.RequestWithBucketID("POST", EndpointGuildChannels(guildID), data, EndpointGuildChannels(guildID)) - if err != nil { - return - } - - err = unmarshal(body, &st) - return -} - -// 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) { - - _, err = s.RequestWithBucketID("PATCH", EndpointGuildChannels(guildID), channels, 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, EndpointGuildInivtes(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") - } - - 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) + fmt.Sprintf("?days=%d", days) - body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildPrune(guildID)) - - 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 []*GuildIntegration, 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 -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Channels -// ------------------------------------------------------------------------------------------------ - -// Channel returns a Channel strucutre 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) (st *Channel, err error) { - - data := struct { - Name string `json:"name"` - }{name} - - 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 = fmt.Sprintf("%s?%s", 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, - }) -} - -// 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 funciton. -// 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, Temporary, -// and XkcdPass 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"` - XKCDPass string `json:"xkcdpass"` - }{i.MaxAge, i.MaxUses, i.Temporary, i.XkcdPass} - - 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, targetType string, allow, deny int) (err error) { - - data := struct { - ID string `json:"id"` - Type string `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 -} - -// ------------------------------------------------------------------------------------------------ -// Functions specific to Discord Invites -// ------------------------------------------------------------------------------------------------ - -// Invite returns an Invite structure of the given invite -// inviteID : The invite code (or maybe xkcdpass?) -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 -} - -// InviteDelete deletes an existing invite -// inviteID : the code (or maybe xkcdpass?) 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 (or maybe xkcdpass?) -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 string) (st *Role, err error) { - - data := struct { - Name string `json:"name,omitempty"` - Avatar string `json:"avatar,omitempty"` - }{name, avatar} - - 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 -func (s *Session) WebhookExecute(webhookID, token string, wait bool, data *WebhookParams) (err error) { - uri := EndpointWebhookToken(webhookID, token) - - if wait { - uri += "?wait=true" - } - - _, err = s.RequestWithBucketID("POST", uri, data, 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 { - - _, 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 { - - _, 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) -func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit int) (st []*User, err error) { - uri := EndpointMessageReactions(channelID, messageID, emojiID) - - v := url.Values{} - - 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, 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/DiscordGo/restapi_test.go b/DiscordGo/restapi_test.go deleted file mode 100644 index 7aa4e604d6fc57d6c5b1810bdfbb90243941ee7b..0000000000000000000000000000000000000000 --- a/DiscordGo/restapi_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package discordgo - -import ( - "testing" -) - -////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////// START OF TESTS - -// TestChannelMessageSend tests the ChannelMessageSend() function. This should not return an error. -func TestChannelMessageSend(t *testing.T) { - - if envChannel == "" { - t.Skip("Skipping, DG_CHANNEL not set.") - } - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - _, err := dg.ChannelMessageSend(envChannel, "Running REST API Tests!") - if err != nil { - t.Errorf("ChannelMessageSend returned error: %+v", err) - } -} - -/* -// removed for now, only works on BOT accounts now -func TestUserAvatar(t *testing.T) { - - if dg == nil { - t.Skip("Cannot TestUserAvatar, dg not set.") - } - - u, err := dg.User("@me") - if err != nil { - t.Error("error fetching @me user,", err) - } - - a, err := dg.UserAvatar(u.ID) - if err != nil { - if err.Error() == `HTTP 404 NOT FOUND, {"code": 0, "message": "404: Not Found"}` { - t.Skip("Skipped, @me doesn't have an Avatar") - } - t.Errorf(err.Error()) - } - - if a == nil { - t.Errorf("a == nil, should be image.Image") - } -} -*/ - -/* Running this causes an error due to 2/hour rate limit on username changes -func TestUserUpdate(t *testing.T) { - if dg == nil { - t.Skip("Cannot test logout, dg not set.") - } - - u, err := dg.User("@me") - if err != nil { - t.Errorf(err.Error()) - } - - s, err := dg.UserUpdate(envEmail, envPassword, "testname", u.Avatar, "") - if err != nil { - t.Error(err.Error()) - } - if s.Username != "testname" { - t.Error("Username != testname") - } - s, err = dg.UserUpdate(envEmail, envPassword, u.Username, u.Avatar, "") - if err != nil { - t.Error(err.Error()) - } - if s.Username != u.Username { - t.Error("Username != " + u.Username) - } -} -*/ - -//func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) { - -func TestUserChannelCreate(t *testing.T) { - if dg == nil { - t.Skip("Cannot TestUserChannelCreate, dg not set.") - } - - if envAdmin == "" { - t.Skip("Skipped, DG_ADMIN not set.") - } - - _, err := dg.UserChannelCreate(envAdmin) - if err != nil { - t.Errorf(err.Error()) - } - - // TODO make sure the channel was added -} - -func TestUserChannels(t *testing.T) { - if dg == nil { - t.Skip("Cannot TestUserChannels, dg not set.") - } - - _, err := dg.UserChannels() - if err != nil { - t.Errorf(err.Error()) - } -} - -func TestUserGuilds(t *testing.T) { - if dg == nil { - t.Skip("Cannot TestUserGuilds, dg not set.") - } - - _, err := dg.UserGuilds(10, "", "") - if err != nil { - t.Errorf(err.Error()) - } -} - -func TestUserSettings(t *testing.T) { - if dg == nil { - t.Skip("Cannot TestUserSettings, dg not set.") - } - - _, err := dg.UserSettings() - if err != nil { - t.Errorf(err.Error()) - } -} - -func TestUserUpdateStatus(t *testing.T) { - if dg == nil { - t.Skip("Cannot TestUserSettings, dg not set.") - } - - _, err := dg.UserUpdateStatus(StatusDoNotDisturb) - if err != nil { - t.Errorf(err.Error()) - } -} - -// TestLogout tests the Logout() function. This should not return an error. -func TestLogout(t *testing.T) { - - if dg == nil { - t.Skip("Cannot TestLogout, dg not set.") - } - - err := dg.Logout() - if err != nil { - t.Errorf("Logout() returned error: %+v", err) - } -} - -func TestGateway(t *testing.T) { - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - _, err := dg.Gateway() - if err != nil { - t.Errorf("Gateway() returned error: %+v", err) - } -} - -func TestGatewayBot(t *testing.T) { - - if dgBot == nil { - t.Skip("Skipping, dgBot not set.") - } - _, err := dgBot.GatewayBot() - if err != nil { - t.Errorf("GatewayBot() returned error: %+v", err) - } -} - -func TestVoiceICE(t *testing.T) { - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - _, err := dg.VoiceICE() - if err != nil { - t.Errorf("VoiceICE() returned error: %+v", err) - } -} - -func TestVoiceRegions(t *testing.T) { - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - _, err := dg.VoiceRegions() - if err != nil { - t.Errorf("VoiceRegions() returned error: %+v", err) - } -} -func TestGuildRoles(t *testing.T) { - - if envGuild == "" { - t.Skip("Skipping, DG_GUILD not set.") - } - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - _, err := dg.GuildRoles(envGuild) - if err != nil { - t.Errorf("GuildRoles(envGuild) returned error: %+v", err) - } - -} - -func TestGuildMemberNickname(t *testing.T) { - - if envGuild == "" { - t.Skip("Skipping, DG_GUILD not set.") - } - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - err := dg.GuildMemberNickname(envGuild, "@me/nick", "testnickname") - if err != nil { - t.Errorf("GuildNickname returned error: %+v", err) - } -} - -// TestChannelMessageSend2 tests the ChannelMessageSend() function. This should not return an error. -func TestChannelMessageSend2(t *testing.T) { - - if envChannel == "" { - t.Skip("Skipping, DG_CHANNEL not set.") - } - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - _, err := dg.ChannelMessageSend(envChannel, "All done running REST API Tests!") - if err != nil { - t.Errorf("ChannelMessageSend returned error: %+v", err) - } -} - -// TestGuildPruneCount tests GuildPruneCount() function. This should not return an error. -func TestGuildPruneCount(t *testing.T) { - - if envGuild == "" { - t.Skip("Skipping, DG_GUILD not set.") - } - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - _, err := dg.GuildPruneCount(envGuild, 1) - if err != nil { - t.Errorf("GuildPruneCount returned error: %+v", err) - } -} - -/* -// TestGuildPrune tests GuildPrune() function. This should not return an error. -func TestGuildPrune(t *testing.T) { - - if envGuild == "" { - t.Skip("Skipping, DG_GUILD not set.") - } - - if dg == nil { - t.Skip("Skipping, dg not set.") - } - - _, err := dg.GuildPrune(envGuild, 1) - if err != nil { - t.Errorf("GuildPrune returned error: %+v", err) - } -} -*/ diff --git a/DiscordGo/state.go b/DiscordGo/state.go deleted file mode 100644 index 35a8e7578b1b1421b230d2166c460a6437f204a3..0000000000000000000000000000000000000000 --- a/DiscordGo/state.go +++ /dev/null @@ -1,983 +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") - -// 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 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.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].Game = presence.Game - guild.Presences[i].Roles = presence.Roles - if presence.Status != "" { - guild.Presences[i].Status = presence.Status - } - if presence.Nick != "" { - guild.Presences[i].Nick = presence.Nick - } - - //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 { - *m = *member // Update the actual data, which will also update the member pointer in the slice - } - - 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 an 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 -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 -} - -// 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: - if s.TrackMembers { - err = s.MemberAdd(t.Member) - } - case *GuildMemberUpdate: - if s.TrackMembers { - err = s.MemberAdd(t.Member) - } - case *GuildMemberRemove: - if s.TrackMembers { - err = s.MemberRemove(t.Member) - } - 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 { - err = s.MessageAdd(t.Message) - } - case *MessageDelete: - if s.MaxMessageCount != 0 { - 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 { - 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, - Nick: t.Nick, - User: t.User, - Roles: t.Roles, - } - - } else { - - if t.Nick != "" { - m.Nick = t.Nick - } - - if t.User.Username != "" { - m.User.Username = t.User.Username - } - - // PresenceUpdates always contain a list of roles, so there's no need to check for an empty list here - m.Roles = t.Roles - - } - - 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 int, 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 - } - - if userID == guild.OwnerID { - apermissions = PermissionAll - return - } - - member, err := s.Member(guild.ID, userID) - if err != nil { - return - } - - return memberPermissions(guild, channel, member), 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 - } - - roles := Roles(guild.Roles) - sort.Sort(roles) - - for _, role := range roles { - for _, roleID := range member.Roles { - if role.ID == roleID { - if role.Color != 0 { - return role.Color - } - } - } - } - - return 0 -} diff --git a/DiscordGo/structs.go b/DiscordGo/structs.go deleted file mode 100644 index c3e3956673895b153e02ea252bba38c27b5f8271..0000000000000000000000000000000000000000 --- a/DiscordGo/structs.go +++ /dev/null @@ -1,660 +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" - "net/http" - "strconv" - "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 - 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 - - // 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 - - // Stores the last HeartbeatAck that was recieved (in UTC) - LastHeartbeatAck time.Time - - // 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{} - - // used to deal with rate limits - ratelimiter *RateLimiter - - // 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 -} - -// 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"` - XkcdPass string `json:"xkcdpass"` - Revoked bool `json:"revoked"` - Temporary bool `json:"temporary"` -} - -// ChannelType is the type of a Channel -type ChannelType int - -// Block contains known ChannelType values -const ( - ChannelTypeGuildText ChannelType = iota - ChannelTypeDM - ChannelTypeGuildVoice - ChannelTypeGroupDM - ChannelTypeGuildCategory -) - -// A Channel holds all data related to an individual Discord channel. -type Channel struct { - ID string `json:"id"` - GuildID string `json:"guild_id"` - Name string `json:"name"` - Topic string `json:"topic"` - Type ChannelType `json:"type"` - LastMessageID string `json:"last_message_id"` - NSFW bool `json:"nsfw"` - Position int `json:"position"` - Bitrate int `json:"bitrate"` - Recipients []*User `json:"recipient"` - Messages []*Message `json:"-"` - PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"` -} - -// A PermissionOverwrite holds permission overwrite data for a Channel -type PermissionOverwrite struct { - ID string `json:"id"` - Type string `json:"type"` - Deny int `json:"deny"` - Allow int `json:"allow"` -} - -// Emoji struct holds data related to Emoji's -type Emoji struct { - ID string `json:"id"` - Name string `json:"name"` - Roles []string `json:"roles"` - Managed bool `json:"managed"` - RequireColons bool `json:"require_colons"` -} - -// 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 defination -type VerificationLevel int - -// Constants for VerificationLevel levels from 0 to 3 inclusive -const ( - VerificationLevelNone VerificationLevel = iota - VerificationLevelLow - VerificationLevelMedium - VerificationLevelHigh -) - -// 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 { - ID string `json:"id"` - Name string `json:"name"` - Icon string `json:"icon"` - Region string `json:"region"` - AfkChannelID string `json:"afk_channel_id"` - EmbedChannelID string `json:"embed_channel_id"` - OwnerID string `json:"owner_id"` - JoinedAt Timestamp `json:"joined_at"` - Splash string `json:"splash"` - AfkTimeout int `json:"afk_timeout"` - MemberCount int `json:"member_count"` - VerificationLevel VerificationLevel `json:"verification_level"` - EmbedEnabled bool `json:"embed_enabled"` - Large bool `json:"large"` // ?? - DefaultMessageNotifications int `json:"default_message_notifications"` - Roles []*Role `json:"roles"` - Emojis []*Emoji `json:"emojis"` - Members []*Member `json:"members"` - Presences []*Presence `json:"presences"` - Channels []*Channel `json:"channels"` - VoiceStates []*VoiceState `json:"voice_states"` - Unavailable bool `json:"unavailable"` -} - -// 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 int `json:"permissions"` -} - -// 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"` -} - -// A Role stores information about Discord guild member roles. -type Role struct { - ID string `json:"id"` - Name string `json:"name"` - Managed bool `json:"managed"` - Mentionable bool `json:"mentionable"` - Hoist bool `json:"hoist"` - Color int `json:"color"` - Position int `json:"position"` - Permissions int `json:"permissions"` -} - -// 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"` - Game *Game `json:"game"` - Nick string `json:"nick"` - Roles []string `json:"roles"` - Since *int `json:"since"` -} - -// A Game struct holds the name of the "playing .." game for a user -type Game struct { - Name string `json:"name"` - Type int `json:"type"` - URL string `json:"url,omitempty"` -} - -// UnmarshalJSON unmarshals json to Game struct -func (g *Game) UnmarshalJSON(bytes []byte) error { - temp := &struct { - Name json.Number `json:"name"` - Type json.RawMessage `json:"type"` - URL string `json:"url"` - }{} - err := json.Unmarshal(bytes, temp) - if err != nil { - return err - } - g.URL = temp.URL - g.Name = temp.Name.String() - - if temp.Type != nil { - err = json.Unmarshal(temp.Type, &g.Type) - if err == nil { - return nil - } - - s := "" - err = json.Unmarshal(temp.Type, &s) - if err == nil { - g.Type, err = strconv.Atoi(s) - } - - return err - } - - return nil -} - -// A Member stores user information for Guild members. -type Member struct { - GuildID string `json:"guild_id"` - JoinedAt string `json:"joined_at"` - Nick string `json:"nick"` - Deaf bool `json:"deaf"` - Mute bool `json:"mute"` - User *User `json:"user"` - Roles []string `json:"roles"` -} - -// 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 defination -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"` -} - -// 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 GuildIntegration stores data for a guild integration. -type GuildIntegration 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"` - ExpireBehavior int `json:"expire_behavior"` - ExpireGracePeriod int `json:"expire_grace_period"` - User *User `json:"user"` - Account *GuildIntegrationAccount `json:"account"` - SyncedAt int `json:"synced_at"` -} - -// A GuildIntegrationAccount stores data for a guild integration account. -type GuildIntegrationAccount struct { - ID string `json:"id"` - Name string `json:"name"` -} - -// A GuildEmbed stores data for a guild embed. -type GuildEmbed struct { - Enabled bool `json:"enabled"` - ChannelID string `json:"channel_id"` -} - -// 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"` - 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"` -} - -// 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 string `json:"file,omitempty"` - Embeds []*MessageEmbed `json:"embeds,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"` -} - -// GatewayBotResponse stores the data for the gateway/bot response -type GatewayBotResponse struct { - URL string `json:"url"` - Shards int `json:"shards"` -} - -// Constants for the different bit offsets of text channel permissions -const ( - 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 -) - -// 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 - - PermissionAllText = PermissionReadMessages | - PermissionSendMessages | - PermissionSendTTSMessages | - PermissionManageMessages | - PermissionEmbedLinks | - PermissionAttachFiles | - PermissionReadMessageHistory | - PermissionMentionEveryone - PermissionAllVoice = PermissionVoiceConnect | - PermissionVoiceSpeak | - PermissionVoiceMuteMembers | - PermissionVoiceDeafenMembers | - PermissionVoiceMoveMembers | - PermissionVoiceUseVAD - PermissionAllChannel = PermissionAllText | - PermissionAllVoice | - PermissionCreateInstantInvite | - PermissionManageRoles | - PermissionManageChannels | - PermissionAddReactions | - PermissionViewAuditLogs - PermissionAll = PermissionAllChannel | - PermissionKickMembers | - PermissionBanMembers | - PermissionManageServer | - PermissionAdministrator -) - -// 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 - - ErrCodeBotsCannotUseEndpoint = 20001 - ErrCodeOnlyBotsCanUseEndpoint = 20002 - - ErrCodeMaximumGuildsReached = 30001 - ErrCodeMaximumFriendsReached = 30002 - ErrCodeMaximumPinsReached = 30003 - ErrCodeMaximumGuildRolesReached = 30005 - ErrCodeTooManyReactions = 30010 - - ErrCodeUnauthorized = 40001 - - ErrCodeMissingAccess = 50001 - ErrCodeInvalidAccountType = 50002 - ErrCodeCannotExecuteActionOnDMChannel = 50003 - ErrCodeEmbedCisabled = 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 -) diff --git a/DiscordGo/tools/cmd/eventhandlers/main.go b/DiscordGo/tools/cmd/eventhandlers/main.go deleted file mode 100644 index 839f009da2cba9753d6f5d10a357bb912b7de101..0000000000000000000000000000000000000000 --- a/DiscordGo/tools/cmd/eventhandlers/main.go +++ /dev/null @@ -1,124 +0,0 @@ -package main - -import ( - "bytes" - "go/format" - "go/parser" - "go/token" - "io/ioutil" - "log" - "path/filepath" - "regexp" - "sort" - "strings" - "text/template" -) - -var eventHandlerTmpl = template.Must(template.New("eventHandler").Funcs(template.FuncMap{ - "constName": constName, - "isDiscordEvent": isDiscordEvent, - "privateName": privateName, -}).Parse(`// 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 ({{range .}} - {{privateName .}}EventType = "{{constName .}}"{{end}} -) -{{range .}} -// {{privateName .}}EventHandler is an event handler for {{.}} events. -type {{privateName .}}EventHandler func(*Session, *{{.}}) - -// Type returns the event type for {{.}} events. -func (eh {{privateName .}}EventHandler) Type() string { - return {{privateName .}}EventType -} -{{if isDiscordEvent .}} -// New returns a new instance of {{.}}. -func (eh {{privateName .}}EventHandler) New() interface{} { - return &{{.}}{} -}{{end}} -// Handle is the handler for {{.}} events. -func (eh {{privateName .}}EventHandler) Handle(s *Session, i interface{}) { - if t, ok := i.(*{{.}}); ok { - eh(s, t) - } -} - -{{end}} -func handlerForInterface(handler interface{}) EventHandler { - switch v := handler.(type) { - case func(*Session, interface{}): - return interfaceEventHandler(v){{range .}} - case func(*Session, *{{.}}): - return {{privateName .}}EventHandler(v){{end}} - } - - return nil -} - -func init() { {{range .}}{{if isDiscordEvent .}} - registerInterfaceProvider({{privateName .}}EventHandler(nil)){{end}}{{end}} -} -`)) - -func main() { - var buf bytes.Buffer - dir := filepath.Dir(".") - - fs := token.NewFileSet() - parsedFile, err := parser.ParseFile(fs, "events.go", nil, 0) - if err != nil { - log.Fatalf("warning: internal error: could not parse events.go: %s", err) - return - } - - names := []string{} - for object := range parsedFile.Scope.Objects { - names = append(names, object) - } - sort.Strings(names) - eventHandlerTmpl.Execute(&buf, names) - - src, err := format.Source(buf.Bytes()) - if err != nil { - log.Println("warning: internal error: invalid Go generated:", err) - src = buf.Bytes() - } - - err = ioutil.WriteFile(filepath.Join(dir, strings.ToLower("eventhandlers.go")), src, 0644) - if err != nil { - log.Fatal(buf, "writing output: %s", err) - } -} - -var constRegexp = regexp.MustCompile("([a-z])([A-Z])") - -func constCase(name string) string { - return strings.ToUpper(constRegexp.ReplaceAllString(name, "${1}_${2}")) -} - -func isDiscordEvent(name string) bool { - switch { - case name == "Connect", name == "Disconnect", name == "Event", name == "RateLimit", name == "Interface": - return false - default: - return true - } -} - -func constName(name string) string { - if !isDiscordEvent(name) { - return "__" + constCase(name) + "__" - } - - return constCase(name) -} - -func privateName(name string) string { - return strings.ToLower(string(name[0])) + name[1:] -} diff --git a/DiscordGo/types.go b/DiscordGo/types.go deleted file mode 100644 index 780b6bb978e26a34eccb0b8212d511994d1a5e67..0000000000000000000000000000000000000000 --- a/DiscordGo/types.go +++ /dev/null @@ -1,58 +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" - "fmt" - "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 fmt.Sprintf("HTTP %s, %s", r.Response.Status, r.ResponseBody) -} diff --git a/DiscordGo/types_test.go b/DiscordGo/types_test.go deleted file mode 100644 index 1d03aa32e9ce872405aa172fad6507b34c802073..0000000000000000000000000000000000000000 --- a/DiscordGo/types_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package discordgo - -import ( - "testing" - "time" -) - -func TestTimestampParse(t *testing.T) { - ts, err := Timestamp("2016-03-24T23:15:59.605000+00:00").Parse() - if err != nil { - t.Fatal(err) - } - if ts.Year() != 2016 || ts.Month() != time.March || ts.Day() != 24 { - t.Error("Incorrect date") - } - if ts.Hour() != 23 || ts.Minute() != 15 || ts.Second() != 59 { - t.Error("Incorrect time") - } - - _, offset := ts.Zone() - if offset != 0 { - t.Error("Incorrect timezone") - } -} diff --git a/DiscordGo/user.go b/DiscordGo/user.go deleted file mode 100644 index 76abdd1d7b025fe3e536d0181b757388b58561e6..0000000000000000000000000000000000000000 --- a/DiscordGo/user.go +++ /dev/null @@ -1,42 +0,0 @@ -package discordgo - -import ( - "fmt" - "strings" -) - -// A User stores all data for an individual Discord user. -type User struct { - ID string `json:"id"` - Email string `json:"email"` - Username string `json:"username"` - Avatar string `json:"avatar"` - Discriminator string `json:"discriminator"` - Token string `json:"token"` - Verified bool `json:"verified"` - MFAEnabled bool `json:"mfa_enabled"` - Bot bool `json:"bot"` -} - -// String returns a unique identifier of the form username#discriminator -func (u *User) String() string { - return fmt.Sprintf("%s#%s", u.Username, u.Discriminator) -} - -// Mention return a string which mentions the user -func (u *User) Mention() string { - return fmt.Sprintf("<@%s>", u.ID) -} - -// AvatarURL returns a URL to the user's avatar. -// size: The size of the user's avatar as a power of two -func (u *User) AvatarURL(size string) string { - var URL string - if strings.HasPrefix(u.Avatar, "a_") { - URL = EndpointUserAvatarAnimated(u.ID, u.Avatar) - } else { - URL = EndpointUserAvatar(u.ID, u.Avatar) - } - - return URL + "?size=" + size -} diff --git a/DiscordGo/user_test.go b/DiscordGo/user_test.go deleted file mode 100644 index a36f673e358c79410d4b2fa128675ac58dffee95..0000000000000000000000000000000000000000 --- a/DiscordGo/user_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package discordgo - -import "testing" - -func TestUser(t *testing.T) { - t.Parallel() - - user := &User{ - Username: "bob", - Discriminator: "8192", - } - - if user.String() != "bob#8192" { - t.Errorf("user.String() == %v", user.String()) - } -} diff --git a/DiscordGo/util.go b/DiscordGo/util.go deleted file mode 100644 index 564e43b267dc4c6c99a81f51a33b0b97fcb72782..0000000000000000000000000000000000000000 --- a/DiscordGo/util.go +++ /dev/null @@ -1,34 +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 utility functions for the discordgo package. These -// functions are not exported and are likely to change substantially in -// the future to match specific needs of the discordgo package itself. - -package discordgo - -import ( - "bytes" - "encoding/json" - "fmt" -) - -// printEvent prints out a WSAPI event. -func printEvent(e *Event) { - //fmt.Println(fmt.Sprintf("Event. Type: %s, State: %d Operation: %d Direction: %d", e.Type, e.State, e.Operation, e.Direction)) - //printJSON(e.RawData) -} - -// printJSON is a helper function to display JSON data in a easy to read format. -func printJSON(body []byte) { - var prettyJSON bytes.Buffer - error := json.Indent(&prettyJSON, body, "", "\t") - if error != nil { - fmt.Print("JSON parse error: ", error) - } - fmt.Println(string(prettyJSON.Bytes())) -} diff --git a/DiscordGo/voice.go b/DiscordGo/voice.go deleted file mode 100644 index 8f033aa006b518ae5a73729a6f52d792b50c9cb2..0000000000000000000000000000000000000000 --- a/DiscordGo/voice.go +++ /dev/null @@ -1,887 +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" - "log" - "net" - "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 defination 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 - log.Println("Speaking() write json error:", 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. -// !!! NOTE !!! this function may be removed in favour of ChannelVoiceLeave -func (v *VoiceConnection) Disconnect() (err error) { - - // Send a OP4 with a nil channel to disconnect - 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 = "" - } - - // 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 { - log.Println("error closing udp connection: ", 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"` -} - -// WaitUntilConnected waits for the Voice Connection to -// become ready, if it does not become ready it retuns 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 := fmt.Sprintf("wss://%s", 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 { - // 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 := fmt.Sprintf("%s:%d", strings.TrimSuffix(v.endpoint, ":80"), 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.LittleEndian.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 reconenct 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/DiscordGo/wsapi.go b/DiscordGo/wsapi.go deleted file mode 100644 index df87092e0913311bc1cf6784b614fa3b6ff7bbd1..0000000000000000000000000000000000000000 --- a/DiscordGo/wsapi.go +++ /dev/null @@ -1,782 +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" - "io" - "net/http" - "runtime" - "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 opens a websocket connection to Discord. -func (s *Session) Open() (err error) { - - s.log(LogInformational, "called") - - s.Lock() - defer func() { - if err != nil { - s.Unlock() - } - }() - - // A basic state is a hard requirement for Voice. - if s.State == nil { - state := NewState() - state.TrackChannels = false - state.TrackEmojis = false - state.TrackMembers = false - state.TrackRoles = false - state.TrackVoice = false - s.State = state - } - - if s.wsConn != nil { - err = ErrWSAlreadyOpen - return - } - - if s.VoiceConnections == nil { - s.log(LogInformational, "creating new VoiceConnections map") - s.VoiceConnections = make(map[string]*VoiceConnection) - } - - // Get the gateway to use for the Websocket connection - if s.gateway == "" { - s.gateway, err = s.Gateway() - if err != nil { - return - } - - // Add the version and encoding to the URL - s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json" - } - - header := http.Header{} - header.Add("accept-encoding", "zlib") - - s.log(LogInformational, "connecting to gateway %s", s.gateway) - s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header) - if err != nil { - s.log(LogWarning, "error connecting to gateway %s, %s", s.gateway, err) - s.gateway = "" // clear cached gateway - // TODO: should we add a retry block here? - return - } - - sequence := atomic.LoadInt64(s.sequence) - if s.sessionID != "" && sequence > 0 { - - 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") - err = s.wsConn.WriteJSON(p) - if err != nil { - s.log(LogWarning, "error sending gateway resume packet, %s, %s", s.gateway, err) - return - } - - } else { - - err = s.identify() - if err != nil { - s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err) - return - } - } - - // Create listening outside of listen, as it needs to happen inside the mutex - // lock. - s.listening = make(chan interface{}) - go s.listen(s.wsConn, s.listening) - s.LastHeartbeatAck = time.Now().UTC() - - s.Unlock() - - s.log(LogInformational, "emit connect event") - s.handleEvent(connectEventType, &Connect{}) - - s.log(LogInformational, "exiting") - return -} - -// 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"` - Trace []string `json:"_trace"` -} - -// FailedHeartbeatAcks is the Number of heartbeat intervals to wait until forcing a connection restart. -const FailedHeartbeatAcks time.Duration = 5 * time.Millisecond - -// 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(LogInformational, "sending gateway websocket heartbeat seq %d", sequence) - s.wsMutex.Lock() - 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 - } - } -} - -type updateStatusData struct { - IdleSince *int `json:"since"` - Game *Game `json:"game"` - AFK bool `json:"afk"` - Status string `json:"status"` -} - -type updateStatusOp struct { - Op int `json:"op"` - Data updateStatusData `json:"d"` -} - -// UpdateStreamingStatus is used to update the user's streaming status. -// If idle>0 then set status to idle. -// If game!="" then set game. -// If game!="" 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, game string, url string) (err error) { - - s.log(LogInformational, "called") - - s.RLock() - defer s.RUnlock() - if s.wsConn == nil { - return ErrWSNotFound - } - - usd := updateStatusData{ - Status: "online", - } - - if idle > 0 { - usd.IdleSince = &idle - } - - if game != "" { - gameType := 0 - if url != "" { - gameType = 1 - } - usd.Game = &Game{ - Name: game, - Type: gameType, - URL: url, - } - } - - s.wsMutex.Lock() - err = s.wsConn.WriteJSON(updateStatusOp{3, usd}) - s.wsMutex.Unlock() - - return -} - -// UpdateStatus is used to update the user's status. -// If idle>0 then set status to idle. -// If game!="" then set game. -// if otherwise, set status to active, and no game. -func (s *Session) UpdateStatus(idle int, game string) (err error) { - return s.UpdateStreamingStatus(idle, game, "") -} - -type requestGuildMembersData struct { - GuildID string `json:"guild_id"` - Query string `json:"query"` - Limit int `json:"limit"` -} - -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 : The ID of the guild 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 -func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err error) { - s.log(LogInformational, "called") - - s.RLock() - defer s.RUnlock() - if s.wsConn == nil { - return ErrWSNotFound - } - - data := requestGuildMembersData{ - GuildID: guildID, - Query: query, - Limit: limit, - } - - 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. -// -// TODO: You may also register a custom event handler entirely using... -func (s *Session) onEvent(messageType int, message []byte) { - - 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 - } - - 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 - } - - 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 - } - - return - } - - // 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.Close() - s.reconnect() - return - } - - // 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 - } - - return - } - - if e.Operation == 10 { - var h helloOp - if err = json.Unmarshal(e.RawData, &h); err != nil { - s.log(LogError, "error unmarshalling helloOp, %s", err) - } else { - go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval) - } - return - } - - if e.Operation == 11 { - s.Lock() - s.LastHeartbeatAck = time.Now().UTC() - s.Unlock() - s.log(LogInformational, "got heartbeat ACK") - return - } - - // 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 - } - - // 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) -} - -// ------------------------------------------------------------------------------------------------ -// 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() - - // Send the request to Discord that we want to join the voice channel - data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, &cID, mute, deaf}} - s.wsMutex.Lock() - err = s.wsConn.WriteJSON(data) - s.wsMutex.Unlock() - 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 -} - -// 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 conenction to the voice server - err := voice.open() - if err != nil { - s.log(LogError, "onVoiceServerUpdate voice.open, %s", err) - } -} - -type identifyProperties struct { - OS string `json:"$os"` - Browser string `json:"$browser"` - Device string `json:"$device"` - Referer string `json:"$referer"` - ReferringDomain string `json:"$referring_domain"` -} - -type identifyData struct { - Token string `json:"token"` - Properties identifyProperties `json:"properties"` - LargeThreshold int `json:"large_threshold"` - Compress bool `json:"compress"` - Shard *[2]int `json:"shard,omitempty"` -} - -type identifyOp struct { - Op int `json:"op"` - Data identifyData `json:"d"` -} - -// identify sends the identify packet to the gateway -func (s *Session) identify() error { - - properties := identifyProperties{runtime.GOOS, - "Discordgo v" + VERSION, - "", - "", - "", - } - - data := identifyData{s.Token, - properties, - 250, - s.Compress, - nil, - } - - if s.ShardCount > 1 { - - if s.ShardID >= s.ShardCount { - return ErrWSShardBounds - } - - data.Shard = &[2]int{s.ShardID, s.ShardCount} - } - - op := identifyOp{2, data} - - s.wsMutex.Lock() - err := s.wsConn.WriteJSON(op) - s.wsMutex.Unlock() - if err != nil { - return err - } - - return nil -} - -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 connections -func (s *Session) Close() (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(websocket.CloseNormalClosure, "")) - 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/websocket/AUTHORS b/websocket/AUTHORS deleted file mode 100644 index b003eca0ca187243683ea444142d4465dd77b619..0000000000000000000000000000000000000000 --- a/websocket/AUTHORS +++ /dev/null @@ -1,8 +0,0 @@ -# This is the official list of Gorilla WebSocket authors for copyright -# purposes. -# -# Please keep the list sorted. - -Gary Burd -Joachim Bauch - diff --git a/websocket/LICENSE b/websocket/LICENSE deleted file mode 100644 index 9171c972252257cf416925ddff4be6cb73973a82..0000000000000000000000000000000000000000 --- a/websocket/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2013 The Gorilla WebSocket Authors. 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. - -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/websocket/README.md b/websocket/README.md deleted file mode 100644 index 33c3d2be3e3d000612d906de645aaa5c7f64a62c..0000000000000000000000000000000000000000 --- a/websocket/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Gorilla WebSocket - -Gorilla WebSocket is a [Go](http://golang.org/) implementation of the -[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. - -[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket) -[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) - -### Documentation - -* [API Reference](http://godoc.org/github.com/gorilla/websocket) -* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) -* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) -* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) -* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) - -### Status - -The Gorilla WebSocket package provides a complete and tested implementation of -the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The -package API is stable. - -### Installation - - go get github.com/gorilla/websocket - -### Protocol Compliance - -The Gorilla WebSocket package passes the server tests in the [Autobahn Test -Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn -subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). - -### Gorilla WebSocket compared with other packages - - - - - - - - - - - - - - - - - - -
github.com/gorillagolang.org/x/net
RFC 6455 Features
Passes Autobahn Test SuiteYesNo
Receive fragmented messageYesNo, see note 1
Send close messageYesNo
Send pings and receive pongsYesNo
Get the type of a received data messageYesYes, see note 2
Other Features
Compression ExtensionsExperimentalNo
Read message using io.ReaderYesNo, see note 3
Write message using io.WriteCloserYesNo, see note 3
- -Notes: - -1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). -2. The application can get the type of a received data message by implementing - a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) - function. -3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. - Read returns when the input buffer is full or a frame boundary is - encountered. Each call to Write sends a single frame message. The Gorilla - io.Reader and io.WriteCloser operate on a single WebSocket message. - diff --git a/websocket/client.go b/websocket/client.go deleted file mode 100644 index 43a87c753bfce1f378ba1bd7462890b18cf30b01..0000000000000000000000000000000000000000 --- a/websocket/client.go +++ /dev/null @@ -1,392 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bufio" - "bytes" - "crypto/tls" - "encoding/base64" - "errors" - "io" - "io/ioutil" - "net" - "net/http" - "net/url" - "strings" - "time" -) - -// ErrBadHandshake is returned when the server response to opening handshake is -// invalid. -var ErrBadHandshake = errors.New("websocket: bad handshake") - -var errInvalidCompression = errors.New("websocket: invalid compression negotiation") - -// NewClient creates a new client connection using the given net connection. -// The URL u specifies the host and request URI. Use requestHeader to specify -// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies -// (Cookie). Use the response.Header to get the selected subprotocol -// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). -// -// If the WebSocket handshake fails, ErrBadHandshake is returned along with a -// non-nil *http.Response so that callers can handle redirects, authentication, -// etc. -// -// Deprecated: Use Dialer instead. -func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { - d := Dialer{ - ReadBufferSize: readBufSize, - WriteBufferSize: writeBufSize, - NetDial: func(net, addr string) (net.Conn, error) { - return netConn, nil - }, - } - return d.Dial(u.String(), requestHeader) -} - -// A Dialer contains options for connecting to WebSocket server. -type Dialer struct { - // NetDial specifies the dial function for creating TCP connections. If - // NetDial is nil, net.Dial is used. - NetDial func(network, addr string) (net.Conn, error) - - // Proxy specifies a function to return a proxy for a given - // Request. If the function returns a non-nil error, the - // request is aborted with the provided error. - // If Proxy is nil or returns a nil *URL, no proxy is used. - Proxy func(*http.Request) (*url.URL, error) - - // TLSClientConfig specifies the TLS configuration to use with tls.Client. - // If nil, the default configuration is used. - TLSClientConfig *tls.Config - - // HandshakeTimeout specifies the duration for the handshake to complete. - HandshakeTimeout time.Duration - - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer - // size is zero, then a useful default size is used. The I/O buffer sizes - // do not limit the size of the messages that can be sent or received. - ReadBufferSize, WriteBufferSize int - - // Subprotocols specifies the client's requested subprotocols. - Subprotocols []string - - // EnableCompression specifies if the client should attempt to negotiate - // per message compression (RFC 7692). Setting this value to true does not - // guarantee that compression will be supported. Currently only "no context - // takeover" modes are supported. - EnableCompression bool - - // Jar specifies the cookie jar. - // If Jar is nil, cookies are not sent in requests and ignored - // in responses. - Jar http.CookieJar -} - -var errMalformedURL = errors.New("malformed ws or wss URL") - -// parseURL parses the URL. -// -// This function is a replacement for the standard library url.Parse function. -// In Go 1.4 and earlier, url.Parse loses information from the path. -func parseURL(s string) (*url.URL, error) { - // From the RFC: - // - // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] - // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] - var u url.URL - switch { - case strings.HasPrefix(s, "ws://"): - u.Scheme = "ws" - s = s[len("ws://"):] - case strings.HasPrefix(s, "wss://"): - u.Scheme = "wss" - s = s[len("wss://"):] - default: - return nil, errMalformedURL - } - - if i := strings.Index(s, "?"); i >= 0 { - u.RawQuery = s[i+1:] - s = s[:i] - } - - if i := strings.Index(s, "/"); i >= 0 { - u.Opaque = s[i:] - s = s[:i] - } else { - u.Opaque = "/" - } - - u.Host = s - - if strings.Contains(u.Host, "@") { - // Don't bother parsing user information because user information is - // not allowed in websocket URIs. - return nil, errMalformedURL - } - - return &u, nil -} - -func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { - hostPort = u.Host - hostNoPort = u.Host - if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { - hostNoPort = hostNoPort[:i] - } else { - switch u.Scheme { - case "wss": - hostPort += ":443" - case "https": - hostPort += ":443" - default: - hostPort += ":80" - } - } - return hostPort, hostNoPort -} - -// DefaultDialer is a dialer with all fields set to the default zero values. -var DefaultDialer = &Dialer{ - Proxy: http.ProxyFromEnvironment, -} - -// Dial creates a new client connection. Use requestHeader to specify the -// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). -// Use the response.Header to get the selected subprotocol -// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). -// -// If the WebSocket handshake fails, ErrBadHandshake is returned along with a -// non-nil *http.Response so that callers can handle redirects, authentication, -// etcetera. The response body may not contain the entire response and does not -// need to be closed by the application. -func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { - - if d == nil { - d = &Dialer{ - Proxy: http.ProxyFromEnvironment, - } - } - - challengeKey, err := generateChallengeKey() - if err != nil { - return nil, nil, err - } - - u, err := parseURL(urlStr) - if err != nil { - return nil, nil, err - } - - switch u.Scheme { - case "ws": - u.Scheme = "http" - case "wss": - u.Scheme = "https" - default: - return nil, nil, errMalformedURL - } - - if u.User != nil { - // User name and password are not allowed in websocket URIs. - return nil, nil, errMalformedURL - } - - req := &http.Request{ - Method: "GET", - URL: u, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: make(http.Header), - Host: u.Host, - } - - // Set the cookies present in the cookie jar of the dialer - if d.Jar != nil { - for _, cookie := range d.Jar.Cookies(u) { - req.AddCookie(cookie) - } - } - - // Set the request headers using the capitalization for names and values in - // RFC examples. Although the capitalization shouldn't matter, there are - // servers that depend on it. The Header.Set method is not used because the - // method canonicalizes the header names. - req.Header["Upgrade"] = []string{"websocket"} - req.Header["Connection"] = []string{"Upgrade"} - req.Header["Sec-WebSocket-Key"] = []string{challengeKey} - req.Header["Sec-WebSocket-Version"] = []string{"13"} - if len(d.Subprotocols) > 0 { - req.Header["Sec-WebSocket-Protocol"] = []string{strings.Join(d.Subprotocols, ", ")} - } - for k, vs := range requestHeader { - switch { - case k == "Host": - if len(vs) > 0 { - req.Host = vs[0] - } - case k == "Upgrade" || - k == "Connection" || - k == "Sec-Websocket-Key" || - k == "Sec-Websocket-Version" || - k == "Sec-Websocket-Extensions" || - (k == "Sec-Websocket-Protocol" && len(d.Subprotocols) > 0): - return nil, nil, errors.New("websocket: duplicate header not allowed: " + k) - default: - req.Header[k] = vs - } - } - - if d.EnableCompression { - req.Header.Set("Sec-Websocket-Extensions", "permessage-deflate; server_no_context_takeover; client_no_context_takeover") - } - - hostPort, hostNoPort := hostPortNoPort(u) - - var proxyURL *url.URL - // Check wether the proxy method has been configured - if d.Proxy != nil { - proxyURL, err = d.Proxy(req) - } - if err != nil { - return nil, nil, err - } - - var targetHostPort string - if proxyURL != nil { - targetHostPort, _ = hostPortNoPort(proxyURL) - } else { - targetHostPort = hostPort - } - - var deadline time.Time - if d.HandshakeTimeout != 0 { - deadline = time.Now().Add(d.HandshakeTimeout) - } - - netDial := d.NetDial - if netDial == nil { - netDialer := &net.Dialer{Deadline: deadline} - netDial = netDialer.Dial - } - - netConn, err := netDial("tcp", targetHostPort) - if err != nil { - return nil, nil, err - } - - defer func() { - if netConn != nil { - netConn.Close() - } - }() - - if err := netConn.SetDeadline(deadline); err != nil { - return nil, nil, err - } - - if proxyURL != nil { - connectHeader := make(http.Header) - if user := proxyURL.User; user != nil { - proxyUser := user.Username() - if proxyPassword, passwordSet := user.Password(); passwordSet { - credential := base64.StdEncoding.EncodeToString([]byte(proxyUser + ":" + proxyPassword)) - connectHeader.Set("Proxy-Authorization", "Basic "+credential) - } - } - connectReq := &http.Request{ - Method: "CONNECT", - URL: &url.URL{Opaque: hostPort}, - Host: hostPort, - Header: connectHeader, - } - - connectReq.Write(netConn) - - // Read response. - // Okay to use and discard buffered reader here, because - // TLS server will not speak until spoken to. - br := bufio.NewReader(netConn) - resp, err := http.ReadResponse(br, connectReq) - if err != nil { - return nil, nil, err - } - if resp.StatusCode != 200 { - f := strings.SplitN(resp.Status, " ", 2) - return nil, nil, errors.New(f[1]) - } - } - - if u.Scheme == "https" { - cfg := cloneTLSConfig(d.TLSClientConfig) - if cfg.ServerName == "" { - cfg.ServerName = hostNoPort - } - tlsConn := tls.Client(netConn, cfg) - netConn = tlsConn - if err := tlsConn.Handshake(); err != nil { - return nil, nil, err - } - if !cfg.InsecureSkipVerify { - if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { - return nil, nil, err - } - } - } - - conn := newConn(netConn, false, d.ReadBufferSize, d.WriteBufferSize) - - if err := req.Write(netConn); err != nil { - return nil, nil, err - } - - resp, err := http.ReadResponse(conn.br, req) - if err != nil { - return nil, nil, err - } - - if d.Jar != nil { - if rc := resp.Cookies(); len(rc) > 0 { - d.Jar.SetCookies(u, rc) - } - } - - if resp.StatusCode != 101 || - !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || - !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || - resp.Header.Get("Sec-Websocket-Accept") != computeAcceptKey(challengeKey) { - // Before closing the network connection on return from this - // function, slurp up some of the response to aid application - // debugging. - buf := make([]byte, 1024) - n, _ := io.ReadFull(resp.Body, buf) - resp.Body = ioutil.NopCloser(bytes.NewReader(buf[:n])) - return nil, resp, ErrBadHandshake - } - - for _, ext := range parseExtensions(resp.Header) { - if ext[""] != "permessage-deflate" { - continue - } - _, snct := ext["server_no_context_takeover"] - _, cnct := ext["client_no_context_takeover"] - if !snct || !cnct { - return nil, resp, errInvalidCompression - } - conn.newCompressionWriter = compressNoContextTakeover - conn.newDecompressionReader = decompressNoContextTakeover - break - } - - resp.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) - conn.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") - - netConn.SetDeadline(time.Time{}) - netConn = nil // to avoid close in defer. - return conn, resp, nil -} diff --git a/websocket/client_clone.go b/websocket/client_clone.go deleted file mode 100644 index 4f0d943723a9d094c1693892812377a51f6791bb..0000000000000000000000000000000000000000 --- a/websocket/client_clone.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.8 - -package websocket - -import "crypto/tls" - -func cloneTLSConfig(cfg *tls.Config) *tls.Config { - if cfg == nil { - return &tls.Config{} - } - return cfg.Clone() -} diff --git a/websocket/client_clone_legacy.go b/websocket/client_clone_legacy.go deleted file mode 100644 index babb007fb4144ac456faeb685e24b62186c7c517..0000000000000000000000000000000000000000 --- a/websocket/client_clone_legacy.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.8 - -package websocket - -import "crypto/tls" - -// cloneTLSConfig clones all public fields except the fields -// SessionTicketsDisabled and SessionTicketKey. This avoids copying the -// sync.Mutex in the sync.Once and makes it safe to call cloneTLSConfig on a -// config in active use. -func cloneTLSConfig(cfg *tls.Config) *tls.Config { - if cfg == nil { - return &tls.Config{} - } - return &tls.Config{ - Rand: cfg.Rand, - Time: cfg.Time, - Certificates: cfg.Certificates, - NameToCertificate: cfg.NameToCertificate, - GetCertificate: cfg.GetCertificate, - RootCAs: cfg.RootCAs, - NextProtos: cfg.NextProtos, - ServerName: cfg.ServerName, - ClientAuth: cfg.ClientAuth, - ClientCAs: cfg.ClientCAs, - InsecureSkipVerify: cfg.InsecureSkipVerify, - CipherSuites: cfg.CipherSuites, - PreferServerCipherSuites: cfg.PreferServerCipherSuites, - ClientSessionCache: cfg.ClientSessionCache, - MinVersion: cfg.MinVersion, - MaxVersion: cfg.MaxVersion, - CurvePreferences: cfg.CurvePreferences, - } -} diff --git a/websocket/client_server_test.go b/websocket/client_server_test.go deleted file mode 100644 index 7d39da6811f2e5e06eb007ca8691ea0312362e8f..0000000000000000000000000000000000000000 --- a/websocket/client_server_test.go +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "crypto/tls" - "crypto/x509" - "encoding/base64" - "io" - "io/ioutil" - "net/http" - "net/http/cookiejar" - "net/http/httptest" - "net/url" - "reflect" - "strings" - "testing" - "time" -) - -var cstUpgrader = Upgrader{ - Subprotocols: []string{"p0", "p1"}, - ReadBufferSize: 1024, - WriteBufferSize: 1024, - EnableCompression: true, - Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) { - http.Error(w, reason.Error(), status) - }, -} - -var cstDialer = Dialer{ - Subprotocols: []string{"p1", "p2"}, - ReadBufferSize: 1024, - WriteBufferSize: 1024, -} - -type cstHandler struct{ *testing.T } - -type cstServer struct { - *httptest.Server - URL string -} - -const ( - cstPath = "/a/b" - cstRawQuery = "x=y" - cstRequestURI = cstPath + "?" + cstRawQuery -) - -func newServer(t *testing.T) *cstServer { - var s cstServer - s.Server = httptest.NewServer(cstHandler{t}) - s.Server.URL += cstRequestURI - s.URL = makeWsProto(s.Server.URL) - return &s -} - -func newTLSServer(t *testing.T) *cstServer { - var s cstServer - s.Server = httptest.NewTLSServer(cstHandler{t}) - s.Server.URL += cstRequestURI - s.URL = makeWsProto(s.Server.URL) - return &s -} - -func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != cstPath { - t.Logf("path=%v, want %v", r.URL.Path, cstPath) - http.Error(w, "bad path", 400) - return - } - if r.URL.RawQuery != cstRawQuery { - t.Logf("query=%v, want %v", r.URL.RawQuery, cstRawQuery) - http.Error(w, "bad path", 400) - return - } - subprotos := Subprotocols(r) - if !reflect.DeepEqual(subprotos, cstDialer.Subprotocols) { - t.Logf("subprotols=%v, want %v", subprotos, cstDialer.Subprotocols) - http.Error(w, "bad protocol", 400) - return - } - ws, err := cstUpgrader.Upgrade(w, r, http.Header{"Set-Cookie": {"sessionID=1234"}}) - if err != nil { - t.Logf("Upgrade: %v", err) - return - } - defer ws.Close() - - if ws.Subprotocol() != "p1" { - t.Logf("Subprotocol() = %s, want p1", ws.Subprotocol()) - ws.Close() - return - } - op, rd, err := ws.NextReader() - if err != nil { - t.Logf("NextReader: %v", err) - return - } - wr, err := ws.NextWriter(op) - if err != nil { - t.Logf("NextWriter: %v", err) - return - } - if _, err = io.Copy(wr, rd); err != nil { - t.Logf("NextWriter: %v", err) - return - } - if err := wr.Close(); err != nil { - t.Logf("Close: %v", err) - return - } -} - -func makeWsProto(s string) string { - return "ws" + strings.TrimPrefix(s, "http") -} - -func sendRecv(t *testing.T, ws *Conn) { - const message = "Hello World!" - if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil { - t.Fatalf("SetWriteDeadline: %v", err) - } - if err := ws.WriteMessage(TextMessage, []byte(message)); err != nil { - t.Fatalf("WriteMessage: %v", err) - } - if err := ws.SetReadDeadline(time.Now().Add(time.Second)); err != nil { - t.Fatalf("SetReadDeadline: %v", err) - } - _, p, err := ws.ReadMessage() - if err != nil { - t.Fatalf("ReadMessage: %v", err) - } - if string(p) != message { - t.Fatalf("message=%s, want %s", p, message) - } -} - -func TestProxyDial(t *testing.T) { - - s := newServer(t) - defer s.Close() - - surl, _ := url.Parse(s.URL) - - cstDialer.Proxy = http.ProxyURL(surl) - - connect := false - origHandler := s.Server.Config.Handler - - // Capture the request Host header. - s.Server.Config.Handler = http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - if r.Method == "CONNECT" { - connect = true - w.WriteHeader(200) - return - } - - if !connect { - t.Log("connect not recieved") - http.Error(w, "connect not recieved", 405) - return - } - origHandler.ServeHTTP(w, r) - }) - - ws, _, err := cstDialer.Dial(s.URL, nil) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - sendRecv(t, ws) - - cstDialer.Proxy = http.ProxyFromEnvironment -} - -func TestProxyAuthorizationDial(t *testing.T) { - s := newServer(t) - defer s.Close() - - surl, _ := url.Parse(s.URL) - surl.User = url.UserPassword("username", "password") - cstDialer.Proxy = http.ProxyURL(surl) - - connect := false - origHandler := s.Server.Config.Handler - - // Capture the request Host header. - s.Server.Config.Handler = http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - proxyAuth := r.Header.Get("Proxy-Authorization") - expectedProxyAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte("username:password")) - if r.Method == "CONNECT" && proxyAuth == expectedProxyAuth { - connect = true - w.WriteHeader(200) - return - } - - if !connect { - t.Log("connect with proxy authorization not recieved") - http.Error(w, "connect with proxy authorization not recieved", 405) - return - } - origHandler.ServeHTTP(w, r) - }) - - ws, _, err := cstDialer.Dial(s.URL, nil) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - sendRecv(t, ws) - - cstDialer.Proxy = http.ProxyFromEnvironment -} - -func TestDial(t *testing.T) { - s := newServer(t) - defer s.Close() - - ws, _, err := cstDialer.Dial(s.URL, nil) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - sendRecv(t, ws) -} - -func TestDialCookieJar(t *testing.T) { - s := newServer(t) - defer s.Close() - - jar, _ := cookiejar.New(nil) - d := cstDialer - d.Jar = jar - - u, _ := parseURL(s.URL) - - switch u.Scheme { - case "ws": - u.Scheme = "http" - case "wss": - u.Scheme = "https" - } - - cookies := []*http.Cookie{&http.Cookie{Name: "gorilla", Value: "ws", Path: "/"}} - d.Jar.SetCookies(u, cookies) - - ws, _, err := d.Dial(s.URL, nil) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - - var gorilla string - var sessionID string - for _, c := range d.Jar.Cookies(u) { - if c.Name == "gorilla" { - gorilla = c.Value - } - - if c.Name == "sessionID" { - sessionID = c.Value - } - } - if gorilla != "ws" { - t.Error("Cookie not present in jar.") - } - - if sessionID != "1234" { - t.Error("Set-Cookie not received from the server.") - } - - sendRecv(t, ws) -} - -func TestDialTLS(t *testing.T) { - s := newTLSServer(t) - defer s.Close() - - certs := x509.NewCertPool() - for _, c := range s.TLS.Certificates { - roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) - if err != nil { - t.Fatalf("error parsing server's root cert: %v", err) - } - for _, root := range roots { - certs.AddCert(root) - } - } - - d := cstDialer - d.TLSClientConfig = &tls.Config{RootCAs: certs} - ws, _, err := d.Dial(s.URL, nil) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - sendRecv(t, ws) -} - -func xTestDialTLSBadCert(t *testing.T) { - // This test is deactivated because of noisy logging from the net/http package. - s := newTLSServer(t) - defer s.Close() - - ws, _, err := cstDialer.Dial(s.URL, nil) - if err == nil { - ws.Close() - t.Fatalf("Dial: nil") - } -} - -func TestDialTLSNoVerify(t *testing.T) { - s := newTLSServer(t) - defer s.Close() - - d := cstDialer - d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - ws, _, err := d.Dial(s.URL, nil) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - sendRecv(t, ws) -} - -func TestDialTimeout(t *testing.T) { - s := newServer(t) - defer s.Close() - - d := cstDialer - d.HandshakeTimeout = -1 - ws, _, err := d.Dial(s.URL, nil) - if err == nil { - ws.Close() - t.Fatalf("Dial: nil") - } -} - -func TestDialBadScheme(t *testing.T) { - s := newServer(t) - defer s.Close() - - ws, _, err := cstDialer.Dial(s.Server.URL, nil) - if err == nil { - ws.Close() - t.Fatalf("Dial: nil") - } -} - -func TestDialBadOrigin(t *testing.T) { - s := newServer(t) - defer s.Close() - - ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}}) - if err == nil { - ws.Close() - t.Fatalf("Dial: nil") - } - if resp == nil { - t.Fatalf("resp=nil, err=%v", err) - } - if resp.StatusCode != http.StatusForbidden { - t.Fatalf("status=%d, want %d", resp.StatusCode, http.StatusForbidden) - } -} - -func TestDialBadHeader(t *testing.T) { - s := newServer(t) - defer s.Close() - - for _, k := range []string{"Upgrade", - "Connection", - "Sec-Websocket-Key", - "Sec-Websocket-Version", - "Sec-Websocket-Protocol"} { - h := http.Header{} - h.Set(k, "bad") - ws, _, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}}) - if err == nil { - ws.Close() - t.Errorf("Dial with header %s returned nil", k) - } - } -} - -func TestBadMethod(t *testing.T) { - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ws, err := cstUpgrader.Upgrade(w, r, nil) - if err == nil { - t.Errorf("handshake succeeded, expect fail") - ws.Close() - } - })) - defer s.Close() - - resp, err := http.PostForm(s.URL, url.Values{}) - if err != nil { - t.Fatalf("PostForm returned error %v", err) - } - resp.Body.Close() - if resp.StatusCode != http.StatusMethodNotAllowed { - t.Errorf("Status = %d, want %d", resp.StatusCode, http.StatusMethodNotAllowed) - } -} - -func TestHandshake(t *testing.T) { - s := newServer(t) - defer s.Close() - - ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {s.URL}}) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - - var sessionID string - for _, c := range resp.Cookies() { - if c.Name == "sessionID" { - sessionID = c.Value - } - } - if sessionID != "1234" { - t.Error("Set-Cookie not received from the server.") - } - - if ws.Subprotocol() != "p1" { - t.Errorf("ws.Subprotocol() = %s, want p1", ws.Subprotocol()) - } - sendRecv(t, ws) -} - -func TestRespOnBadHandshake(t *testing.T) { - const expectedStatus = http.StatusGone - const expectedBody = "This is the response body." - - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(expectedStatus) - io.WriteString(w, expectedBody) - })) - defer s.Close() - - ws, resp, err := cstDialer.Dial(makeWsProto(s.URL), nil) - if err == nil { - ws.Close() - t.Fatalf("Dial: nil") - } - - if resp == nil { - t.Fatalf("resp=nil, err=%v", err) - } - - if resp.StatusCode != expectedStatus { - t.Errorf("resp.StatusCode=%d, want %d", resp.StatusCode, expectedStatus) - } - - p, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("ReadFull(resp.Body) returned error %v", err) - } - - if string(p) != expectedBody { - t.Errorf("resp.Body=%s, want %s", p, expectedBody) - } -} - -// TestHostHeader confirms that the host header provided in the call to Dial is -// sent to the server. -func TestHostHeader(t *testing.T) { - s := newServer(t) - defer s.Close() - - specifiedHost := make(chan string, 1) - origHandler := s.Server.Config.Handler - - // Capture the request Host header. - s.Server.Config.Handler = http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - specifiedHost <- r.Host - origHandler.ServeHTTP(w, r) - }) - - ws, _, err := cstDialer.Dial(s.URL, http.Header{"Host": {"testhost"}}) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - - if gotHost := <-specifiedHost; gotHost != "testhost" { - t.Fatalf("gotHost = %q, want \"testhost\"", gotHost) - } - - sendRecv(t, ws) -} - -func TestDialCompression(t *testing.T) { - s := newServer(t) - defer s.Close() - - dialer := cstDialer - dialer.EnableCompression = true - ws, _, err := dialer.Dial(s.URL, nil) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer ws.Close() - sendRecv(t, ws) -} diff --git a/websocket/client_test.go b/websocket/client_test.go deleted file mode 100644 index 7d2b0844fdbf0d7337cf9de2de78764b4ad26537..0000000000000000000000000000000000000000 --- a/websocket/client_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "net/url" - "reflect" - "testing" -) - -var parseURLTests = []struct { - s string - u *url.URL - rui string -}{ - {"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"}, - {"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}, "/"}, - {"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}, "/"}, - {"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}, "/"}, - {"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}, "/a/b"}, - {"ss://example.com/a/b", nil, ""}, - {"ws://webmaster@example.com/", nil, ""}, - {"wss://example.com/a/b?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b", RawQuery: "x=y"}, "/a/b?x=y"}, - {"wss://example.com?x=y", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/", RawQuery: "x=y"}, "/?x=y"}, -} - -func TestParseURL(t *testing.T) { - for _, tt := range parseURLTests { - u, err := parseURL(tt.s) - if tt.u != nil && err != nil { - t.Errorf("parseURL(%q) returned error %v", tt.s, err) - continue - } - if tt.u == nil { - if err == nil { - t.Errorf("parseURL(%q) did not return error", tt.s) - } - continue - } - if !reflect.DeepEqual(u, tt.u) { - t.Errorf("parseURL(%q) = %v, want %v", tt.s, u, tt.u) - continue - } - if u.RequestURI() != tt.rui { - t.Errorf("parseURL(%q).RequestURI() = %v, want %v", tt.s, u.RequestURI(), tt.rui) - } - } -} - -var hostPortNoPortTests = []struct { - u *url.URL - hostPort, hostNoPort string -}{ - {&url.URL{Scheme: "ws", Host: "example.com"}, "example.com:80", "example.com"}, - {&url.URL{Scheme: "wss", Host: "example.com"}, "example.com:443", "example.com"}, - {&url.URL{Scheme: "ws", Host: "example.com:7777"}, "example.com:7777", "example.com"}, - {&url.URL{Scheme: "wss", Host: "example.com:7777"}, "example.com:7777", "example.com"}, -} - -func TestHostPortNoPort(t *testing.T) { - for _, tt := range hostPortNoPortTests { - hostPort, hostNoPort := hostPortNoPort(tt.u) - if hostPort != tt.hostPort { - t.Errorf("hostPortNoPort(%v) returned hostPort %q, want %q", tt.u, hostPort, tt.hostPort) - } - if hostNoPort != tt.hostNoPort { - t.Errorf("hostPortNoPort(%v) returned hostNoPort %q, want %q", tt.u, hostNoPort, tt.hostNoPort) - } - } -} diff --git a/websocket/compression.go b/websocket/compression.go deleted file mode 100644 index 813ffb1e84336da415256244366e09bcd65b6765..0000000000000000000000000000000000000000 --- a/websocket/compression.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "compress/flate" - "errors" - "io" - "strings" - "sync" -) - -const ( - minCompressionLevel = -2 // flate.HuffmanOnly not defined in Go < 1.6 - maxCompressionLevel = flate.BestCompression - defaultCompressionLevel = 1 -) - -var ( - flateWriterPools [maxCompressionLevel - minCompressionLevel + 1]sync.Pool - flateReaderPool = sync.Pool{New: func() interface{} { - return flate.NewReader(nil) - }} -) - -func decompressNoContextTakeover(r io.Reader) io.ReadCloser { - const tail = - // Add four bytes as specified in RFC - "\x00\x00\xff\xff" + - // Add final block to squelch unexpected EOF error from flate reader. - "\x01\x00\x00\xff\xff" - - fr, _ := flateReaderPool.Get().(io.ReadCloser) - fr.(flate.Resetter).Reset(io.MultiReader(r, strings.NewReader(tail)), nil) - return &flateReadWrapper{fr} -} - -func isValidCompressionLevel(level int) bool { - return minCompressionLevel <= level && level <= maxCompressionLevel -} - -func compressNoContextTakeover(w io.WriteCloser, level int) io.WriteCloser { - p := &flateWriterPools[level-minCompressionLevel] - tw := &truncWriter{w: w} - fw, _ := p.Get().(*flate.Writer) - if fw == nil { - fw, _ = flate.NewWriter(tw, level) - } else { - fw.Reset(tw) - } - return &flateWriteWrapper{fw: fw, tw: tw, p: p} -} - -// truncWriter is an io.Writer that writes all but the last four bytes of the -// stream to another io.Writer. -type truncWriter struct { - w io.WriteCloser - n int - p [4]byte -} - -func (w *truncWriter) Write(p []byte) (int, error) { - n := 0 - - // fill buffer first for simplicity. - if w.n < len(w.p) { - n = copy(w.p[w.n:], p) - p = p[n:] - w.n += n - if len(p) == 0 { - return n, nil - } - } - - m := len(p) - if m > len(w.p) { - m = len(w.p) - } - - if nn, err := w.w.Write(w.p[:m]); err != nil { - return n + nn, err - } - - copy(w.p[:], w.p[m:]) - copy(w.p[len(w.p)-m:], p[len(p)-m:]) - nn, err := w.w.Write(p[:len(p)-m]) - return n + nn, err -} - -type flateWriteWrapper struct { - fw *flate.Writer - tw *truncWriter - p *sync.Pool -} - -func (w *flateWriteWrapper) Write(p []byte) (int, error) { - if w.fw == nil { - return 0, errWriteClosed - } - return w.fw.Write(p) -} - -func (w *flateWriteWrapper) Close() error { - if w.fw == nil { - return errWriteClosed - } - err1 := w.fw.Flush() - w.p.Put(w.fw) - w.fw = nil - if w.tw.p != [4]byte{0, 0, 0xff, 0xff} { - return errors.New("websocket: internal error, unexpected bytes at end of flate stream") - } - err2 := w.tw.w.Close() - if err1 != nil { - return err1 - } - return err2 -} - -type flateReadWrapper struct { - fr io.ReadCloser -} - -func (r *flateReadWrapper) Read(p []byte) (int, error) { - if r.fr == nil { - return 0, io.ErrClosedPipe - } - n, err := r.fr.Read(p) - if err == io.EOF { - // Preemptively place the reader back in the pool. This helps with - // scenarios where the application does not call NextReader() soon after - // this final read. - r.Close() - } - return n, err -} - -func (r *flateReadWrapper) Close() error { - if r.fr == nil { - return io.ErrClosedPipe - } - err := r.fr.Close() - flateReaderPool.Put(r.fr) - r.fr = nil - return err -} diff --git a/websocket/compression_test.go b/websocket/compression_test.go deleted file mode 100644 index 659cf4215fdbbd3100ce9c94f2c46dfb0a7537dd..0000000000000000000000000000000000000000 --- a/websocket/compression_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package websocket - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "testing" -) - -type nopCloser struct{ io.Writer } - -func (nopCloser) Close() error { return nil } - -func TestTruncWriter(t *testing.T) { - const data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijlkmnopqrstuvwxyz987654321" - for n := 1; n <= 10; n++ { - var b bytes.Buffer - w := &truncWriter{w: nopCloser{&b}} - p := []byte(data) - for len(p) > 0 { - m := len(p) - if m > n { - m = n - } - w.Write(p[:m]) - p = p[m:] - } - if b.String() != data[:len(data)-len(w.p)] { - t.Errorf("%d: %q", n, b.String()) - } - } -} - -func textMessages(num int) [][]byte { - messages := make([][]byte, num) - for i := 0; i < num; i++ { - msg := fmt.Sprintf("planet: %d, country: %d, city: %d, street: %d", i, i, i, i) - messages[i] = []byte(msg) - } - return messages -} - -func BenchmarkWriteNoCompression(b *testing.B) { - w := ioutil.Discard - c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) - messages := textMessages(100) - b.ResetTimer() - for i := 0; i < b.N; i++ { - c.WriteMessage(TextMessage, messages[i%len(messages)]) - } - b.ReportAllocs() -} - -func BenchmarkWriteWithCompression(b *testing.B) { - w := ioutil.Discard - c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) - messages := textMessages(100) - c.enableWriteCompression = true - c.newCompressionWriter = compressNoContextTakeover - b.ResetTimer() - for i := 0; i < b.N; i++ { - c.WriteMessage(TextMessage, messages[i%len(messages)]) - } - b.ReportAllocs() -} - -func TestValidCompressionLevel(t *testing.T) { - c := newConn(fakeNetConn{}, false, 1024, 1024) - for _, level := range []int{minCompressionLevel - 1, maxCompressionLevel + 1} { - if err := c.SetCompressionLevel(level); err == nil { - t.Errorf("no error for level %d", level) - } - } - for _, level := range []int{minCompressionLevel, maxCompressionLevel} { - if err := c.SetCompressionLevel(level); err != nil { - t.Errorf("error for level %d", level) - } - } -} diff --git a/websocket/conn.go b/websocket/conn.go deleted file mode 100644 index 97e1dbacb1237677ceece110c3e9a0a45068f49f..0000000000000000000000000000000000000000 --- a/websocket/conn.go +++ /dev/null @@ -1,1149 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bufio" - "encoding/binary" - "errors" - "io" - "io/ioutil" - "math/rand" - "net" - "strconv" - "sync" - "time" - "unicode/utf8" -) - -const ( - // Frame header byte 0 bits from Section 5.2 of RFC 6455 - finalBit = 1 << 7 - rsv1Bit = 1 << 6 - rsv2Bit = 1 << 5 - rsv3Bit = 1 << 4 - - // Frame header byte 1 bits from Section 5.2 of RFC 6455 - maskBit = 1 << 7 - - maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask - maxControlFramePayloadSize = 125 - - writeWait = time.Second - - defaultReadBufferSize = 4096 - defaultWriteBufferSize = 4096 - - continuationFrame = 0 - noFrame = -1 -) - -// Close codes defined in RFC 6455, section 11.7. -const ( - CloseNormalClosure = 1000 - CloseGoingAway = 1001 - CloseProtocolError = 1002 - CloseUnsupportedData = 1003 - CloseNoStatusReceived = 1005 - CloseAbnormalClosure = 1006 - CloseInvalidFramePayloadData = 1007 - ClosePolicyViolation = 1008 - CloseMessageTooBig = 1009 - CloseMandatoryExtension = 1010 - CloseInternalServerErr = 1011 - CloseServiceRestart = 1012 - CloseTryAgainLater = 1013 - CloseTLSHandshake = 1015 -) - -// The message types are defined in RFC 6455, section 11.8. -const ( - // TextMessage denotes a text data message. The text message payload is - // interpreted as UTF-8 encoded text data. - TextMessage = 1 - - // BinaryMessage denotes a binary data message. - BinaryMessage = 2 - - // CloseMessage denotes a close control message. The optional message - // payload contains a numeric code and text. Use the FormatCloseMessage - // function to format a close message payload. - CloseMessage = 8 - - // PingMessage denotes a ping control message. The optional message payload - // is UTF-8 encoded text. - PingMessage = 9 - - // PongMessage denotes a ping control message. The optional message payload - // is UTF-8 encoded text. - PongMessage = 10 -) - -// ErrCloseSent is returned when the application writes a message to the -// connection after sending a close message. -var ErrCloseSent = errors.New("websocket: close sent") - -// ErrReadLimit is returned when reading a message that is larger than the -// read limit set for the connection. -var ErrReadLimit = errors.New("websocket: read limit exceeded") - -// netError satisfies the net Error interface. -type netError struct { - msg string - temporary bool - timeout bool -} - -func (e *netError) Error() string { return e.msg } -func (e *netError) Temporary() bool { return e.temporary } -func (e *netError) Timeout() bool { return e.timeout } - -// CloseError represents close frame. -type CloseError struct { - - // Code is defined in RFC 6455, section 11.7. - Code int - - // Text is the optional text payload. - Text string -} - -func (e *CloseError) Error() string { - s := []byte("websocket: close ") - s = strconv.AppendInt(s, int64(e.Code), 10) - switch e.Code { - case CloseNormalClosure: - s = append(s, " (normal)"...) - case CloseGoingAway: - s = append(s, " (going away)"...) - case CloseProtocolError: - s = append(s, " (protocol error)"...) - case CloseUnsupportedData: - s = append(s, " (unsupported data)"...) - case CloseNoStatusReceived: - s = append(s, " (no status)"...) - case CloseAbnormalClosure: - s = append(s, " (abnormal closure)"...) - case CloseInvalidFramePayloadData: - s = append(s, " (invalid payload data)"...) - case ClosePolicyViolation: - s = append(s, " (policy violation)"...) - case CloseMessageTooBig: - s = append(s, " (message too big)"...) - case CloseMandatoryExtension: - s = append(s, " (mandatory extension missing)"...) - case CloseInternalServerErr: - s = append(s, " (internal server error)"...) - case CloseTLSHandshake: - s = append(s, " (TLS handshake error)"...) - } - if e.Text != "" { - s = append(s, ": "...) - s = append(s, e.Text...) - } - return string(s) -} - -// IsCloseError returns boolean indicating whether the error is a *CloseError -// with one of the specified codes. -func IsCloseError(err error, codes ...int) bool { - if e, ok := err.(*CloseError); ok { - for _, code := range codes { - if e.Code == code { - return true - } - } - } - return false -} - -// IsUnexpectedCloseError returns boolean indicating whether the error is a -// *CloseError with a code not in the list of expected codes. -func IsUnexpectedCloseError(err error, expectedCodes ...int) bool { - if e, ok := err.(*CloseError); ok { - for _, code := range expectedCodes { - if e.Code == code { - return false - } - } - return true - } - return false -} - -var ( - errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true} - errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()} - errBadWriteOpCode = errors.New("websocket: bad write message type") - errWriteClosed = errors.New("websocket: write closed") - errInvalidControlFrame = errors.New("websocket: invalid control frame") -) - -func newMaskKey() [4]byte { - n := rand.Uint32() - return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} -} - -func hideTempErr(err error) error { - if e, ok := err.(net.Error); ok && e.Temporary() { - err = &netError{msg: e.Error(), timeout: e.Timeout()} - } - return err -} - -func isControl(frameType int) bool { - return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage -} - -func isData(frameType int) bool { - return frameType == TextMessage || frameType == BinaryMessage -} - -var validReceivedCloseCodes = map[int]bool{ - // see http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number - - CloseNormalClosure: true, - CloseGoingAway: true, - CloseProtocolError: true, - CloseUnsupportedData: true, - CloseNoStatusReceived: false, - CloseAbnormalClosure: false, - CloseInvalidFramePayloadData: true, - ClosePolicyViolation: true, - CloseMessageTooBig: true, - CloseMandatoryExtension: true, - CloseInternalServerErr: true, - CloseServiceRestart: true, - CloseTryAgainLater: true, - CloseTLSHandshake: false, -} - -func isValidReceivedCloseCode(code int) bool { - return validReceivedCloseCodes[code] || (code >= 3000 && code <= 4999) -} - -// The Conn type represents a WebSocket connection. -type Conn struct { - conn net.Conn - isServer bool - subprotocol string - - // Write fields - mu chan bool // used as mutex to protect write to conn - writeBuf []byte // frame is constructed in this buffer. - writeDeadline time.Time - writer io.WriteCloser // the current writer returned to the application - isWriting bool // for best-effort concurrent write detection - - writeErrMu sync.Mutex - writeErr error - - enableWriteCompression bool - compressionLevel int - newCompressionWriter func(io.WriteCloser, int) io.WriteCloser - - // Read fields - reader io.ReadCloser // the current reader returned to the application - readErr error - br *bufio.Reader - readRemaining int64 // bytes remaining in current frame. - readFinal bool // true the current message has more frames. - readLength int64 // Message size. - readLimit int64 // Maximum message size. - readMaskPos int - readMaskKey [4]byte - handlePong func(string) error - handlePing func(string) error - handleClose func(int, string) error - readErrCount int - messageReader *messageReader // the current low-level reader - - readDecompress bool // whether last read frame had RSV1 set - newDecompressionReader func(io.Reader) io.ReadCloser -} - -func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { - return newConnBRW(conn, isServer, readBufferSize, writeBufferSize, nil) -} - -type writeHook struct { - p []byte -} - -func (wh *writeHook) Write(p []byte) (int, error) { - wh.p = p - return len(p), nil -} - -func newConnBRW(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, brw *bufio.ReadWriter) *Conn { - mu := make(chan bool, 1) - mu <- true - - var br *bufio.Reader - if readBufferSize == 0 && brw != nil && brw.Reader != nil { - // Reuse the supplied bufio.Reader if the buffer has a useful size. - // This code assumes that peek on a reader returns - // bufio.Reader.buf[:0]. - brw.Reader.Reset(conn) - if p, err := brw.Reader.Peek(0); err == nil && cap(p) >= 256 { - br = brw.Reader - } - } - if br == nil { - if readBufferSize == 0 { - readBufferSize = defaultReadBufferSize - } - if readBufferSize < maxControlFramePayloadSize { - readBufferSize = maxControlFramePayloadSize - } - br = bufio.NewReaderSize(conn, readBufferSize) - } - - var writeBuf []byte - if writeBufferSize == 0 && brw != nil && brw.Writer != nil { - // Use the bufio.Writer's buffer if the buffer has a useful size. This - // code assumes that bufio.Writer.buf[:1] is passed to the - // bufio.Writer's underlying writer. - var wh writeHook - brw.Writer.Reset(&wh) - brw.Writer.WriteByte(0) - brw.Flush() - if cap(wh.p) >= maxFrameHeaderSize+256 { - writeBuf = wh.p[:cap(wh.p)] - } - } - - if writeBuf == nil { - if writeBufferSize == 0 { - writeBufferSize = defaultWriteBufferSize - } - writeBuf = make([]byte, writeBufferSize+maxFrameHeaderSize) - } - - c := &Conn{ - isServer: isServer, - br: br, - conn: conn, - mu: mu, - readFinal: true, - writeBuf: writeBuf, - enableWriteCompression: true, - compressionLevel: defaultCompressionLevel, - } - c.SetCloseHandler(nil) - c.SetPingHandler(nil) - c.SetPongHandler(nil) - return c -} - -// Subprotocol returns the negotiated protocol for the connection. -func (c *Conn) Subprotocol() string { - return c.subprotocol -} - -// Close closes the underlying network connection without sending or waiting for a close frame. -func (c *Conn) Close() error { - return c.conn.Close() -} - -// LocalAddr returns the local network address. -func (c *Conn) LocalAddr() net.Addr { - return c.conn.LocalAddr() -} - -// RemoteAddr returns the remote network address. -func (c *Conn) RemoteAddr() net.Addr { - return c.conn.RemoteAddr() -} - -// Write methods - -func (c *Conn) writeFatal(err error) error { - err = hideTempErr(err) - c.writeErrMu.Lock() - if c.writeErr == nil { - c.writeErr = err - } - c.writeErrMu.Unlock() - return err -} - -func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error { - <-c.mu - defer func() { c.mu <- true }() - - c.writeErrMu.Lock() - err := c.writeErr - c.writeErrMu.Unlock() - if err != nil { - return err - } - - c.conn.SetWriteDeadline(deadline) - for _, buf := range bufs { - if len(buf) > 0 { - _, err := c.conn.Write(buf) - if err != nil { - return c.writeFatal(err) - } - } - } - - if frameType == CloseMessage { - c.writeFatal(ErrCloseSent) - } - return nil -} - -// WriteControl writes a control message with the given deadline. The allowed -// message types are CloseMessage, PingMessage and PongMessage. -func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error { - if !isControl(messageType) { - return errBadWriteOpCode - } - if len(data) > maxControlFramePayloadSize { - return errInvalidControlFrame - } - - b0 := byte(messageType) | finalBit - b1 := byte(len(data)) - if !c.isServer { - b1 |= maskBit - } - - buf := make([]byte, 0, maxFrameHeaderSize+maxControlFramePayloadSize) - buf = append(buf, b0, b1) - - if c.isServer { - buf = append(buf, data...) - } else { - key := newMaskKey() - buf = append(buf, key[:]...) - buf = append(buf, data...) - maskBytes(key, 0, buf[6:]) - } - - d := time.Hour * 1000 - if !deadline.IsZero() { - d = deadline.Sub(time.Now()) - if d < 0 { - return errWriteTimeout - } - } - - timer := time.NewTimer(d) - select { - case <-c.mu: - timer.Stop() - case <-timer.C: - return errWriteTimeout - } - defer func() { c.mu <- true }() - - c.writeErrMu.Lock() - err := c.writeErr - c.writeErrMu.Unlock() - if err != nil { - return err - } - - c.conn.SetWriteDeadline(deadline) - _, err = c.conn.Write(buf) - if err != nil { - return c.writeFatal(err) - } - if messageType == CloseMessage { - c.writeFatal(ErrCloseSent) - } - return err -} - -func (c *Conn) prepWrite(messageType int) error { - // Close previous writer if not already closed by the application. It's - // probably better to return an error in this situation, but we cannot - // change this without breaking existing applications. - if c.writer != nil { - c.writer.Close() - c.writer = nil - } - - if !isControl(messageType) && !isData(messageType) { - return errBadWriteOpCode - } - - c.writeErrMu.Lock() - err := c.writeErr - c.writeErrMu.Unlock() - return err -} - -// NextWriter returns a writer for the next message to send. The writer's Close -// method flushes the complete message to the network. -// -// There can be at most one open writer on a connection. NextWriter closes the -// previous writer if the application has not already done so. -func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { - if err := c.prepWrite(messageType); err != nil { - return nil, err - } - - mw := &messageWriter{ - c: c, - frameType: messageType, - pos: maxFrameHeaderSize, - } - c.writer = mw - if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { - w := c.newCompressionWriter(c.writer, c.compressionLevel) - mw.compress = true - c.writer = w - } - return c.writer, nil -} - -type messageWriter struct { - c *Conn - compress bool // whether next call to flushFrame should set RSV1 - pos int // end of data in writeBuf. - frameType int // type of the current frame. - err error -} - -func (w *messageWriter) fatal(err error) error { - if w.err != nil { - w.err = err - w.c.writer = nil - } - return err -} - -// flushFrame writes buffered data and extra as a frame to the network. The -// final argument indicates that this is the last frame in the message. -func (w *messageWriter) flushFrame(final bool, extra []byte) error { - c := w.c - length := w.pos - maxFrameHeaderSize + len(extra) - - // Check for invalid control frames. - if isControl(w.frameType) && - (!final || length > maxControlFramePayloadSize) { - return w.fatal(errInvalidControlFrame) - } - - b0 := byte(w.frameType) - if final { - b0 |= finalBit - } - if w.compress { - b0 |= rsv1Bit - } - w.compress = false - - b1 := byte(0) - if !c.isServer { - b1 |= maskBit - } - - // Assume that the frame starts at beginning of c.writeBuf. - framePos := 0 - if c.isServer { - // Adjust up if mask not included in the header. - framePos = 4 - } - - switch { - case length >= 65536: - c.writeBuf[framePos] = b0 - c.writeBuf[framePos+1] = b1 | 127 - binary.BigEndian.PutUint64(c.writeBuf[framePos+2:], uint64(length)) - case length > 125: - framePos += 6 - c.writeBuf[framePos] = b0 - c.writeBuf[framePos+1] = b1 | 126 - binary.BigEndian.PutUint16(c.writeBuf[framePos+2:], uint16(length)) - default: - framePos += 8 - c.writeBuf[framePos] = b0 - c.writeBuf[framePos+1] = b1 | byte(length) - } - - if !c.isServer { - key := newMaskKey() - copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) - maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos]) - if len(extra) > 0 { - return c.writeFatal(errors.New("websocket: internal error, extra used in client mode")) - } - } - - // Write the buffers to the connection with best-effort detection of - // concurrent writes. See the concurrency section in the package - // documentation for more info. - - if c.isWriting { - panic("concurrent write to websocket connection") - } - c.isWriting = true - - err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra) - - if !c.isWriting { - panic("concurrent write to websocket connection") - } - c.isWriting = false - - if err != nil { - return w.fatal(err) - } - - if final { - c.writer = nil - return nil - } - - // Setup for next frame. - w.pos = maxFrameHeaderSize - w.frameType = continuationFrame - return nil -} - -func (w *messageWriter) ncopy(max int) (int, error) { - n := len(w.c.writeBuf) - w.pos - if n <= 0 { - if err := w.flushFrame(false, nil); err != nil { - return 0, err - } - n = len(w.c.writeBuf) - w.pos - } - if n > max { - n = max - } - return n, nil -} - -func (w *messageWriter) Write(p []byte) (int, error) { - if w.err != nil { - return 0, w.err - } - - if len(p) > 2*len(w.c.writeBuf) && w.c.isServer { - // Don't buffer large messages. - err := w.flushFrame(false, p) - if err != nil { - return 0, err - } - return len(p), nil - } - - nn := len(p) - for len(p) > 0 { - n, err := w.ncopy(len(p)) - if err != nil { - return 0, err - } - copy(w.c.writeBuf[w.pos:], p[:n]) - w.pos += n - p = p[n:] - } - return nn, nil -} - -func (w *messageWriter) WriteString(p string) (int, error) { - if w.err != nil { - return 0, w.err - } - - nn := len(p) - for len(p) > 0 { - n, err := w.ncopy(len(p)) - if err != nil { - return 0, err - } - copy(w.c.writeBuf[w.pos:], p[:n]) - w.pos += n - p = p[n:] - } - return nn, nil -} - -func (w *messageWriter) ReadFrom(r io.Reader) (nn int64, err error) { - if w.err != nil { - return 0, w.err - } - for { - if w.pos == len(w.c.writeBuf) { - err = w.flushFrame(false, nil) - if err != nil { - break - } - } - var n int - n, err = r.Read(w.c.writeBuf[w.pos:]) - w.pos += n - nn += int64(n) - if err != nil { - if err == io.EOF { - err = nil - } - break - } - } - return nn, err -} - -func (w *messageWriter) Close() error { - if w.err != nil { - return w.err - } - if err := w.flushFrame(true, nil); err != nil { - return err - } - w.err = errWriteClosed - return nil -} - -// WritePreparedMessage writes prepared message into connection. -func (c *Conn) WritePreparedMessage(pm *PreparedMessage) error { - frameType, frameData, err := pm.frame(prepareKey{ - isServer: c.isServer, - compress: c.newCompressionWriter != nil && c.enableWriteCompression && isData(pm.messageType), - compressionLevel: c.compressionLevel, - }) - if err != nil { - return err - } - if c.isWriting { - panic("concurrent write to websocket connection") - } - c.isWriting = true - err = c.write(frameType, c.writeDeadline, frameData, nil) - if !c.isWriting { - panic("concurrent write to websocket connection") - } - c.isWriting = false - return err -} - -// WriteMessage is a helper method for getting a writer using NextWriter, -// writing the message and closing the writer. -func (c *Conn) WriteMessage(messageType int, data []byte) error { - - if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) { - // Fast path with no allocations and single frame. - - if err := c.prepWrite(messageType); err != nil { - return err - } - mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize} - n := copy(c.writeBuf[mw.pos:], data) - mw.pos += n - data = data[n:] - return mw.flushFrame(true, data) - } - - w, err := c.NextWriter(messageType) - if err != nil { - return err - } - if _, err = w.Write(data); err != nil { - return err - } - return w.Close() -} - -// SetWriteDeadline sets the write deadline on the underlying network -// connection. After a write has timed out, the websocket state is corrupt and -// all future writes will return an error. A zero value for t means writes will -// not time out. -func (c *Conn) SetWriteDeadline(t time.Time) error { - c.writeDeadline = t - return nil -} - -// Read methods - -func (c *Conn) advanceFrame() (int, error) { - - // 1. Skip remainder of previous frame. - - if c.readRemaining > 0 { - if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { - return noFrame, err - } - } - - // 2. Read and parse first two bytes of frame header. - - p, err := c.read(2) - if err != nil { - return noFrame, err - } - - final := p[0]&finalBit != 0 - frameType := int(p[0] & 0xf) - mask := p[1]&maskBit != 0 - c.readRemaining = int64(p[1] & 0x7f) - - c.readDecompress = false - if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 { - c.readDecompress = true - p[0] &^= rsv1Bit - } - - if rsv := p[0] & (rsv1Bit | rsv2Bit | rsv3Bit); rsv != 0 { - return noFrame, c.handleProtocolError("unexpected reserved bits 0x" + strconv.FormatInt(int64(rsv), 16)) - } - - switch frameType { - case CloseMessage, PingMessage, PongMessage: - if c.readRemaining > maxControlFramePayloadSize { - return noFrame, c.handleProtocolError("control frame length > 125") - } - if !final { - return noFrame, c.handleProtocolError("control frame not final") - } - case TextMessage, BinaryMessage: - if !c.readFinal { - return noFrame, c.handleProtocolError("message start before final message frame") - } - c.readFinal = final - case continuationFrame: - if c.readFinal { - return noFrame, c.handleProtocolError("continuation after final message frame") - } - c.readFinal = final - default: - return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType)) - } - - // 3. Read and parse frame length. - - switch c.readRemaining { - case 126: - p, err := c.read(2) - if err != nil { - return noFrame, err - } - c.readRemaining = int64(binary.BigEndian.Uint16(p)) - case 127: - p, err := c.read(8) - if err != nil { - return noFrame, err - } - c.readRemaining = int64(binary.BigEndian.Uint64(p)) - } - - // 4. Handle frame masking. - - if mask != c.isServer { - return noFrame, c.handleProtocolError("incorrect mask flag") - } - - if mask { - c.readMaskPos = 0 - p, err := c.read(len(c.readMaskKey)) - if err != nil { - return noFrame, err - } - copy(c.readMaskKey[:], p) - } - - // 5. For text and binary messages, enforce read limit and return. - - if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage { - - c.readLength += c.readRemaining - if c.readLimit > 0 && c.readLength > c.readLimit { - c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) - return noFrame, ErrReadLimit - } - - return frameType, nil - } - - // 6. Read control frame payload. - - var payload []byte - if c.readRemaining > 0 { - payload, err = c.read(int(c.readRemaining)) - c.readRemaining = 0 - if err != nil { - return noFrame, err - } - if c.isServer { - maskBytes(c.readMaskKey, 0, payload) - } - } - - // 7. Process control frame payload. - - switch frameType { - case PongMessage: - if err := c.handlePong(string(payload)); err != nil { - return noFrame, err - } - case PingMessage: - if err := c.handlePing(string(payload)); err != nil { - return noFrame, err - } - case CloseMessage: - closeCode := CloseNoStatusReceived - closeText := "" - if len(payload) >= 2 { - closeCode = int(binary.BigEndian.Uint16(payload)) - if !isValidReceivedCloseCode(closeCode) { - return noFrame, c.handleProtocolError("invalid close code") - } - closeText = string(payload[2:]) - if !utf8.ValidString(closeText) { - return noFrame, c.handleProtocolError("invalid utf8 payload in close frame") - } - } - if err := c.handleClose(closeCode, closeText); err != nil { - return noFrame, err - } - return noFrame, &CloseError{Code: closeCode, Text: closeText} - } - - return frameType, nil -} - -func (c *Conn) handleProtocolError(message string) error { - c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait)) - return errors.New("websocket: " + message) -} - -// NextReader returns the next data message received from the peer. The -// returned messageType is either TextMessage or BinaryMessage. -// -// There can be at most one open reader on a connection. NextReader discards -// the previous message if the application has not already consumed it. -// -// Applications must break out of the application's read loop when this method -// returns a non-nil error value. Errors returned from this method are -// permanent. Once this method returns a non-nil error, all subsequent calls to -// this method return the same error. -func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { - // Close previous reader, only relevant for decompression. - if c.reader != nil { - c.reader.Close() - c.reader = nil - } - - c.messageReader = nil - c.readLength = 0 - - for c.readErr == nil { - frameType, err := c.advanceFrame() - if err != nil { - c.readErr = hideTempErr(err) - break - } - if frameType == TextMessage || frameType == BinaryMessage { - c.messageReader = &messageReader{c} - c.reader = c.messageReader - if c.readDecompress { - c.reader = c.newDecompressionReader(c.reader) - } - return frameType, c.reader, nil - } - } - - // Applications that do handle the error returned from this method spin in - // tight loop on connection failure. To help application developers detect - // this error, panic on repeated reads to the failed connection. - c.readErrCount++ - if c.readErrCount >= 1000 { - panic("repeated read on failed websocket connection") - } - - return noFrame, nil, c.readErr -} - -type messageReader struct{ c *Conn } - -func (r *messageReader) Read(b []byte) (int, error) { - c := r.c - if c.messageReader != r { - return 0, io.EOF - } - - for c.readErr == nil { - - if c.readRemaining > 0 { - if int64(len(b)) > c.readRemaining { - b = b[:c.readRemaining] - } - n, err := c.br.Read(b) - c.readErr = hideTempErr(err) - if c.isServer { - c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) - } - c.readRemaining -= int64(n) - if c.readRemaining > 0 && c.readErr == io.EOF { - c.readErr = errUnexpectedEOF - } - return n, c.readErr - } - - if c.readFinal { - c.messageReader = nil - return 0, io.EOF - } - - frameType, err := c.advanceFrame() - switch { - case err != nil: - c.readErr = hideTempErr(err) - case frameType == TextMessage || frameType == BinaryMessage: - c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") - } - } - - err := c.readErr - if err == io.EOF && c.messageReader == r { - err = errUnexpectedEOF - } - return 0, err -} - -func (r *messageReader) Close() error { - return nil -} - -// ReadMessage is a helper method for getting a reader using NextReader and -// reading from that reader to a buffer. -func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { - var r io.Reader - messageType, r, err = c.NextReader() - if err != nil { - return messageType, nil, err - } - p, err = ioutil.ReadAll(r) - return messageType, p, err -} - -// SetReadDeadline sets the read deadline on the underlying network connection. -// After a read has timed out, the websocket connection state is corrupt and -// all future reads will return an error. A zero value for t means reads will -// not time out. -func (c *Conn) SetReadDeadline(t time.Time) error { - return c.conn.SetReadDeadline(t) -} - -// SetReadLimit sets the maximum size for a message read from the peer. If a -// message exceeds the limit, the connection sends a close frame to the peer -// and returns ErrReadLimit to the application. -func (c *Conn) SetReadLimit(limit int64) { - c.readLimit = limit -} - -// CloseHandler returns the current close handler -func (c *Conn) CloseHandler() func(code int, text string) error { - return c.handleClose -} - -// SetCloseHandler sets the handler for close messages received from the peer. -// The code argument to h is the received close code or CloseNoStatusReceived -// if the close message is empty. The default close handler sends a close frame -// back to the peer. -// -// The application must read the connection to process close messages as -// described in the section on Control Frames above. -// -// The connection read methods return a CloseError when a close frame is -// received. Most applications should handle close messages as part of their -// normal error handling. Applications should only set a close handler when the -// application must perform some action before sending a close frame back to -// the peer. -func (c *Conn) SetCloseHandler(h func(code int, text string) error) { - if h == nil { - h = func(code int, text string) error { - message := []byte{} - if code != CloseNoStatusReceived { - message = FormatCloseMessage(code, "") - } - c.WriteControl(CloseMessage, message, time.Now().Add(writeWait)) - return nil - } - } - c.handleClose = h -} - -// PingHandler returns the current ping handler -func (c *Conn) PingHandler() func(appData string) error { - return c.handlePing -} - -// SetPingHandler sets the handler for ping messages received from the peer. -// The appData argument to h is the PING frame application data. The default -// ping handler sends a pong to the peer. -// -// The application must read the connection to process ping messages as -// described in the section on Control Frames above. -func (c *Conn) SetPingHandler(h func(appData string) error) { - if h == nil { - h = func(message string) error { - err := c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) - if err == ErrCloseSent { - return nil - } else if e, ok := err.(net.Error); ok && e.Temporary() { - return nil - } - return err - } - } - c.handlePing = h -} - -// PongHandler returns the current pong handler -func (c *Conn) PongHandler() func(appData string) error { - return c.handlePong -} - -// SetPongHandler sets the handler for pong messages received from the peer. -// The appData argument to h is the PONG frame application data. The default -// pong handler does nothing. -// -// The application must read the connection to process ping messages as -// described in the section on Control Frames above. -func (c *Conn) SetPongHandler(h func(appData string) error) { - if h == nil { - h = func(string) error { return nil } - } - c.handlePong = h -} - -// UnderlyingConn returns the internal net.Conn. This can be used to further -// modifications to connection specific flags. -func (c *Conn) UnderlyingConn() net.Conn { - return c.conn -} - -// EnableWriteCompression enables and disables write compression of -// subsequent text and binary messages. This function is a noop if -// compression was not negotiated with the peer. -func (c *Conn) EnableWriteCompression(enable bool) { - c.enableWriteCompression = enable -} - -// SetCompressionLevel sets the flate compression level for subsequent text and -// binary messages. This function is a noop if compression was not negotiated -// with the peer. See the compress/flate package for a description of -// compression levels. -func (c *Conn) SetCompressionLevel(level int) error { - if !isValidCompressionLevel(level) { - return errors.New("websocket: invalid compression level") - } - c.compressionLevel = level - return nil -} - -// FormatCloseMessage formats closeCode and text as a WebSocket close message. -func FormatCloseMessage(closeCode int, text string) []byte { - buf := make([]byte, 2+len(text)) - binary.BigEndian.PutUint16(buf, uint16(closeCode)) - copy(buf[2:], text) - return buf -} diff --git a/websocket/conn_broadcast_test.go b/websocket/conn_broadcast_test.go deleted file mode 100644 index 45038e4881ff909dd0812a9deea4fec16ce5d2d4..0000000000000000000000000000000000000000 --- a/websocket/conn_broadcast_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.7 - -package websocket - -import ( - "io" - "io/ioutil" - "sync/atomic" - "testing" -) - -// broadcastBench allows to run broadcast benchmarks. -// In every broadcast benchmark we create many connections, then send the same -// message into every connection and wait for all writes complete. This emulates -// an application where many connections listen to the same data - i.e. PUB/SUB -// scenarios with many subscribers in one channel. -type broadcastBench struct { - w io.Writer - message *broadcastMessage - closeCh chan struct{} - doneCh chan struct{} - count int32 - conns []*broadcastConn - compression bool - usePrepared bool -} - -type broadcastMessage struct { - payload []byte - prepared *PreparedMessage -} - -type broadcastConn struct { - conn *Conn - msgCh chan *broadcastMessage -} - -func newBroadcastConn(c *Conn) *broadcastConn { - return &broadcastConn{ - conn: c, - msgCh: make(chan *broadcastMessage, 1), - } -} - -func newBroadcastBench(usePrepared, compression bool) *broadcastBench { - bench := &broadcastBench{ - w: ioutil.Discard, - doneCh: make(chan struct{}), - closeCh: make(chan struct{}), - usePrepared: usePrepared, - compression: compression, - } - msg := &broadcastMessage{ - payload: textMessages(1)[0], - } - if usePrepared { - pm, _ := NewPreparedMessage(TextMessage, msg.payload) - msg.prepared = pm - } - bench.message = msg - bench.makeConns(10000) - return bench -} - -func (b *broadcastBench) makeConns(numConns int) { - conns := make([]*broadcastConn, numConns) - - for i := 0; i < numConns; i++ { - c := newConn(fakeNetConn{Reader: nil, Writer: b.w}, true, 1024, 1024) - if b.compression { - c.enableWriteCompression = true - c.newCompressionWriter = compressNoContextTakeover - } - conns[i] = newBroadcastConn(c) - go func(c *broadcastConn) { - for { - select { - case msg := <-c.msgCh: - if b.usePrepared { - c.conn.WritePreparedMessage(msg.prepared) - } else { - c.conn.WriteMessage(TextMessage, msg.payload) - } - val := atomic.AddInt32(&b.count, 1) - if val%int32(numConns) == 0 { - b.doneCh <- struct{}{} - } - case <-b.closeCh: - return - } - } - }(conns[i]) - } - b.conns = conns -} - -func (b *broadcastBench) close() { - close(b.closeCh) -} - -func (b *broadcastBench) runOnce() { - for _, c := range b.conns { - c.msgCh <- b.message - } - <-b.doneCh -} - -func BenchmarkBroadcast(b *testing.B) { - benchmarks := []struct { - name string - usePrepared bool - compression bool - }{ - {"NoCompression", false, false}, - {"WithCompression", false, true}, - {"NoCompressionPrepared", true, false}, - {"WithCompressionPrepared", true, true}, - } - for _, bm := range benchmarks { - b.Run(bm.name, func(b *testing.B) { - bench := newBroadcastBench(bm.usePrepared, bm.compression) - defer bench.close() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bench.runOnce() - } - b.ReportAllocs() - }) - } -} diff --git a/websocket/conn_read.go b/websocket/conn_read.go deleted file mode 100644 index 1ea15059ee14b675f1ee138a0f3c9df24c727be1..0000000000000000000000000000000000000000 --- a/websocket/conn_read.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.5 - -package websocket - -import "io" - -func (c *Conn) read(n int) ([]byte, error) { - p, err := c.br.Peek(n) - if err == io.EOF { - err = errUnexpectedEOF - } - c.br.Discard(len(p)) - return p, err -} diff --git a/websocket/conn_read_legacy.go b/websocket/conn_read_legacy.go deleted file mode 100644 index 018541cf6cbb2d22d86df0e35ad9db30f7f63689..0000000000000000000000000000000000000000 --- a/websocket/conn_read_legacy.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.5 - -package websocket - -import "io" - -func (c *Conn) read(n int) ([]byte, error) { - p, err := c.br.Peek(n) - if err == io.EOF { - err = errUnexpectedEOF - } - if len(p) > 0 { - // advance over the bytes just read - io.ReadFull(c.br, p) - } - return p, err -} diff --git a/websocket/conn_test.go b/websocket/conn_test.go deleted file mode 100644 index 06e9bc3f552b9e3103a35f385494e479ec50c993..0000000000000000000000000000000000000000 --- a/websocket/conn_test.go +++ /dev/null @@ -1,497 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "io/ioutil" - "net" - "reflect" - "testing" - "testing/iotest" - "time" -) - -var _ net.Error = errWriteTimeout - -type fakeNetConn struct { - io.Reader - io.Writer -} - -func (c fakeNetConn) Close() error { return nil } -func (c fakeNetConn) LocalAddr() net.Addr { return localAddr } -func (c fakeNetConn) RemoteAddr() net.Addr { return remoteAddr } -func (c fakeNetConn) SetDeadline(t time.Time) error { return nil } -func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil } -func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil } - -type fakeAddr int - -var ( - localAddr = fakeAddr(1) - remoteAddr = fakeAddr(2) -) - -func (a fakeAddr) Network() string { - return "net" -} - -func (a fakeAddr) String() string { - return "str" -} - -func TestFraming(t *testing.T) { - frameSizes := []int{0, 1, 2, 124, 125, 126, 127, 128, 129, 65534, 65535, 65536, 65537} - var readChunkers = []struct { - name string - f func(io.Reader) io.Reader - }{ - {"half", iotest.HalfReader}, - {"one", iotest.OneByteReader}, - {"asis", func(r io.Reader) io.Reader { return r }}, - } - writeBuf := make([]byte, 65537) - for i := range writeBuf { - writeBuf[i] = byte(i) - } - var writers = []struct { - name string - f func(w io.Writer, n int) (int, error) - }{ - {"iocopy", func(w io.Writer, n int) (int, error) { - nn, err := io.Copy(w, bytes.NewReader(writeBuf[:n])) - return int(nn), err - }}, - {"write", func(w io.Writer, n int) (int, error) { - return w.Write(writeBuf[:n]) - }}, - {"string", func(w io.Writer, n int) (int, error) { - return io.WriteString(w, string(writeBuf[:n])) - }}, - } - - for _, compress := range []bool{false, true} { - for _, isServer := range []bool{true, false} { - for _, chunker := range readChunkers { - - var connBuf bytes.Buffer - wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024) - rc := newConn(fakeNetConn{Reader: chunker.f(&connBuf), Writer: nil}, !isServer, 1024, 1024) - if compress { - wc.newCompressionWriter = compressNoContextTakeover - rc.newDecompressionReader = decompressNoContextTakeover - } - for _, n := range frameSizes { - for _, writer := range writers { - name := fmt.Sprintf("z:%v, s:%v, r:%s, n:%d w:%s", compress, isServer, chunker.name, n, writer.name) - - w, err := wc.NextWriter(TextMessage) - if err != nil { - t.Errorf("%s: wc.NextWriter() returned %v", name, err) - continue - } - nn, err := writer.f(w, n) - if err != nil || nn != n { - t.Errorf("%s: w.Write(writeBuf[:n]) returned %d, %v", name, nn, err) - continue - } - err = w.Close() - if err != nil { - t.Errorf("%s: w.Close() returned %v", name, err) - continue - } - - opCode, r, err := rc.NextReader() - if err != nil || opCode != TextMessage { - t.Errorf("%s: NextReader() returned %d, r, %v", name, opCode, err) - continue - } - rbuf, err := ioutil.ReadAll(r) - if err != nil { - t.Errorf("%s: ReadFull() returned rbuf, %v", name, err) - continue - } - - if len(rbuf) != n { - t.Errorf("%s: len(rbuf) is %d, want %d", name, len(rbuf), n) - continue - } - - for i, b := range rbuf { - if byte(i) != b { - t.Errorf("%s: bad byte at offset %d", name, i) - break - } - } - } - } - } - } - } -} - -func TestControl(t *testing.T) { - const message = "this is a ping/pong messsage" - for _, isServer := range []bool{true, false} { - for _, isWriteControl := range []bool{true, false} { - name := fmt.Sprintf("s:%v, wc:%v", isServer, isWriteControl) - var connBuf bytes.Buffer - wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024) - rc := newConn(fakeNetConn{Reader: &connBuf, Writer: nil}, !isServer, 1024, 1024) - if isWriteControl { - wc.WriteControl(PongMessage, []byte(message), time.Now().Add(time.Second)) - } else { - w, err := wc.NextWriter(PongMessage) - if err != nil { - t.Errorf("%s: wc.NextWriter() returned %v", name, err) - continue - } - if _, err := w.Write([]byte(message)); err != nil { - t.Errorf("%s: w.Write() returned %v", name, err) - continue - } - if err := w.Close(); err != nil { - t.Errorf("%s: w.Close() returned %v", name, err) - continue - } - var actualMessage string - rc.SetPongHandler(func(s string) error { actualMessage = s; return nil }) - rc.NextReader() - if actualMessage != message { - t.Errorf("%s: pong=%q, want %q", name, actualMessage, message) - continue - } - } - } - } -} - -func TestCloseFrameBeforeFinalMessageFrame(t *testing.T) { - const bufSize = 512 - - expectedErr := &CloseError{Code: CloseNormalClosure, Text: "hello"} - - var b1, b2 bytes.Buffer - wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize) - rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) - - w, _ := wc.NextWriter(BinaryMessage) - w.Write(make([]byte, bufSize+bufSize/2)) - wc.WriteControl(CloseMessage, FormatCloseMessage(expectedErr.Code, expectedErr.Text), time.Now().Add(10*time.Second)) - w.Close() - - op, r, err := rc.NextReader() - if op != BinaryMessage || err != nil { - t.Fatalf("NextReader() returned %d, %v", op, err) - } - _, err = io.Copy(ioutil.Discard, r) - if !reflect.DeepEqual(err, expectedErr) { - t.Fatalf("io.Copy() returned %v, want %v", err, expectedErr) - } - _, _, err = rc.NextReader() - if !reflect.DeepEqual(err, expectedErr) { - t.Fatalf("NextReader() returned %v, want %v", err, expectedErr) - } -} - -func TestEOFWithinFrame(t *testing.T) { - const bufSize = 64 - - for n := 0; ; n++ { - var b bytes.Buffer - wc := newConn(fakeNetConn{Reader: nil, Writer: &b}, false, 1024, 1024) - rc := newConn(fakeNetConn{Reader: &b, Writer: nil}, true, 1024, 1024) - - w, _ := wc.NextWriter(BinaryMessage) - w.Write(make([]byte, bufSize)) - w.Close() - - if n >= b.Len() { - break - } - b.Truncate(n) - - op, r, err := rc.NextReader() - if err == errUnexpectedEOF { - continue - } - if op != BinaryMessage || err != nil { - t.Fatalf("%d: NextReader() returned %d, %v", n, op, err) - } - _, err = io.Copy(ioutil.Discard, r) - if err != errUnexpectedEOF { - t.Fatalf("%d: io.Copy() returned %v, want %v", n, err, errUnexpectedEOF) - } - _, _, err = rc.NextReader() - if err != errUnexpectedEOF { - t.Fatalf("%d: NextReader() returned %v, want %v", n, err, errUnexpectedEOF) - } - } -} - -func TestEOFBeforeFinalFrame(t *testing.T) { - const bufSize = 512 - - var b1, b2 bytes.Buffer - wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize) - rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) - - w, _ := wc.NextWriter(BinaryMessage) - w.Write(make([]byte, bufSize+bufSize/2)) - - op, r, err := rc.NextReader() - if op != BinaryMessage || err != nil { - t.Fatalf("NextReader() returned %d, %v", op, err) - } - _, err = io.Copy(ioutil.Discard, r) - if err != errUnexpectedEOF { - t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF) - } - _, _, err = rc.NextReader() - if err != errUnexpectedEOF { - t.Fatalf("NextReader() returned %v, want %v", err, errUnexpectedEOF) - } -} - -func TestWriteAfterMessageWriterClose(t *testing.T) { - wc := newConn(fakeNetConn{Reader: nil, Writer: &bytes.Buffer{}}, false, 1024, 1024) - w, _ := wc.NextWriter(BinaryMessage) - io.WriteString(w, "hello") - if err := w.Close(); err != nil { - t.Fatalf("unxpected error closing message writer, %v", err) - } - - if _, err := io.WriteString(w, "world"); err == nil { - t.Fatalf("no error writing after close") - } - - w, _ = wc.NextWriter(BinaryMessage) - io.WriteString(w, "hello") - - // close w by getting next writer - _, err := wc.NextWriter(BinaryMessage) - if err != nil { - t.Fatalf("unexpected error getting next writer, %v", err) - } - - if _, err := io.WriteString(w, "world"); err == nil { - t.Fatalf("no error writing after close") - } -} - -func TestReadLimit(t *testing.T) { - - const readLimit = 512 - message := make([]byte, readLimit+1) - - var b1, b2 bytes.Buffer - wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, readLimit-2) - rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) - rc.SetReadLimit(readLimit) - - // Send message at the limit with interleaved pong. - w, _ := wc.NextWriter(BinaryMessage) - w.Write(message[:readLimit-1]) - wc.WriteControl(PongMessage, []byte("this is a pong"), time.Now().Add(10*time.Second)) - w.Write(message[:1]) - w.Close() - - // Send message larger than the limit. - wc.WriteMessage(BinaryMessage, message[:readLimit+1]) - - op, _, err := rc.NextReader() - if op != BinaryMessage || err != nil { - t.Fatalf("1: NextReader() returned %d, %v", op, err) - } - op, r, err := rc.NextReader() - if op != BinaryMessage || err != nil { - t.Fatalf("2: NextReader() returned %d, %v", op, err) - } - _, err = io.Copy(ioutil.Discard, r) - if err != ErrReadLimit { - t.Fatalf("io.Copy() returned %v", err) - } -} - -func TestAddrs(t *testing.T) { - c := newConn(&fakeNetConn{}, true, 1024, 1024) - if c.LocalAddr() != localAddr { - t.Errorf("LocalAddr = %v, want %v", c.LocalAddr(), localAddr) - } - if c.RemoteAddr() != remoteAddr { - t.Errorf("RemoteAddr = %v, want %v", c.RemoteAddr(), remoteAddr) - } -} - -func TestUnderlyingConn(t *testing.T) { - var b1, b2 bytes.Buffer - fc := fakeNetConn{Reader: &b1, Writer: &b2} - c := newConn(fc, true, 1024, 1024) - ul := c.UnderlyingConn() - if ul != fc { - t.Fatalf("Underlying conn is not what it should be.") - } -} - -func TestBufioReadBytes(t *testing.T) { - - // Test calling bufio.ReadBytes for value longer than read buffer size. - - m := make([]byte, 512) - m[len(m)-1] = '\n' - - var b1, b2 bytes.Buffer - wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, len(m)+64, len(m)+64) - rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, len(m)-64, len(m)-64) - - w, _ := wc.NextWriter(BinaryMessage) - w.Write(m) - w.Close() - - op, r, err := rc.NextReader() - if op != BinaryMessage || err != nil { - t.Fatalf("NextReader() returned %d, %v", op, err) - } - - br := bufio.NewReader(r) - p, err := br.ReadBytes('\n') - if err != nil { - t.Fatalf("ReadBytes() returned %v", err) - } - if len(p) != len(m) { - t.Fatalf("read returnd %d bytes, want %d bytes", len(p), len(m)) - } -} - -var closeErrorTests = []struct { - err error - codes []int - ok bool -}{ - {&CloseError{Code: CloseNormalClosure}, []int{CloseNormalClosure}, true}, - {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived}, false}, - {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived, CloseNormalClosure}, true}, - {errors.New("hello"), []int{CloseNormalClosure}, false}, -} - -func TestCloseError(t *testing.T) { - for _, tt := range closeErrorTests { - ok := IsCloseError(tt.err, tt.codes...) - if ok != tt.ok { - t.Errorf("IsCloseError(%#v, %#v) returned %v, want %v", tt.err, tt.codes, ok, tt.ok) - } - } -} - -var unexpectedCloseErrorTests = []struct { - err error - codes []int - ok bool -}{ - {&CloseError{Code: CloseNormalClosure}, []int{CloseNormalClosure}, false}, - {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived}, true}, - {&CloseError{Code: CloseNormalClosure}, []int{CloseNoStatusReceived, CloseNormalClosure}, false}, - {errors.New("hello"), []int{CloseNormalClosure}, false}, -} - -func TestUnexpectedCloseErrors(t *testing.T) { - for _, tt := range unexpectedCloseErrorTests { - ok := IsUnexpectedCloseError(tt.err, tt.codes...) - if ok != tt.ok { - t.Errorf("IsUnexpectedCloseError(%#v, %#v) returned %v, want %v", tt.err, tt.codes, ok, tt.ok) - } - } -} - -type blockingWriter struct { - c1, c2 chan struct{} -} - -func (w blockingWriter) Write(p []byte) (int, error) { - // Allow main to continue - close(w.c1) - // Wait for panic in main - <-w.c2 - return len(p), nil -} - -func TestConcurrentWritePanic(t *testing.T) { - w := blockingWriter{make(chan struct{}), make(chan struct{})} - c := newConn(fakeNetConn{Reader: nil, Writer: w}, false, 1024, 1024) - go func() { - c.WriteMessage(TextMessage, []byte{}) - }() - - // wait for goroutine to block in write. - <-w.c1 - - defer func() { - close(w.c2) - if v := recover(); v != nil { - return - } - }() - - c.WriteMessage(TextMessage, []byte{}) - t.Fatal("should not get here") -} - -type failingReader struct{} - -func (r failingReader) Read(p []byte) (int, error) { - return 0, io.EOF -} - -func TestFailedConnectionReadPanic(t *testing.T) { - c := newConn(fakeNetConn{Reader: failingReader{}, Writer: nil}, false, 1024, 1024) - - defer func() { - if v := recover(); v != nil { - return - } - }() - - for i := 0; i < 20000; i++ { - c.ReadMessage() - } - t.Fatal("should not get here") -} - -func TestBufioReuse(t *testing.T) { - brw := bufio.NewReadWriter(bufio.NewReader(nil), bufio.NewWriter(nil)) - c := newConnBRW(nil, false, 0, 0, brw) - - if c.br != brw.Reader { - t.Error("connection did not reuse bufio.Reader") - } - - var wh writeHook - brw.Writer.Reset(&wh) - brw.WriteByte(0) - brw.Flush() - if &c.writeBuf[0] != &wh.p[0] { - t.Error("connection did not reuse bufio.Writer") - } - - brw = bufio.NewReadWriter(bufio.NewReaderSize(nil, 0), bufio.NewWriterSize(nil, 0)) - c = newConnBRW(nil, false, 0, 0, brw) - - if c.br == brw.Reader { - t.Error("connection used bufio.Reader with small size") - } - - brw.Writer.Reset(&wh) - brw.WriteByte(0) - brw.Flush() - if &c.writeBuf[0] != &wh.p[0] { - t.Error("connection used bufio.Writer with small size") - } - -} diff --git a/websocket/doc.go b/websocket/doc.go deleted file mode 100644 index f5ff0823da773f6d389000c632d9d0fc0f4a753a..0000000000000000000000000000000000000000 --- a/websocket/doc.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package websocket implements the WebSocket protocol defined in RFC 6455. -// -// Overview -// -// The Conn type represents a WebSocket connection. A server application calls -// the Upgrader.Upgrade method from an HTTP request handler to get a *Conn: -// -// var upgrader = websocket.Upgrader{ -// ReadBufferSize: 1024, -// WriteBufferSize: 1024, -// } -// -// func handler(w http.ResponseWriter, r *http.Request) { -// conn, err := upgrader.Upgrade(w, r, nil) -// if err != nil { -// log.Println(err) -// return -// } -// ... Use conn to send and receive messages. -// } -// -// Call the connection's WriteMessage and ReadMessage methods to send and -// receive messages as a slice of bytes. This snippet of code shows how to echo -// messages using these methods: -// -// for { -// messageType, p, err := conn.ReadMessage() -// if err != nil { -// return -// } -// if err := conn.WriteMessage(messageType, p); err != nil { -// return err -// } -// } -// -// In above snippet of code, p is a []byte and messageType is an int with value -// websocket.BinaryMessage or websocket.TextMessage. -// -// An application can also send and receive messages using the io.WriteCloser -// and io.Reader interfaces. To send a message, call the connection NextWriter -// method to get an io.WriteCloser, write the message to the writer and close -// the writer when done. To receive a message, call the connection NextReader -// method to get an io.Reader and read until io.EOF is returned. This snippet -// shows how to echo messages using the NextWriter and NextReader methods: -// -// for { -// messageType, r, err := conn.NextReader() -// if err != nil { -// return -// } -// w, err := conn.NextWriter(messageType) -// if err != nil { -// return err -// } -// if _, err := io.Copy(w, r); err != nil { -// return err -// } -// if err := w.Close(); err != nil { -// return err -// } -// } -// -// Data Messages -// -// The WebSocket protocol distinguishes between text and binary data messages. -// Text messages are interpreted as UTF-8 encoded text. The interpretation of -// binary messages is left to the application. -// -// This package uses the TextMessage and BinaryMessage integer constants to -// identify the two data message types. The ReadMessage and NextReader methods -// return the type of the received message. The messageType argument to the -// WriteMessage and NextWriter methods specifies the type of a sent message. -// -// It is the application's responsibility to ensure that text messages are -// valid UTF-8 encoded text. -// -// Control Messages -// -// The WebSocket protocol defines three types of control messages: close, ping -// and pong. Call the connection WriteControl, WriteMessage or NextWriter -// methods to send a control message to the peer. -// -// Connections handle received close messages by sending a close message to the -// peer and returning a *CloseError from the the NextReader, ReadMessage or the -// message Read method. -// -// Connections handle received ping and pong messages by invoking callback -// functions set with SetPingHandler and SetPongHandler methods. The callback -// functions are called from the NextReader, ReadMessage and the message Read -// methods. -// -// The default ping handler sends a pong to the peer. The application's reading -// goroutine can block for a short time while the handler writes the pong data -// to the connection. -// -// The application must read the connection to process ping, pong and close -// messages sent from the peer. If the application is not otherwise interested -// in messages from the peer, then the application should start a goroutine to -// read and discard messages from the peer. A simple example is: -// -// func readLoop(c *websocket.Conn) { -// for { -// if _, _, err := c.NextReader(); err != nil { -// c.Close() -// break -// } -// } -// } -// -// Concurrency -// -// Connections support one concurrent reader and one concurrent writer. -// -// Applications are responsible for ensuring that no more than one goroutine -// calls the write methods (NextWriter, SetWriteDeadline, WriteMessage, -// WriteJSON, EnableWriteCompression, SetCompressionLevel) concurrently and -// that no more than one goroutine calls the read methods (NextReader, -// SetReadDeadline, ReadMessage, ReadJSON, SetPongHandler, SetPingHandler) -// concurrently. -// -// The Close and WriteControl methods can be called concurrently with all other -// methods. -// -// Origin Considerations -// -// Web browsers allow Javascript applications to open a WebSocket connection to -// any host. It's up to the server to enforce an origin policy using the Origin -// request header sent by the browser. -// -// The Upgrader calls the function specified in the CheckOrigin field to check -// the origin. If the CheckOrigin function returns false, then the Upgrade -// method fails the WebSocket handshake with HTTP status 403. -// -// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail -// the handshake if the Origin request header is present and not equal to the -// Host request header. -// -// An application can allow connections from any origin by specifying a -// function that always returns true: -// -// var upgrader = websocket.Upgrader{ -// CheckOrigin: func(r *http.Request) bool { return true }, -// } -// -// The deprecated package-level Upgrade function does not perform origin -// checking. The application is responsible for checking the Origin header -// before calling the Upgrade function. -// -// Compression EXPERIMENTAL -// -// Per message compression extensions (RFC 7692) are experimentally supported -// by this package in a limited capacity. Setting the EnableCompression option -// to true in Dialer or Upgrader will attempt to negotiate per message deflate -// support. -// -// var upgrader = websocket.Upgrader{ -// EnableCompression: true, -// } -// -// If compression was successfully negotiated with the connection's peer, any -// message received in compressed form will be automatically decompressed. -// All Read methods will return uncompressed bytes. -// -// Per message compression of messages written to a connection can be enabled -// or disabled by calling the corresponding Conn method: -// -// conn.EnableWriteCompression(false) -// -// Currently this package does not support compression with "context takeover". -// This means that messages must be compressed and decompressed in isolation, -// without retaining sliding window or dictionary state across messages. For -// more details refer to RFC 7692. -// -// Use of compression is experimental and may result in decreased performance. -package websocket diff --git a/websocket/example_test.go b/websocket/example_test.go deleted file mode 100644 index 96449eac70e0528fd46a060c478cf10273c68b17..0000000000000000000000000000000000000000 --- a/websocket/example_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket_test - -import ( - "log" - "net/http" - "testing" - - "github.com/gorilla/websocket" -) - -var ( - c *websocket.Conn - req *http.Request -) - -// The websocket.IsUnexpectedCloseError function is useful for identifying -// application and protocol errors. -// -// This server application works with a client application running in the -// browser. The client application does not explicitly close the websocket. The -// only expected close message from the client has the code -// websocket.CloseGoingAway. All other other close messages are likely the -// result of an application or protocol error and are logged to aid debugging. -func ExampleIsUnexpectedCloseError() { - - for { - messageType, p, err := c.ReadMessage() - if err != nil { - if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { - log.Printf("error: %v, user-agent: %v", err, req.Header.Get("User-Agent")) - } - return - } - processMesage(messageType, p) - } -} - -func processMesage(mt int, p []byte) {} - -// TestX prevents godoc from showing this entire file in the example. Remove -// this function when a second example is added. -func TestX(t *testing.T) {} diff --git a/websocket/f.zip b/websocket/f.zip deleted file mode 100644 index c4d9f8f465130300245c285b7e608b32a113146c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63895 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�|mKTKIDIYTJc+>;O(+5mO|29}8Adz(K}h6bjUd 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 0; i-- { - if err := wc.WriteMessage(TextMessage, data[:i]); err != nil { - t.Fatal(err) - } - messageCount++ - } - - // Whitespace. - - if err := wc.WriteMessage(TextMessage, []byte(" ")); err != nil { - t.Fatal(err) - } - messageCount++ - - // Close. - - if err := wc.WriteMessage(CloseMessage, FormatCloseMessage(CloseNormalClosure, "")); err != nil { - t.Fatal(err) - } - - for i := 0; i < messageCount; i++ { - err := rc.ReadJSON(&v) - if err != io.ErrUnexpectedEOF { - t.Error("read", i, err) - } - } - - err = rc.ReadJSON(&v) - if _, ok := err.(*CloseError); !ok { - t.Error("final", err) - } -} - -func TestDeprecatedJSON(t *testing.T) { - var buf bytes.Buffer - c := fakeNetConn{&buf, &buf} - wc := newConn(c, true, 1024, 1024) - rc := newConn(c, false, 1024, 1024) - - var actual, expect struct { - A int - B string - } - expect.A = 1 - expect.B = "hello" - - if err := WriteJSON(wc, &expect); err != nil { - t.Fatal("write", err) - } - - if err := ReadJSON(rc, &actual); err != nil { - t.Fatal("read", err) - } - - if !reflect.DeepEqual(&actual, &expect) { - t.Fatal("equal", actual, expect) - } -} diff --git a/websocket/mask.go b/websocket/mask.go deleted file mode 100644 index 6a88bbc74342cd64c6d342c1578fb02ee42bfd22..0000000000000000000000000000000000000000 --- a/websocket/mask.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of -// this source code is governed by a BSD-style license that can be found in the -// LICENSE file. - -// +build !appengine - -package websocket - -import "unsafe" - -const wordSize = int(unsafe.Sizeof(uintptr(0))) - -func maskBytes(key [4]byte, pos int, b []byte) int { - - // Mask one byte at a time for small buffers. - if len(b) < 2*wordSize { - for i := range b { - b[i] ^= key[pos&3] - pos++ - } - return pos & 3 - } - - // Mask one byte at a time to word boundary. - if n := int(uintptr(unsafe.Pointer(&b[0]))) % wordSize; n != 0 { - n = wordSize - n - for i := range b[:n] { - b[i] ^= key[pos&3] - pos++ - } - b = b[n:] - } - - // Create aligned word size key. - var k [wordSize]byte - for i := range k { - k[i] = key[(pos+i)&3] - } - kw := *(*uintptr)(unsafe.Pointer(&k)) - - // Mask one word at a time. - n := (len(b) / wordSize) * wordSize - for i := 0; i < n; i += wordSize { - *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(i))) ^= kw - } - - // Mask one byte at a time for remaining bytes. - b = b[n:] - for i := range b { - b[i] ^= key[pos&3] - pos++ - } - - return pos & 3 -} diff --git a/websocket/mask_safe.go b/websocket/mask_safe.go deleted file mode 100644 index 2aac060e52e7093d2dce5051d639a31560f6c4e8..0000000000000000000000000000000000000000 --- a/websocket/mask_safe.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of -// this source code is governed by a BSD-style license that can be found in the -// LICENSE file. - -// +build appengine - -package websocket - -func maskBytes(key [4]byte, pos int, b []byte) int { - for i := range b { - b[i] ^= key[pos&3] - pos++ - } - return pos & 3 -} diff --git a/websocket/mask_test.go b/websocket/mask_test.go deleted file mode 100644 index 298a1e509ee637618c6a7673a44a96b0cdcce270..0000000000000000000000000000000000000000 --- a/websocket/mask_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2016 The Gorilla WebSocket Authors. All rights reserved. Use of -// this source code is governed by a BSD-style license that can be found in the -// LICENSE file. - -// Require 1.7 for sub-bencmarks -// +build go1.7,!appengine - -package websocket - -import ( - "fmt" - "testing" -) - -func maskBytesByByte(key [4]byte, pos int, b []byte) int { - for i := range b { - b[i] ^= key[pos&3] - pos++ - } - return pos & 3 -} - -func notzero(b []byte) int { - for i := range b { - if b[i] != 0 { - return i - } - } - return -1 -} - -func TestMaskBytes(t *testing.T) { - key := [4]byte{1, 2, 3, 4} - for size := 1; size <= 1024; size++ { - for align := 0; align < wordSize; align++ { - for pos := 0; pos < 4; pos++ { - b := make([]byte, size+align)[align:] - maskBytes(key, pos, b) - maskBytesByByte(key, pos, b) - if i := notzero(b); i >= 0 { - t.Errorf("size:%d, align:%d, pos:%d, offset:%d", size, align, pos, i) - } - } - } - } -} - -func BenchmarkMaskBytes(b *testing.B) { - for _, size := range []int{2, 4, 8, 16, 32, 512, 1024} { - b.Run(fmt.Sprintf("size-%d", size), func(b *testing.B) { - for _, align := range []int{wordSize / 2} { - b.Run(fmt.Sprintf("align-%d", align), func(b *testing.B) { - for _, fn := range []struct { - name string - fn func(key [4]byte, pos int, b []byte) int - }{ - {"byte", maskBytesByByte}, - {"word", maskBytes}, - } { - b.Run(fn.name, func(b *testing.B) { - key := newMaskKey() - data := make([]byte, size+align)[align:] - for i := 0; i < b.N; i++ { - fn.fn(key, 0, data) - } - b.SetBytes(int64(len(data))) - }) - } - }) - } - }) - } -} diff --git a/websocket/prepared.go b/websocket/prepared.go deleted file mode 100644 index 1efffbd1ebe91160879664fe5c62d2c94be09064..0000000000000000000000000000000000000000 --- a/websocket/prepared.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bytes" - "net" - "sync" - "time" -) - -// PreparedMessage caches on the wire representations of a message payload. -// Use PreparedMessage to efficiently send a message payload to multiple -// connections. PreparedMessage is especially useful when compression is used -// because the CPU and memory expensive compression operation can be executed -// once for a given set of compression options. -type PreparedMessage struct { - messageType int - data []byte - err error - mu sync.Mutex - frames map[prepareKey]*preparedFrame -} - -// prepareKey defines a unique set of options to cache prepared frames in PreparedMessage. -type prepareKey struct { - isServer bool - compress bool - compressionLevel int -} - -// preparedFrame contains data in wire representation. -type preparedFrame struct { - once sync.Once - data []byte -} - -// NewPreparedMessage returns an initialized PreparedMessage. You can then send -// it to connection using WritePreparedMessage method. Valid wire -// representation will be calculated lazily only once for a set of current -// connection options. -func NewPreparedMessage(messageType int, data []byte) (*PreparedMessage, error) { - pm := &PreparedMessage{ - messageType: messageType, - frames: make(map[prepareKey]*preparedFrame), - data: data, - } - - // Prepare a plain server frame. - _, frameData, err := pm.frame(prepareKey{isServer: true, compress: false}) - if err != nil { - return nil, err - } - - // To protect against caller modifying the data argument, remember the data - // copied to the plain server frame. - pm.data = frameData[len(frameData)-len(data):] - return pm, nil -} - -func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { - pm.mu.Lock() - frame, ok := pm.frames[key] - if !ok { - frame = &preparedFrame{} - pm.frames[key] = frame - } - pm.mu.Unlock() - - var err error - frame.once.Do(func() { - // Prepare a frame using a 'fake' connection. - // TODO: Refactor code in conn.go to allow more direct construction of - // the frame. - mu := make(chan bool, 1) - mu <- true - var nc prepareConn - c := &Conn{ - conn: &nc, - mu: mu, - isServer: key.isServer, - compressionLevel: key.compressionLevel, - enableWriteCompression: true, - writeBuf: make([]byte, defaultWriteBufferSize+maxFrameHeaderSize), - } - if key.compress { - c.newCompressionWriter = compressNoContextTakeover - } - err = c.WriteMessage(pm.messageType, pm.data) - frame.data = nc.buf.Bytes() - }) - return pm.messageType, frame.data, err -} - -type prepareConn struct { - buf bytes.Buffer - net.Conn -} - -func (pc *prepareConn) Write(p []byte) (int, error) { return pc.buf.Write(p) } -func (pc *prepareConn) SetWriteDeadline(t time.Time) error { return nil } diff --git a/websocket/prepared_test.go b/websocket/prepared_test.go deleted file mode 100644 index cf98c6c10a306ce25fb75c317fb9a3270f7f31e7..0000000000000000000000000000000000000000 --- a/websocket/prepared_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2017 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bytes" - "compress/flate" - "math/rand" - "testing" -) - -var preparedMessageTests = []struct { - messageType int - isServer bool - enableWriteCompression bool - compressionLevel int -}{ - // Server - {TextMessage, true, false, flate.BestSpeed}, - {TextMessage, true, true, flate.BestSpeed}, - {TextMessage, true, true, flate.BestCompression}, - {PingMessage, true, false, flate.BestSpeed}, - {PingMessage, true, true, flate.BestSpeed}, - - // Client - {TextMessage, false, false, flate.BestSpeed}, - {TextMessage, false, true, flate.BestSpeed}, - {TextMessage, false, true, flate.BestCompression}, - {PingMessage, false, false, flate.BestSpeed}, - {PingMessage, false, true, flate.BestSpeed}, -} - -func TestPreparedMessage(t *testing.T) { - for _, tt := range preparedMessageTests { - var data = []byte("this is a test") - var buf bytes.Buffer - c := newConn(fakeNetConn{Reader: nil, Writer: &buf}, tt.isServer, 1024, 1024) - if tt.enableWriteCompression { - c.newCompressionWriter = compressNoContextTakeover - } - c.SetCompressionLevel(tt.compressionLevel) - - // Seed random number generator for consistent frame mask. - rand.Seed(1234) - - if err := c.WriteMessage(tt.messageType, data); err != nil { - t.Fatal(err) - } - want := buf.String() - - pm, err := NewPreparedMessage(tt.messageType, data) - if err != nil { - t.Fatal(err) - } - - // Scribble on data to ensure that NewPreparedMessage takes a snapshot. - copy(data, "hello world") - - // Seed random number generator for consistent frame mask. - rand.Seed(1234) - - buf.Reset() - if err := c.WritePreparedMessage(pm); err != nil { - t.Fatal(err) - } - got := buf.String() - - if got != want { - t.Errorf("write message != prepared message for %+v", tt) - } - } -} diff --git a/websocket/server.go b/websocket/server.go deleted file mode 100644 index 6ae97c54fec62e19abe0570d0859e743cc019350..0000000000000000000000000000000000000000 --- a/websocket/server.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "bufio" - "errors" - "net" - "net/http" - "net/url" - "strings" - "time" -) - -// HandshakeError describes an error with the handshake from the peer. -type HandshakeError struct { - message string -} - -func (e HandshakeError) Error() string { return e.message } - -// Upgrader specifies parameters for upgrading an HTTP connection to a -// WebSocket connection. -type Upgrader struct { - // HandshakeTimeout specifies the duration for the handshake to complete. - HandshakeTimeout time.Duration - - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer - // size is zero, then buffers allocated by the HTTP server are used. The - // I/O buffer sizes do not limit the size of the messages that can be sent - // or received. - ReadBufferSize, WriteBufferSize int - - // Subprotocols specifies the server's supported protocols in order of - // preference. If this field is set, then the Upgrade method negotiates a - // subprotocol by selecting the first match in this list with a protocol - // requested by the client. - Subprotocols []string - - // Error specifies the function for generating HTTP error responses. If Error - // is nil, then http.Error is used to generate the HTTP response. - Error func(w http.ResponseWriter, r *http.Request, status int, reason error) - - // CheckOrigin returns true if the request Origin header is acceptable. If - // CheckOrigin is nil, the host in the Origin header must not be set or - // must match the host of the request. - CheckOrigin func(r *http.Request) bool - - // EnableCompression specify if the server should attempt to negotiate per - // message compression (RFC 7692). Setting this value to true does not - // guarantee that compression will be supported. Currently only "no context - // takeover" modes are supported. - EnableCompression bool -} - -func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { - err := HandshakeError{reason} - if u.Error != nil { - u.Error(w, r, status, err) - } else { - w.Header().Set("Sec-Websocket-Version", "13") - http.Error(w, http.StatusText(status), status) - } - return nil, err -} - -// checkSameOrigin returns true if the origin is not set or is equal to the request host. -func checkSameOrigin(r *http.Request) bool { - origin := r.Header["Origin"] - if len(origin) == 0 { - return true - } - u, err := url.Parse(origin[0]) - if err != nil { - return false - } - return u.Host == r.Host -} - -func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { - if u.Subprotocols != nil { - clientProtocols := Subprotocols(r) - for _, serverProtocol := range u.Subprotocols { - for _, clientProtocol := range clientProtocols { - if clientProtocol == serverProtocol { - return clientProtocol - } - } - } - } else if responseHeader != nil { - return responseHeader.Get("Sec-Websocket-Protocol") - } - return "" -} - -// Upgrade upgrades the HTTP server connection to the WebSocket protocol. -// -// The responseHeader is included in the response to the client's upgrade -// request. Use the responseHeader to specify cookies (Set-Cookie) and the -// application negotiated subprotocol (Sec-Websocket-Protocol). -// -// If the upgrade fails, then Upgrade replies to the client with an HTTP error -// response. -func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { - if r.Method != "GET" { - return u.returnError(w, r, http.StatusMethodNotAllowed, "websocket: not a websocket handshake: request method is not GET") - } - - if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { - return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-Websocket-Extensions' headers are unsupported") - } - - if !tokenListContainsValue(r.Header, "Connection", "upgrade") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'upgrade' token not found in 'Connection' header") - } - - if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'websocket' token not found in 'Upgrade' header") - } - - if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { - return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") - } - - checkOrigin := u.CheckOrigin - if checkOrigin == nil { - checkOrigin = checkSameOrigin - } - if !checkOrigin(r) { - return u.returnError(w, r, http.StatusForbidden, "websocket: 'Origin' header value not allowed") - } - - challengeKey := r.Header.Get("Sec-Websocket-Key") - if challengeKey == "" { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-Websocket-Key' header is missing or blank") - } - - subprotocol := u.selectSubprotocol(r, responseHeader) - - // Negotiate PMCE - var compress bool - if u.EnableCompression { - for _, ext := range parseExtensions(r.Header) { - if ext[""] != "permessage-deflate" { - continue - } - compress = true - break - } - } - - var ( - netConn net.Conn - err error - ) - - h, ok := w.(http.Hijacker) - if !ok { - return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") - } - var brw *bufio.ReadWriter - netConn, brw, err = h.Hijack() - if err != nil { - return u.returnError(w, r, http.StatusInternalServerError, err.Error()) - } - - if brw.Reader.Buffered() > 0 { - netConn.Close() - return nil, errors.New("websocket: client sent data before handshake is complete") - } - - c := newConnBRW(netConn, true, u.ReadBufferSize, u.WriteBufferSize, brw) - c.subprotocol = subprotocol - - if compress { - c.newCompressionWriter = compressNoContextTakeover - c.newDecompressionReader = decompressNoContextTakeover - } - - p := c.writeBuf[:0] - p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) - p = append(p, computeAcceptKey(challengeKey)...) - p = append(p, "\r\n"...) - if c.subprotocol != "" { - p = append(p, "Sec-Websocket-Protocol: "...) - p = append(p, c.subprotocol...) - p = append(p, "\r\n"...) - } - if compress { - p = append(p, "Sec-Websocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) - } - for k, vs := range responseHeader { - if k == "Sec-Websocket-Protocol" { - continue - } - for _, v := range vs { - p = append(p, k...) - p = append(p, ": "...) - for i := 0; i < len(v); i++ { - b := v[i] - if b <= 31 { - // prevent response splitting. - b = ' ' - } - p = append(p, b) - } - p = append(p, "\r\n"...) - } - } - p = append(p, "\r\n"...) - - // Clear deadlines set by HTTP server. - netConn.SetDeadline(time.Time{}) - - if u.HandshakeTimeout > 0 { - netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) - } - if _, err = netConn.Write(p); err != nil { - netConn.Close() - return nil, err - } - if u.HandshakeTimeout > 0 { - netConn.SetWriteDeadline(time.Time{}) - } - - return c, nil -} - -// Upgrade upgrades the HTTP server connection to the WebSocket protocol. -// -// Deprecated: Use websocket.Upgrader instead. -// -// Upgrade does not perform origin checking. The application is responsible for -// checking the Origin header before calling Upgrade. An example implementation -// of the same origin policy check is: -// -// if req.Header.Get("Origin") != "http://"+req.Host { -// http.Error(w, "Origin not allowed", 403) -// return -// } -// -// If the endpoint supports subprotocols, then the application is responsible -// for negotiating the protocol used on the connection. Use the Subprotocols() -// function to get the subprotocols requested by the client. Use the -// Sec-Websocket-Protocol response header to specify the subprotocol selected -// by the application. -// -// The responseHeader is included in the response to the client's upgrade -// request. Use the responseHeader to specify cookies (Set-Cookie) and the -// negotiated subprotocol (Sec-Websocket-Protocol). -// -// The connection buffers IO to the underlying network connection. The -// readBufSize and writeBufSize parameters specify the size of the buffers to -// use. Messages can be larger than the buffers. -// -// If the request is not a valid WebSocket handshake, then Upgrade returns an -// error of type HandshakeError. Applications should handle this error by -// replying to the client with an HTTP error response. -func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { - u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} - u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { - // don't return errors to maintain backwards compatibility - } - u.CheckOrigin = func(r *http.Request) bool { - // allow all connections by default - return true - } - return u.Upgrade(w, r, responseHeader) -} - -// Subprotocols returns the subprotocols requested by the client in the -// Sec-Websocket-Protocol header. -func Subprotocols(r *http.Request) []string { - h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) - if h == "" { - return nil - } - protocols := strings.Split(h, ",") - for i := range protocols { - protocols[i] = strings.TrimSpace(protocols[i]) - } - return protocols -} - -// IsWebSocketUpgrade returns true if the client requested upgrade to the -// WebSocket protocol. -func IsWebSocketUpgrade(r *http.Request) bool { - return tokenListContainsValue(r.Header, "Connection", "upgrade") && - tokenListContainsValue(r.Header, "Upgrade", "websocket") -} diff --git a/websocket/server_test.go b/websocket/server_test.go deleted file mode 100644 index 0a28141d6c6edbddd5cfcff784167b79a679fc36..0000000000000000000000000000000000000000 --- a/websocket/server_test.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "net/http" - "reflect" - "testing" -) - -var subprotocolTests = []struct { - h string - protocols []string -}{ - {"", nil}, - {"foo", []string{"foo"}}, - {"foo,bar", []string{"foo", "bar"}}, - {"foo, bar", []string{"foo", "bar"}}, - {" foo, bar", []string{"foo", "bar"}}, - {" foo, bar ", []string{"foo", "bar"}}, -} - -func TestSubprotocols(t *testing.T) { - for _, st := range subprotocolTests { - r := http.Request{Header: http.Header{"Sec-Websocket-Protocol": {st.h}}} - protocols := Subprotocols(&r) - if !reflect.DeepEqual(st.protocols, protocols) { - t.Errorf("SubProtocols(%q) returned %#v, want %#v", st.h, protocols, st.protocols) - } - } -} - -var isWebSocketUpgradeTests = []struct { - ok bool - h http.Header -}{ - {false, http.Header{"Upgrade": {"websocket"}}}, - {false, http.Header{"Connection": {"upgrade"}}}, - {true, http.Header{"Connection": {"upgRade"}, "Upgrade": {"WebSocket"}}}, -} - -func TestIsWebSocketUpgrade(t *testing.T) { - for _, tt := range isWebSocketUpgradeTests { - ok := IsWebSocketUpgrade(&http.Request{Header: tt.h}) - if tt.ok != ok { - t.Errorf("IsWebSocketUpgrade(%v) returned %v, want %v", tt.h, ok, tt.ok) - } - } -} diff --git a/websocket/util.go b/websocket/util.go deleted file mode 100644 index 262e647bce224464a2edfef7604a47267c4152e8..0000000000000000000000000000000000000000 --- a/websocket/util.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "crypto/rand" - "crypto/sha1" - "encoding/base64" - "io" - "net/http" - "strings" -) - -var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") - -func computeAcceptKey(challengeKey string) string { - h := sha1.New() - h.Write([]byte(challengeKey)) - h.Write(keyGUID) - return base64.StdEncoding.EncodeToString(h.Sum(nil)) -} - -func generateChallengeKey() (string, error) { - p := make([]byte, 16) - if _, err := io.ReadFull(rand.Reader, p); err != nil { - return "", err - } - return base64.StdEncoding.EncodeToString(p), nil -} - -// Octet types from RFC 2616. -var octetTypes [256]byte - -const ( - isTokenOctet = 1 << iota - isSpaceOctet -) - -func init() { - // From RFC 2616 - // - // OCTET = - // CHAR = - // CTL = - // CR = - // LF = - // SP = - // HT = - // <"> = - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1* - // qdtext = > - - for c := 0; c < 256; c++ { - var t byte - isCtl := c <= 31 || c == 127 - isChar := 0 <= c && c <= 127 - isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 - if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { - t |= isSpaceOctet - } - if isChar && !isCtl && !isSeparator { - t |= isTokenOctet - } - octetTypes[c] = t - } -} - -func skipSpace(s string) (rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpaceOctet == 0 { - break - } - } - return s[i:] -} - -func nextToken(s string) (token, rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isTokenOctet == 0 { - break - } - } - return s[:i], s[i:] -} - -func nextTokenOrQuoted(s string) (value string, rest string) { - if !strings.HasPrefix(s, "\"") { - return nextToken(s) - } - s = s[1:] - for i := 0; i < len(s); i++ { - switch s[i] { - case '"': - return s[:i], s[i+1:] - case '\\': - p := make([]byte, len(s)-1) - j := copy(p, s[:i]) - escape := true - for i = i + 1; i < len(s); i++ { - b := s[i] - switch { - case escape: - escape = false - p[j] = b - j++ - case b == '\\': - escape = true - case b == '"': - return string(p[:j]), s[i+1:] - default: - p[j] = b - j++ - } - } - return "", "" - } - } - return "", "" -} - -// tokenListContainsValue returns true if the 1#token header with the given -// name contains token. -func tokenListContainsValue(header http.Header, name string, value string) bool { -headers: - for _, s := range header[name] { - for { - var t string - t, s = nextToken(skipSpace(s)) - if t == "" { - continue headers - } - s = skipSpace(s) - if s != "" && s[0] != ',' { - continue headers - } - if strings.EqualFold(t, value) { - return true - } - if s == "" { - continue headers - } - s = s[1:] - } - } - return false -} - -// parseExtensiosn parses WebSocket extensions from a header. -func parseExtensions(header http.Header) []map[string]string { - - // From RFC 6455: - // - // Sec-WebSocket-Extensions = extension-list - // extension-list = 1#extension - // extension = extension-token *( ";" extension-param ) - // extension-token = registered-token - // registered-token = token - // extension-param = token [ "=" (token | quoted-string) ] - // ;When using the quoted-string syntax variant, the value - // ;after quoted-string unescaping MUST conform to the - // ;'token' ABNF. - - var result []map[string]string -headers: - for _, s := range header["Sec-Websocket-Extensions"] { - for { - var t string - t, s = nextToken(skipSpace(s)) - if t == "" { - continue headers - } - ext := map[string]string{"": t} - for { - s = skipSpace(s) - if !strings.HasPrefix(s, ";") { - break - } - var k string - k, s = nextToken(skipSpace(s[1:])) - if k == "" { - continue headers - } - s = skipSpace(s) - var v string - if strings.HasPrefix(s, "=") { - v, s = nextTokenOrQuoted(skipSpace(s[1:])) - s = skipSpace(s) - } - if s != "" && s[0] != ',' && s[0] != ';' { - continue headers - } - ext[k] = v - } - if s != "" && s[0] != ',' { - continue headers - } - result = append(result, ext) - if s == "" { - continue headers - } - s = s[1:] - } - } - return result -} diff --git a/websocket/util_test.go b/websocket/util_test.go deleted file mode 100644 index 610e613c02ec01a1e639c389429f38e1827eda4b..0000000000000000000000000000000000000000 --- a/websocket/util_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package websocket - -import ( - "net/http" - "reflect" - "testing" -) - -var tokenListContainsValueTests = []struct { - value string - ok bool -}{ - {"WebSocket", true}, - {"WEBSOCKET", true}, - {"websocket", true}, - {"websockets", false}, - {"x websocket", false}, - {"websocket x", false}, - {"other,websocket,more", true}, - {"other, websocket, more", true}, -} - -func TestTokenListContainsValue(t *testing.T) { - for _, tt := range tokenListContainsValueTests { - h := http.Header{"Upgrade": {tt.value}} - ok := tokenListContainsValue(h, "Upgrade", "websocket") - if ok != tt.ok { - t.Errorf("tokenListContainsValue(h, n, %q) = %v, want %v", tt.value, ok, tt.ok) - } - } -} - -var parseExtensionTests = []struct { - value string - extensions []map[string]string -}{ - {`foo`, []map[string]string{map[string]string{"": "foo"}}}, - {`foo, bar; baz=2`, []map[string]string{ - map[string]string{"": "foo"}, - map[string]string{"": "bar", "baz": "2"}}}, - {`foo; bar="b,a;z"`, []map[string]string{ - map[string]string{"": "foo", "bar": "b,a;z"}}}, - {`foo , bar; baz = 2`, []map[string]string{ - map[string]string{"": "foo"}, - map[string]string{"": "bar", "baz": "2"}}}, - {`foo, bar; baz=2 junk`, []map[string]string{ - map[string]string{"": "foo"}}}, - {`foo junk, bar; baz=2 junk`, nil}, - {`mux; max-channels=4; flow-control, deflate-stream`, []map[string]string{ - map[string]string{"": "mux", "max-channels": "4", "flow-control": ""}, - map[string]string{"": "deflate-stream"}}}, - {`permessage-foo; x="10"`, []map[string]string{ - map[string]string{"": "permessage-foo", "x": "10"}}}, - {`permessage-foo; use_y, permessage-foo`, []map[string]string{ - map[string]string{"": "permessage-foo", "use_y": ""}, - map[string]string{"": "permessage-foo"}}}, - {`permessage-deflate; client_max_window_bits; server_max_window_bits=10 , permessage-deflate; client_max_window_bits`, []map[string]string{ - map[string]string{"": "permessage-deflate", "client_max_window_bits": "", "server_max_window_bits": "10"}, - map[string]string{"": "permessage-deflate", "client_max_window_bits": ""}}}, -} - -func TestParseExtensions(t *testing.T) { - for _, tt := range parseExtensionTests { - h := http.Header{http.CanonicalHeaderKey("Sec-WebSocket-Extensions"): {tt.value}} - extensions := parseExtensions(h) - if !reflect.DeepEqual(extensions, tt.extensions) { - t.Errorf("parseExtensions(%q)\n = %v,\nwant %v", tt.value, extensions, tt.extensions) - } - } -}