Add Discord support
This commit is contained in:
		
							
								
								
									
										28
									
								
								vendor/github.com/bwmarrin/discordgo/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/bwmarrin/discordgo/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| Copyright (c) 2015, Bruce Marriner | ||||
| All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are met: | ||||
|  | ||||
| * Redistributions of source code must retain the above copyright notice, this | ||||
|   list of conditions and the following disclaimer. | ||||
|  | ||||
| * Redistributions in binary form must reproduce the above copyright notice, | ||||
|   this list of conditions and the following disclaimer in the documentation | ||||
|   and/or other materials provided with the distribution. | ||||
|  | ||||
| * Neither the name of discordgo nor the names of its | ||||
|   contributors may be used to endorse or promote products derived from | ||||
|   this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  | ||||
							
								
								
									
										257
									
								
								vendor/github.com/bwmarrin/discordgo/discord.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								vendor/github.com/bwmarrin/discordgo/discord.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,257 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  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 ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| ) | ||||
|  | ||||
| // VERSION of Discordgo, follows Symantic Versioning. (http://semver.org/) | ||||
| const VERSION = "0.13.0" | ||||
|  | ||||
| // 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. | ||||
| //     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. | ||||
| func New(args ...interface{}) (s *Session, err error) { | ||||
|  | ||||
| 	// Create an empty Session interface. | ||||
| 	s = &Session{ | ||||
| 		State:                  NewState(), | ||||
| 		StateEnabled:           true, | ||||
| 		Compress:               true, | ||||
| 		ShouldReconnectOnError: true, | ||||
| 		ShardID:                0, | ||||
| 		ShardCount:             1, | ||||
| 	} | ||||
|  | ||||
| 	// 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 == "" { | ||||
| 			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 | ||||
| } | ||||
|  | ||||
| // validateHandler takes an event handler func, and returns the type of event. | ||||
| // eg. | ||||
| //     Session.validateHandler(func (s *discordgo.Session, m *discordgo.MessageCreate)) | ||||
| //     will return the reflect.Type of *discordgo.MessageCreate | ||||
| func (s *Session) validateHandler(handler interface{}) reflect.Type { | ||||
|  | ||||
| 	handlerType := reflect.TypeOf(handler) | ||||
|  | ||||
| 	if handlerType.NumIn() != 2 { | ||||
| 		panic("Unable to add event handler, handler must be of the type func(*discordgo.Session, *discordgo.EventType).") | ||||
| 	} | ||||
|  | ||||
| 	if handlerType.In(0) != reflect.TypeOf(s) { | ||||
| 		panic("Unable to add event handler, first argument must be of type *discordgo.Session.") | ||||
| 	} | ||||
|  | ||||
| 	eventType := handlerType.In(1) | ||||
|  | ||||
| 	// Support handlers of type interface{}, this is a special handler, which is triggered on every event. | ||||
| 	if eventType.Kind() == reflect.Interface { | ||||
| 		eventType = nil | ||||
| 	} | ||||
|  | ||||
| 	return eventType | ||||
| } | ||||
|  | ||||
| // AddHandler allows you to add an event handler that will be fired anytime | ||||
| // the Discord WSAPI event that matches the interface fires. | ||||
| // eventToInterface in events.go has a list of all the Discord WSAPI events | ||||
| // and their respective interface. | ||||
| // 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() { | ||||
|  | ||||
| 	s.initialize() | ||||
|  | ||||
| 	eventType := s.validateHandler(handler) | ||||
|  | ||||
| 	s.handlersMu.Lock() | ||||
| 	defer s.handlersMu.Unlock() | ||||
|  | ||||
| 	h := reflect.ValueOf(handler) | ||||
|  | ||||
| 	s.handlers[eventType] = append(s.handlers[eventType], h) | ||||
|  | ||||
| 	// This must be done as we need a consistent reference to the | ||||
| 	// reflected value, otherwise a RemoveHandler method would have | ||||
| 	// been nice. | ||||
| 	return func() { | ||||
| 		s.handlersMu.Lock() | ||||
| 		defer s.handlersMu.Unlock() | ||||
|  | ||||
| 		handlers := s.handlers[eventType] | ||||
| 		for i, v := range handlers { | ||||
| 			if h == v { | ||||
| 				s.handlers[eventType] = append(handlers[:i], handlers[i+1:]...) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // handle calls any handlers that match the event type and any handlers of | ||||
| // interface{}. | ||||
| func (s *Session) handle(event interface{}) { | ||||
|  | ||||
| 	s.handlersMu.RLock() | ||||
| 	defer s.handlersMu.RUnlock() | ||||
|  | ||||
| 	if s.handlers == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)} | ||||
|  | ||||
| 	if handlers, ok := s.handlers[nil]; ok { | ||||
| 		for _, handler := range handlers { | ||||
| 			go handler.Call(handlerParameters) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if handlers, ok := s.handlers[reflect.TypeOf(event)]; ok { | ||||
| 		for _, handler := range handlers { | ||||
| 			go handler.Call(handlerParameters) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // initialize adds all internal handlers and state tracking handlers. | ||||
| func (s *Session) initialize() { | ||||
|  | ||||
| 	s.log(LogInformational, "called") | ||||
|  | ||||
| 	s.handlersMu.Lock() | ||||
| 	if s.handlers != nil { | ||||
| 		s.handlersMu.Unlock() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	s.handlers = map[interface{}][]reflect.Value{} | ||||
| 	s.handlersMu.Unlock() | ||||
|  | ||||
| 	s.AddHandler(s.onReady) | ||||
| 	s.AddHandler(s.onResumed) | ||||
| 	s.AddHandler(s.onVoiceServerUpdate) | ||||
| 	s.AddHandler(s.onVoiceStateUpdate) | ||||
| 	s.AddHandler(s.State.onInterface) | ||||
| } | ||||
|  | ||||
| // onReady handles the ready event. | ||||
| func (s *Session) onReady(se *Session, r *Ready) { | ||||
|  | ||||
| 	// Store the SessionID within the Session struct. | ||||
| 	s.sessionID = r.SessionID | ||||
|  | ||||
| 	// Start the heartbeat to keep the connection alive. | ||||
| 	go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval) | ||||
| } | ||||
|  | ||||
| // onResumed handles the resumed event. | ||||
| func (s *Session) onResumed(se *Session, r *Resumed) { | ||||
|  | ||||
| 	// Start the heartbeat to keep the connection alive. | ||||
| 	go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval) | ||||
| } | ||||
							
								
								
									
										99
									
								
								vendor/github.com/bwmarrin/discordgo/endpoints.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								vendor/github.com/bwmarrin/discordgo/endpoints.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  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 | ||||
|  | ||||
| // 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/" | ||||
| 	EndpointGuilds   = EndpointAPI + "guilds/" | ||||
| 	EndpointChannels = EndpointAPI + "channels/" | ||||
| 	EndpointUsers    = EndpointAPI + "users/" | ||||
| 	EndpointGateway  = EndpointAPI + "gateway" | ||||
|  | ||||
| 	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 EndpointUsers + uID + "/avatars/" + aID + ".jpg" } | ||||
| 	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" } | ||||
|  | ||||
| 	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 } | ||||
| 	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 EndpointGuilds + gID + "/icons/" + hash + ".jpg" } | ||||
| 	EndpointGuildSplash          = func(gID, hash string) string { return EndpointGuilds + gID + "/splashes/" + hash + ".jpg" } | ||||
|  | ||||
| 	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 } | ||||
|  | ||||
| 	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" } | ||||
| ) | ||||
							
								
								
									
										159
									
								
								vendor/github.com/bwmarrin/discordgo/events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								vendor/github.com/bwmarrin/discordgo/events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| package discordgo | ||||
|  | ||||
| // eventToInterface is a mapping of Discord WSAPI events to their | ||||
| // DiscordGo event container. | ||||
| // Each Discord WSAPI event maps to a unique interface. | ||||
| // Use Session.AddHandler with one of these types to handle that | ||||
| // type of event. | ||||
| // eg: | ||||
| //     Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| //     }) | ||||
| // | ||||
| // or: | ||||
| //     Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) { | ||||
| //     }) | ||||
| var eventToInterface = map[string]interface{}{ | ||||
| 	"CHANNEL_CREATE":             ChannelCreate{}, | ||||
| 	"CHANNEL_UPDATE":             ChannelUpdate{}, | ||||
| 	"CHANNEL_DELETE":             ChannelDelete{}, | ||||
| 	"GUILD_CREATE":               GuildCreate{}, | ||||
| 	"GUILD_UPDATE":               GuildUpdate{}, | ||||
| 	"GUILD_DELETE":               GuildDelete{}, | ||||
| 	"GUILD_BAN_ADD":              GuildBanAdd{}, | ||||
| 	"GUILD_BAN_REMOVE":           GuildBanRemove{}, | ||||
| 	"GUILD_MEMBER_ADD":           GuildMemberAdd{}, | ||||
| 	"GUILD_MEMBER_UPDATE":        GuildMemberUpdate{}, | ||||
| 	"GUILD_MEMBER_REMOVE":        GuildMemberRemove{}, | ||||
| 	"GUILD_ROLE_CREATE":          GuildRoleCreate{}, | ||||
| 	"GUILD_ROLE_UPDATE":          GuildRoleUpdate{}, | ||||
| 	"GUILD_ROLE_DELETE":          GuildRoleDelete{}, | ||||
| 	"GUILD_INTEGRATIONS_UPDATE":  GuildIntegrationsUpdate{}, | ||||
| 	"GUILD_EMOJIS_UPDATE":        GuildEmojisUpdate{}, | ||||
| 	"MESSAGE_ACK":                MessageAck{}, | ||||
| 	"MESSAGE_CREATE":             MessageCreate{}, | ||||
| 	"MESSAGE_UPDATE":             MessageUpdate{}, | ||||
| 	"MESSAGE_DELETE":             MessageDelete{}, | ||||
| 	"PRESENCE_UPDATE":            PresenceUpdate{}, | ||||
| 	"PRESENCES_REPLACE":          PresencesReplace{}, | ||||
| 	"READY":                      Ready{}, | ||||
| 	"USER_UPDATE":                UserUpdate{}, | ||||
| 	"USER_SETTINGS_UPDATE":       UserSettingsUpdate{}, | ||||
| 	"USER_GUILD_SETTINGS_UPDATE": UserGuildSettingsUpdate{}, | ||||
| 	"TYPING_START":               TypingStart{}, | ||||
| 	"VOICE_SERVER_UPDATE":        VoiceServerUpdate{}, | ||||
| 	"VOICE_STATE_UPDATE":         VoiceStateUpdate{}, | ||||
| 	"RESUMED":                    Resumed{}, | ||||
| } | ||||
|  | ||||
| // Connect is an empty struct for an event. | ||||
| type Connect struct{} | ||||
|  | ||||
| // Disconnect is an empty struct for an event. | ||||
| type Disconnect struct{} | ||||
|  | ||||
| // RateLimit is a struct for the RateLimited event | ||||
| type RateLimit struct { | ||||
| 	*TooManyRequests | ||||
| 	URL string | ||||
| } | ||||
|  | ||||
| // MessageCreate is a wrapper struct for an event. | ||||
| type MessageCreate struct { | ||||
| 	*Message | ||||
| } | ||||
|  | ||||
| // MessageUpdate is a wrapper struct for an event. | ||||
| type MessageUpdate struct { | ||||
| 	*Message | ||||
| } | ||||
|  | ||||
| // MessageDelete is a wrapper struct for an event. | ||||
| type MessageDelete struct { | ||||
| 	*Message | ||||
| } | ||||
|  | ||||
| // ChannelCreate is a wrapper struct for an event. | ||||
| type ChannelCreate struct { | ||||
| 	*Channel | ||||
| } | ||||
|  | ||||
| // ChannelUpdate is a wrapper struct for an event. | ||||
| type ChannelUpdate struct { | ||||
| 	*Channel | ||||
| } | ||||
|  | ||||
| // ChannelDelete is a wrapper struct for an event. | ||||
| type ChannelDelete struct { | ||||
| 	*Channel | ||||
| } | ||||
|  | ||||
| // GuildCreate is a wrapper struct for an event. | ||||
| type GuildCreate struct { | ||||
| 	*Guild | ||||
| } | ||||
|  | ||||
| // GuildUpdate is a wrapper struct for an event. | ||||
| type GuildUpdate struct { | ||||
| 	*Guild | ||||
| } | ||||
|  | ||||
| // GuildDelete is a wrapper struct for an event. | ||||
| type GuildDelete struct { | ||||
| 	*Guild | ||||
| } | ||||
|  | ||||
| // GuildBanAdd is a wrapper struct for an event. | ||||
| type GuildBanAdd struct { | ||||
| 	*GuildBan | ||||
| } | ||||
|  | ||||
| // GuildBanRemove is a wrapper struct for an event. | ||||
| type GuildBanRemove struct { | ||||
| 	*GuildBan | ||||
| } | ||||
|  | ||||
| // GuildMemberAdd is a wrapper struct for an event. | ||||
| type GuildMemberAdd struct { | ||||
| 	*Member | ||||
| } | ||||
|  | ||||
| // GuildMemberUpdate is a wrapper struct for an event. | ||||
| type GuildMemberUpdate struct { | ||||
| 	*Member | ||||
| } | ||||
|  | ||||
| // GuildMemberRemove is a wrapper struct for an event. | ||||
| type GuildMemberRemove struct { | ||||
| 	*Member | ||||
| } | ||||
|  | ||||
| // GuildRoleCreate is a wrapper struct for an event. | ||||
| type GuildRoleCreate struct { | ||||
| 	*GuildRole | ||||
| } | ||||
|  | ||||
| // GuildRoleUpdate is a wrapper struct for an event. | ||||
| type GuildRoleUpdate struct { | ||||
| 	*GuildRole | ||||
| } | ||||
|  | ||||
| // PresencesReplace is an array of Presences for an event. | ||||
| type PresencesReplace []*Presence | ||||
|  | ||||
| // VoiceStateUpdate is a wrapper struct for an event. | ||||
| type VoiceStateUpdate struct { | ||||
| 	*VoiceState | ||||
| } | ||||
|  | ||||
| // UserUpdate is a wrapper struct for an event. | ||||
| type UserUpdate struct { | ||||
| 	*User | ||||
| } | ||||
|  | ||||
| // UserSettingsUpdate is a map for an event. | ||||
| type UserSettingsUpdate map[string]interface{} | ||||
|  | ||||
| // UserGuildSettingsUpdate is a map for an event. | ||||
| type UserGuildSettingsUpdate struct { | ||||
| 	*UserGuildSettings | ||||
| } | ||||
							
								
								
									
										186
									
								
								vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/binary" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	flag.StringVar(&token, "t", "", "Account Token") | ||||
| 	flag.Parse() | ||||
| } | ||||
|  | ||||
| var token string | ||||
| var buffer = make([][]byte, 0) | ||||
|  | ||||
| func main() { | ||||
| 	if token == "" { | ||||
| 		fmt.Println("No token provided. Please run: airhorn -t <bot token>") | ||||
| 		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 token. | ||||
| 	dg, err := discordgo.New(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) | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println("Airhorn is now running.  Press CTRL-C to exit.") | ||||
| 	// Simple way to keep program running until CTRL-C is pressed. | ||||
| 	<-make(chan struct{}) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| 	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 guilds 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 != nil { | ||||
| 		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 { | ||||
| 			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 | ||||
| } | ||||
							
								
								
									
										98
									
								
								vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								vendor/github.com/bwmarrin/discordgo/examples/appmaker/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| // Variables used for command line options | ||||
| var ( | ||||
| 	Email    string | ||||
| 	Password string | ||||
| 	Token    string | ||||
| 	AppName  string | ||||
| 	DeleteID string | ||||
| 	ListOnly bool | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
|  | ||||
| 	flag.StringVar(&Email, "e", "", "Account Email") | ||||
| 	flag.StringVar(&Password, "p", "", "Account Password") | ||||
| 	flag.StringVar(&Token, "t", "", "Account Token") | ||||
| 	flag.StringVar(&DeleteID, "d", "", "Application ID to delete") | ||||
| 	flag.BoolVar(&ListOnly, "l", false, "List Applications Only") | ||||
| 	flag.StringVar(&AppName, "a", "", "App/Bot Name") | ||||
| 	flag.Parse() | ||||
| } | ||||
|  | ||||
| func main() { | ||||
|  | ||||
| 	var err error | ||||
| 	// Create a new Discord session using the provided login information. | ||||
| 	dg, err := discordgo.New(Email, Password, 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, err2 := dg.Applications() | ||||
| 		if err2 != nil { | ||||
| 			fmt.Println("error fetching applications,", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		for k, v := range aps { | ||||
| 			fmt.Printf("%d : --------------------------------------\n", k) | ||||
| 			fmt.Printf("ID: %s\n", v.ID) | ||||
| 			fmt.Printf("Name: %s\n", v.Name) | ||||
| 			fmt.Printf("Secret: %s\n", v.Secret) | ||||
| 			fmt.Printf("Description: %s\n", v.Description) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// if -d set, delete the given Application | ||||
| 	if DeleteID != "" { | ||||
| 		err = dg.ApplicationDelete(DeleteID) | ||||
| 		if err != nil { | ||||
| 			fmt.Println("error deleting application,", err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Create a new application. | ||||
| 	ap := &discordgo.Application{} | ||||
| 	ap.Name = AppName | ||||
| 	ap, err = dg.ApplicationCreate(ap) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error creating new applicaiton,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fmt.Printf("Application created successfully:\n") | ||||
| 	fmt.Printf("ID: %s\n", ap.ID) | ||||
| 	fmt.Printf("Name: %s\n", ap.Name) | ||||
| 	fmt.Printf("Secret: %s\n\n", ap.Secret) | ||||
|  | ||||
| 	// 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") | ||||
| 	fmt.Printf("ID: %s\n", bot.ID) | ||||
| 	fmt.Printf("Username: %s\n", bot.Username) | ||||
| 	fmt.Printf("Token: %s\n\n", bot.Token) | ||||
| 	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.") | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										73
									
								
								vendor/github.com/bwmarrin/discordgo/examples/avatar/localfile/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								vendor/github.com/bwmarrin/discordgo/examples/avatar/localfile/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| // Variables used for command line parameters | ||||
| var ( | ||||
| 	Email       string | ||||
| 	Password    string | ||||
| 	Token       string | ||||
| 	Avatar      string | ||||
| 	BotID       string | ||||
| 	BotUsername string | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
|  | ||||
| 	flag.StringVar(&Email, "e", "", "Account Email") | ||||
| 	flag.StringVar(&Password, "p", "", "Account Password") | ||||
| 	flag.StringVar(&Token, "t", "", "Account Token") | ||||
| 	flag.StringVar(&Avatar, "f", "./avatar.jpg", "Avatar File Name") | ||||
| 	flag.Parse() | ||||
| } | ||||
|  | ||||
| func main() { | ||||
|  | ||||
| 	// Create a new Discord session using the provided login information. | ||||
| 	// Use discordgo.New(Token) to just use a token for login. | ||||
| 	dg, err := discordgo.New(Email, Password, Token) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error creating Discord session,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	bot, err := dg.User("@me") | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error fetching the bot details,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	BotID = bot.ID | ||||
| 	BotUsername = bot.Username | ||||
| 	changeAvatar(dg) | ||||
|  | ||||
| 	fmt.Println("Bot is now running.  Press CTRL-C to exit.") | ||||
| 	// Simple way to keep program running until CTRL-C is pressed. | ||||
| 	<-make(chan struct{}) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Helper function to change the avatar | ||||
| func changeAvatar(s *discordgo.Session) { | ||||
| 	img, err := ioutil.ReadFile(Avatar) | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| 	} | ||||
|  | ||||
| 	base64 := base64.StdEncoding.EncodeToString(img) | ||||
|  | ||||
| 	avatar := fmt.Sprintf("data:%s;base64,%s", http.DetectContentType(img), base64) | ||||
|  | ||||
| 	_, err = s.UserUpdate("", "", BotUsername, avatar, "") | ||||
| 	if err != nil { | ||||
| 		fmt.Println(err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										86
									
								
								vendor/github.com/bwmarrin/discordgo/examples/avatar/url/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								vendor/github.com/bwmarrin/discordgo/examples/avatar/url/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base64" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| // Variables used for command line parameters | ||||
| var ( | ||||
| 	Email       string | ||||
| 	Password    string | ||||
| 	Token       string | ||||
| 	URL         string | ||||
| 	BotID       string | ||||
| 	BotUsername string | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
|  | ||||
| 	flag.StringVar(&Email, "e", "", "Account Email") | ||||
| 	flag.StringVar(&Password, "p", "", "Account Password") | ||||
| 	flag.StringVar(&Token, "t", "", "Account Token") | ||||
| 	flag.StringVar(&URL, "l", "http://bwmarrin.github.io/discordgo/img/discordgo.png", "Link to the avatar image") | ||||
| 	flag.Parse() | ||||
| } | ||||
|  | ||||
| func main() { | ||||
|  | ||||
| 	// Create a new Discord session using the provided login information. | ||||
| 	// Use discordgo.New(Token) to just use a token for login. | ||||
| 	dg, err := discordgo.New(Email, Password, Token) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error creating Discord session,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	bot, err := dg.User("@me") | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error fetching the bot details,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	BotID = bot.ID | ||||
| 	BotUsername = bot.Username | ||||
| 	changeAvatar(dg) | ||||
|  | ||||
| 	fmt.Println("Bot is now running.  Press CTRL-C to exit.") | ||||
| 	// Simple way to keep program running until CTRL-C is pressed. | ||||
| 	<-make(chan struct{}) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // Helper function to change the avatar | ||||
| func changeAvatar(s *discordgo.Session) { | ||||
|  | ||||
| 	resp, err := http.Get(URL) | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	base64 := base64.StdEncoding.EncodeToString(img) | ||||
|  | ||||
| 	avatar := fmt.Sprintf("data:%s;base64,%s", http.DetectContentType(img), base64) | ||||
|  | ||||
| 	_, err = s.UserUpdate("", "", BotUsername, avatar, "") | ||||
| 	if err != nil { | ||||
| 		fmt.Println("Error setting the avatar, ", err) | ||||
| 	} | ||||
|  | ||||
| } | ||||
							
								
								
									
										33
									
								
								vendor/github.com/bwmarrin/discordgo/examples/mytoken/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								vendor/github.com/bwmarrin/discordgo/examples/mytoken/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"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() | ||||
| } | ||||
|  | ||||
| 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 | ||||
| 	} | ||||
|  | ||||
| 	fmt.Printf("Your Authentication Token is:\n\n%s\n", dg.Token) | ||||
| } | ||||
							
								
								
									
										58
									
								
								vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| // Variables used for command line parameters | ||||
| var ( | ||||
| 	Email    string | ||||
| 	Password string | ||||
| 	Token    string | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
|  | ||||
| 	flag.StringVar(&Email, "e", "", "Account Email") | ||||
| 	flag.StringVar(&Password, "p", "", "Account Password") | ||||
| 	flag.StringVar(&Token, "t", "", "Account Token") | ||||
| 	flag.Parse() | ||||
| } | ||||
|  | ||||
| func main() { | ||||
|  | ||||
| 	// Create a new Discord session using the provided login information. | ||||
| 	// Use discordgo.New(Token) to just use a token for login. | ||||
| 	dg, err := discordgo.New(Email, Password, Token) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error creating Discord session,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Register messageCreate as a callback for the messageCreate events. | ||||
| 	dg.AddHandler(messageCreate) | ||||
|  | ||||
| 	// Open the websocket and begin listening. | ||||
| 	err = dg.Open() | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error opening connection,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println("Bot is now running.  Press CTRL-C to exit.") | ||||
| 	// Simple way to keep program running until CTRL-C is pressed. | ||||
| 	<-make(chan struct{}) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // 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) { | ||||
|  | ||||
| 	// Print message to stdout. | ||||
| 	fmt.Printf("%20s %20s %20s > %s\n", m.ChannelID, time.Now().Format(time.Stamp), m.Author.Username, m.Content) | ||||
| } | ||||
							
								
								
									
										78
									
								
								vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| // Variables used for command line parameters | ||||
| var ( | ||||
| 	Email    string | ||||
| 	Password string | ||||
| 	Token    string | ||||
| 	BotID    string | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
|  | ||||
| 	flag.StringVar(&Email, "e", "", "Account Email") | ||||
| 	flag.StringVar(&Password, "p", "", "Account Password") | ||||
| 	flag.StringVar(&Token, "t", "", "Account Token") | ||||
| 	flag.Parse() | ||||
| } | ||||
|  | ||||
| func main() { | ||||
|  | ||||
| 	// Create a new Discord session using the provided login information. | ||||
| 	dg, err := discordgo.New(Email, Password, Token) | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error creating Discord session,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Get the account information. | ||||
| 	u, err := dg.User("@me") | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error obtaining account details,", err) | ||||
| 	} | ||||
|  | ||||
| 	// Store the account ID for later use. | ||||
| 	BotID = u.ID | ||||
|  | ||||
| 	// Register messageCreate as a callback for the messageCreate events. | ||||
| 	dg.AddHandler(messageCreate) | ||||
|  | ||||
| 	// Open the websocket and begin listening. | ||||
| 	err = dg.Open() | ||||
| 	if err != nil { | ||||
| 		fmt.Println("error opening connection,", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fmt.Println("Bot is now running.  Press CTRL-C to exit.") | ||||
| 	// Simple way to keep program running until CTRL-C is pressed. | ||||
| 	<-make(chan struct{}) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 	if m.Author.ID == BotID { | ||||
| 		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!") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										95
									
								
								vendor/github.com/bwmarrin/discordgo/logging.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								vendor/github.com/bwmarrin/discordgo/logging.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  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())) | ||||
| } | ||||
| */ | ||||
							
								
								
									
										82
									
								
								vendor/github.com/bwmarrin/discordgo/message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								vendor/github.com/bwmarrin/discordgo/message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  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 ( | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| ) | ||||
|  | ||||
| // 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       string               `json:"timestamp"` | ||||
| 	EditedTimestamp string               `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"` | ||||
| } | ||||
|  | ||||
| // 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"` | ||||
| } | ||||
|  | ||||
| // An MessageEmbed stores data for message embeds. | ||||
| type MessageEmbed struct { | ||||
| 	URL         string `json:"url"` | ||||
| 	Type        string `json:"type"` | ||||
| 	Title       string `json:"title"` | ||||
| 	Description string `json:"description"` | ||||
| 	Thumbnail   *struct { | ||||
| 		URL      string `json:"url"` | ||||
| 		ProxyURL string `json:"proxy_url"` | ||||
| 		Width    int    `json:"width"` | ||||
| 		Height   int    `json:"height"` | ||||
| 	} `json:"thumbnail"` | ||||
| 	Provider *struct { | ||||
| 		URL  string `json:"url"` | ||||
| 		Name string `json:"name"` | ||||
| 	} `json:"provider"` | ||||
| 	Author *struct { | ||||
| 		URL  string `json:"url"` | ||||
| 		Name string `json:"name"` | ||||
| 	} `json:"author"` | ||||
| 	Video *struct { | ||||
| 		URL    string `json:"url"` | ||||
| 		Width  int    `json:"width"` | ||||
| 		Height int    `json:"height"` | ||||
| 	} `json:"video"` | ||||
| } | ||||
|  | ||||
| // ContentWithMentionsReplaced will replace all @<id> mentions with the | ||||
| // username of the mention. | ||||
| func (m *Message) ContentWithMentionsReplaced() string { | ||||
| 	if m.Mentions == nil { | ||||
| 		return m.Content | ||||
| 	} | ||||
| 	content := m.Content | ||||
| 	for _, user := range m.Mentions { | ||||
| 		content = regexp.MustCompile(fmt.Sprintf("<@!?(%s)>", user.ID)).ReplaceAllString(content, "@"+user.Username) | ||||
| 	} | ||||
| 	return content | ||||
| } | ||||
							
								
								
									
										120
									
								
								vendor/github.com/bwmarrin/discordgo/oauth2.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								vendor/github.com/bwmarrin/discordgo/oauth2.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  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"` | ||||
| } | ||||
|  | ||||
| // 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.Request("GET", EndpointApplication(appID), nil) | ||||
| 	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.Request("GET", EndpointApplications, nil) | ||||
| 	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.Request("POST", EndpointApplications, data) | ||||
| 	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.Request("PUT", EndpointApplication(appID), data) | ||||
| 	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.Request("DELETE", EndpointApplication(appID), nil) | ||||
| 	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.Request("POST", EndpointApplicationsBot(appID), nil) | ||||
| 	if err != nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = unmarshal(body, &st) | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										1403
									
								
								vendor/github.com/bwmarrin/discordgo/restapi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1403
									
								
								vendor/github.com/bwmarrin/discordgo/restapi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										746
									
								
								vendor/github.com/bwmarrin/discordgo/state.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										746
									
								
								vendor/github.com/bwmarrin/discordgo/state.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,746 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  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" | ||||
| 	"sync" | ||||
| ) | ||||
|  | ||||
| // ErrNilState is returned when the state is nil. | ||||
| var ErrNilState = errors.New("State not instantiated, please use discordgo.New() or assign Session.State.") | ||||
|  | ||||
| // 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 | ||||
|  | ||||
| 	guildMap   map[string]*Guild | ||||
| 	channelMap map[string]*Channel | ||||
| } | ||||
|  | ||||
| // 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, | ||||
| 		guildMap:      make(map[string]*Guild), | ||||
| 		channelMap:    make(map[string]*Channel), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // OnReady takes a Ready event and updates all internal state. | ||||
| func (s *State) OnReady(r *Ready) error { | ||||
| 	if s == nil { | ||||
| 		return ErrNilState | ||||
| 	} | ||||
|  | ||||
| 	s.Lock() | ||||
| 	defer s.Unlock() | ||||
|  | ||||
| 	s.Ready = *r | ||||
|  | ||||
| 	for _, g := range s.Guilds { | ||||
| 		s.guildMap[g.ID] = g | ||||
|  | ||||
| 		for _, c := range g.Channels { | ||||
| 			c.GuildID = g.ID | ||||
| 			s.channelMap[c.ID] = c | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, c := range s.PrivateChannels { | ||||
| 		s.channelMap[c.ID] = c | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // 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 { | ||||
| 		c.GuildID = guild.ID | ||||
| 		s.channelMap[c.ID] = c | ||||
| 	} | ||||
|  | ||||
| 	// If the guild exists, replace it. | ||||
| 	if g, ok := s.guildMap[guild.ID]; ok { | ||||
| 		// If this guild already exists with data, don't stomp on props. | ||||
| 		if g.Unavailable != nil && !*g.Unavailable { | ||||
| 			guild.Members = g.Members | ||||
| 			guild.Presences = g.Presences | ||||
| 			guild.Channels = g.Channels | ||||
| 			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, errors.New("Guild not found.") | ||||
| } | ||||
|  | ||||
| // 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() | ||||
|  | ||||
| 	for i, m := range guild.Members { | ||||
| 		if m.User.ID == member.User.ID { | ||||
| 			guild.Members[i] = member | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	guild.Members = append(guild.Members, member) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // MemberRemove removes a member from current world state. | ||||
| func (s *State) MemberRemove(member *Member) error { | ||||
| 	if s == nil { | ||||
| 		return ErrNilState | ||||
| 	} | ||||
|  | ||||
| 	guild, err := s.Guild(member.GuildID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	s.Lock() | ||||
| 	defer s.Unlock() | ||||
|  | ||||
| 	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 errors.New("Member not found.") | ||||
| } | ||||
|  | ||||
| // Member gets a member by ID from a guild. | ||||
| func (s *State) Member(guildID, userID string) (*Member, error) { | ||||
| 	if s == nil { | ||||
| 		return nil, ErrNilState | ||||
| 	} | ||||
|  | ||||
| 	guild, err := s.Guild(guildID) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	s.RLock() | ||||
| 	defer s.RUnlock() | ||||
|  | ||||
| 	for _, m := range guild.Members { | ||||
| 		if m.User.ID == userID { | ||||
| 			return m, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil, errors.New("Member not found.") | ||||
| } | ||||
|  | ||||
| // 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 errors.New("Role not found.") | ||||
| } | ||||
|  | ||||
| // 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, errors.New("Role not found.") | ||||
| } | ||||
|  | ||||
| // ChannelAdd adds a guild 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 { | ||||
| 		channel.Messages = c.Messages | ||||
| 		channel.PermissionOverwrites = c.PermissionOverwrites | ||||
|  | ||||
| 		*c = *channel | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if channel.IsPrivate { | ||||
| 		s.PrivateChannels = append(s.PrivateChannels, channel) | ||||
| 	} else { | ||||
| 		guild, ok := s.guildMap[channel.GuildID] | ||||
| 		if !ok { | ||||
| 			return errors.New("Guild for channel not found.") | ||||
| 		} | ||||
|  | ||||
| 		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.IsPrivate { | ||||
| 		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, errors.New("Channel not found.") | ||||
| } | ||||
|  | ||||
| // 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, errors.New("Emoji not found.") | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 			} | ||||
|  | ||||
| 			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 | ||||
| 	} | ||||
|  | ||||
| 	c, err := s.Channel(message.ChannelID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	s.Lock() | ||||
| 	defer s.Unlock() | ||||
|  | ||||
| 	for i, m := range c.Messages { | ||||
| 		if m.ID == message.ID { | ||||
| 			c.Messages = append(c.Messages[:i], c.Messages[i+1:]...) | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return errors.New("Message not found.") | ||||
| } | ||||
|  | ||||
| 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, errors.New("Message not found.") | ||||
| } | ||||
|  | ||||
| // onInterface handles all events related to states. | ||||
| func (s *State) onInterface(se *Session, i interface{}) (err error) { | ||||
| 	if s == nil { | ||||
| 		return ErrNilState | ||||
| 	} | ||||
| 	if !se.StateEnabled { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	switch t := i.(type) { | ||||
| 	case *Ready: | ||||
| 		err = s.OnReady(t) | ||||
| 	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 *VoiceStateUpdate: | ||||
| 		if s.TrackVoice { | ||||
| 			err = s.voiceStateUpdate(t) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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) { | ||||
|  | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	for _, role := range guild.Roles { | ||||
| 		for _, roleID := range member.Roles { | ||||
| 			if role.ID == roleID { | ||||
| 				apermissions |= role.Permissions | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if apermissions&PermissionManageRoles > 0 { | ||||
| 		apermissions |= PermissionAll | ||||
| 	} | ||||
|  | ||||
| 	// 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 { | ||||
| 				apermissions &= ^overwrite.Deny | ||||
| 				apermissions |= overwrite.Allow | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, overwrite := range channel.PermissionOverwrites { | ||||
| 		if overwrite.Type == "member" && overwrite.ID == userID { | ||||
| 			apermissions &= ^overwrite.Deny | ||||
| 			apermissions |= overwrite.Allow | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if apermissions&PermissionManageRoles > 0 { | ||||
| 		apermissions |= PermissionAllChannel | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
							
								
								
									
										521
									
								
								vendor/github.com/bwmarrin/discordgo/structs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										521
									
								
								vendor/github.com/bwmarrin/discordgo/structs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,521 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  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" | ||||
| 	"reflect" | ||||
| 	"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 | ||||
|  | ||||
| 	// 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 | ||||
|  | ||||
| 	// Exposed but should not be modified by User. | ||||
|  | ||||
| 	// Whether the Data Websocket is ready | ||||
| 	DataReady bool // NOTE: Maye be deprecated soon | ||||
|  | ||||
| 	// 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 | ||||
|  | ||||
| 	handlersMu sync.RWMutex | ||||
| 	// This is a mapping of event struct to a reflected value | ||||
| 	// for event handlers. | ||||
| 	// We store the reflected value instead of the function | ||||
| 	// reference as it is more performant, instead of re-reflecting | ||||
| 	// the function each event. | ||||
| 	handlers map[interface{}][]reflect.Value | ||||
|  | ||||
| 	// The websocket connection. | ||||
| 	wsConn *websocket.Conn | ||||
|  | ||||
| 	// When nil, the session is not listening. | ||||
| 	listening chan interface{} | ||||
|  | ||||
| 	// used to deal with rate limits | ||||
| 	// may switch to slices later | ||||
| 	// TODO: performance test map vs slices | ||||
| 	rateLimit rateLimitMutex | ||||
|  | ||||
| 	// sequence tracks the current gateway api websocket sequence number | ||||
| 	sequence int | ||||
|  | ||||
| 	// 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 | ||||
| } | ||||
|  | ||||
| type rateLimitMutex struct { | ||||
| 	sync.Mutex | ||||
| 	url map[string]*sync.Mutex | ||||
| 	// bucket map[string]*sync.Mutex // TODO :) | ||||
| } | ||||
|  | ||||
| // A Resumed struct holds the data received in a RESUMED event | ||||
| type Resumed struct { | ||||
| 	HeartbeatInterval time.Duration `json:"heartbeat_interval"` | ||||
| 	Trace             []string      `json:"_trace"` | ||||
| } | ||||
|  | ||||
| // 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 string   `json:"created_at"` // TODO make timestamp | ||||
| 	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"` | ||||
| } | ||||
|  | ||||
| // 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                 string                 `json:"type"` | ||||
| 	LastMessageID        string                 `json:"last_message_id"` | ||||
| 	Position             int                    `json:"position"` | ||||
| 	Bitrate              int                    `json:"bitrate"` | ||||
| 	IsPrivate            bool                   `json:"is_private"` | ||||
| 	Recipient            *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"` | ||||
| } | ||||
|  | ||||
| // 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                    string            `json:"joined_at"` // make this a timestamp | ||||
| 	Splash                      string            `json:"splash"` | ||||
| 	AfkTimeout                  int               `json:"afk_timeout"` | ||||
| 	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 GuildParams stores all the data needed to update discord guild settings | ||||
| type GuildParams struct { | ||||
| 	Name              string             `json:"name"` | ||||
| 	Region            string             `json:"region"` | ||||
| 	VerificationLevel *VerificationLevel `json:"verification_level"` | ||||
| } | ||||
|  | ||||
| // A Role stores information about Discord guild member roles. | ||||
| type Role struct { | ||||
| 	ID          string `json:"id"` | ||||
| 	Name        string `json:"name"` | ||||
| 	Managed     bool   `json:"managed"` | ||||
| 	Hoist       bool   `json:"hoist"` | ||||
| 	Color       int    `json:"color"` | ||||
| 	Position    int    `json:"position"` | ||||
| 	Permissions int    `json:"permissions"` | ||||
| } | ||||
|  | ||||
| // 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 string `json:"status"` | ||||
| 	Game   *Game  `json:"game"` | ||||
| } | ||||
|  | ||||
| // 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"` | ||||
| } | ||||
|  | ||||
| // 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 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"` | ||||
| } | ||||
|  | ||||
| // 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"` | ||||
| 	AllowEmailFriendRequest bool               `json:"allow_email_friend_request"` | ||||
| 	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"` | ||||
| } | ||||
|  | ||||
| // FriendSourceFlags stores ... TODO :) | ||||
| type FriendSourceFlags struct { | ||||
| 	All           bool `json:"all"` | ||||
| 	MutualGuilds  bool `json:"mutual_guilds"` | ||||
| 	MutualFriends bool `json:"mutual_friends"` | ||||
| } | ||||
|  | ||||
| // An Event provides a basic initial struct for all websocket event. | ||||
| type Event struct { | ||||
| 	Operation int             `json:"op"` | ||||
| 	Sequence  int             `json:"s"` | ||||
| 	Type      string          `json:"t"` | ||||
| 	RawData   json.RawMessage `json:"d"` | ||||
| 	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"` | ||||
| 	HeartbeatInterval time.Duration `json:"heartbeat_interval"` | ||||
| 	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"` | ||||
| } | ||||
|  | ||||
| // 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"` | ||||
| } | ||||
|  | ||||
| // A TypingStart stores data for the typing start websocket event. | ||||
| type TypingStart struct { | ||||
| 	UserID    string `json:"user_id"` | ||||
| 	ChannelID string `json:"channel_id"` | ||||
| 	Timestamp int    `json:"timestamp"` | ||||
| } | ||||
|  | ||||
| // A PresenceUpdate stores data for the presence update websocket event. | ||||
| type PresenceUpdate struct { | ||||
| 	Presence | ||||
| 	GuildID string   `json:"guild_id"` | ||||
| 	Roles   []string `json:"roles"` | ||||
| } | ||||
|  | ||||
| // A MessageAck stores data for the message ack websocket event. | ||||
| type MessageAck struct { | ||||
| 	MessageID string `json:"message_id"` | ||||
| 	ChannelID string `json:"channel_id"` | ||||
| } | ||||
|  | ||||
| // A GuildIntegrationsUpdate stores data for the guild integrations update | ||||
| // websocket event. | ||||
| type GuildIntegrationsUpdate struct { | ||||
| 	GuildID string `json:"guild_id"` | ||||
| } | ||||
|  | ||||
| // A GuildRole stores data for guild role websocket events. | ||||
| type GuildRole struct { | ||||
| 	Role    *Role  `json:"role"` | ||||
| 	GuildID string `json:"guild_id"` | ||||
| } | ||||
|  | ||||
| // A GuildRoleDelete stores data for the guild role delete websocket event. | ||||
| type GuildRoleDelete struct { | ||||
| 	RoleID  string `json:"role_id"` | ||||
| 	GuildID string `json:"guild_id"` | ||||
| } | ||||
|  | ||||
| // A GuildBan stores data for a guild ban. | ||||
| type GuildBan struct { | ||||
| 	User    *User  `json:"user"` | ||||
| 	GuildID string `json:"guild_id"` | ||||
| } | ||||
|  | ||||
| // A GuildEmojisUpdate stores data for a guild emoji update event. | ||||
| type GuildEmojisUpdate struct { | ||||
| 	GuildID string   `json:"guild_id"` | ||||
| 	Emojis  []*Emoji `json:"emojis"` | ||||
| } | ||||
|  | ||||
| // 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"` | ||||
| } | ||||
|  | ||||
| // Constants for the different bit offsets of text channel permissions | ||||
| const ( | ||||
| 	PermissionReadMessages = 1 << (iota + 10) | ||||
| 	PermissionSendMessages | ||||
| 	PermissionSendTTSMessages | ||||
| 	PermissionManageMessages | ||||
| 	PermissionEmbedLinks | ||||
| 	PermissionAttachFiles | ||||
| 	PermissionReadMessageHistory | ||||
| 	PermissionMentionEveryone | ||||
| ) | ||||
|  | ||||
| // Constants for the different bit offsets of voice permissions | ||||
| const ( | ||||
| 	PermissionVoiceConnect = 1 << (iota + 20) | ||||
| 	PermissionVoiceSpeak | ||||
| 	PermissionVoiceMuteMembers | ||||
| 	PermissionVoiceDeafenMembers | ||||
| 	PermissionVoiceMoveMembers | ||||
| 	PermissionVoiceUseVAD | ||||
| ) | ||||
|  | ||||
| // Constants for the different bit offsets of general permissions | ||||
| const ( | ||||
| 	PermissionCreateInstantInvite = 1 << iota | ||||
| 	PermissionKickMembers | ||||
| 	PermissionBanMembers | ||||
| 	PermissionManageRoles | ||||
| 	PermissionManageChannels | ||||
| 	PermissionManageServer | ||||
|  | ||||
| 	PermissionAllText = PermissionReadMessages | | ||||
| 		PermissionSendMessages | | ||||
| 		PermissionSendTTSMessages | | ||||
| 		PermissionManageMessages | | ||||
| 		PermissionEmbedLinks | | ||||
| 		PermissionAttachFiles | | ||||
| 		PermissionReadMessageHistory | | ||||
| 		PermissionMentionEveryone | ||||
| 	PermissionAllVoice = PermissionVoiceConnect | | ||||
| 		PermissionVoiceSpeak | | ||||
| 		PermissionVoiceMuteMembers | | ||||
| 		PermissionVoiceDeafenMembers | | ||||
| 		PermissionVoiceMoveMembers | | ||||
| 		PermissionVoiceUseVAD | ||||
| 	PermissionAllChannel = PermissionAllText | | ||||
| 		PermissionAllVoice | | ||||
| 		PermissionCreateInstantInvite | | ||||
| 		PermissionManageRoles | | ||||
| 		PermissionManageChannels | ||||
| 	PermissionAll = PermissionAllChannel | | ||||
| 		PermissionKickMembers | | ||||
| 		PermissionBanMembers | | ||||
| 		PermissionManageServer | ||||
| ) | ||||
							
								
								
									
										853
									
								
								vendor/github.com/bwmarrin/discordgo/voice.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										853
									
								
								vendor/github.com/bwmarrin/discordgo/voice.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,853 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  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" | ||||
| 	"runtime" | ||||
| 	"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() | ||||
| 	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.wsMutex.Lock() | ||||
| 		err = v.session.wsConn.WriteJSON(data) | ||||
| 		v.wsMutex.Unlock() | ||||
| 		v.sessionID = "" | ||||
| 	} | ||||
|  | ||||
| 	// Close websocket and udp connections | ||||
| 	v.Close() | ||||
|  | ||||
| 	v.log(LogInformational, "Deleting VoiceConnection %s", v.GuildID) | ||||
| 	delete(v.session.VoiceConnections, v.GuildID) | ||||
|  | ||||
| 	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. | ||||
| 		err := v.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) | ||||
| 		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 { | ||||
| 		if v.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) | ||||
| 		} | ||||
|  | ||||
| 		// Send the ready event | ||||
| 		v.connected <- true | ||||
| 		return | ||||
|  | ||||
| 	case 3: // HEARTBEAT response | ||||
| 		// add code to use this to track latency? | ||||
| 		return | ||||
|  | ||||
| 	case 4: // udp encryption secret key | ||||
| 		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(LogError, "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) | ||||
| 	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 though 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) | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	runtime.LockOSThread() | ||||
|  | ||||
| 	// VoiceConnection is now ready to receive audio packets | ||||
| 	// TODO: this needs reviewed as I think there must be a better way. | ||||
| 	v.Ready = true | ||||
| 	defer func() { v.Ready = false }() | ||||
|  | ||||
| 	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))) | ||||
| 	for { | ||||
|  | ||||
| 		// Get data from chan.  If chan is closed, return. | ||||
| 		select { | ||||
| 		case <-close: | ||||
| 			return | ||||
| 		case recvbuf, ok = <-opus: | ||||
| 			if !ok { | ||||
| 				return | ||||
| 			} | ||||
| 			// else, continue loop | ||||
| 		} | ||||
|  | ||||
| 		if !v.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) | ||||
| 		sendbuf := secretbox.Seal(udpHeader, recvbuf, &nonce, &v.op4.SecretKey) | ||||
|  | ||||
| 		// 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 | ||||
| 	} | ||||
|  | ||||
| 	p := Packet{} | ||||
| 	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 { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// build a audio packet struct | ||||
| 		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 c != nil { | ||||
| 			c <- &p | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| 		} | ||||
|  | ||||
| 		// 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) | ||||
| 		} | ||||
|  | ||||
| 		v.log(LogInformational, "error reconnecting to channel %s, %s", v.ChannelID, err) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										679
									
								
								vendor/github.com/bwmarrin/discordgo/wsapi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										679
									
								
								vendor/github.com/bwmarrin/discordgo/wsapi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,679 @@ | ||||
| // Discordgo - Discord bindings for Go | ||||
| // Available at https://github.com/bwmarrin/discordgo | ||||
|  | ||||
| // Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // This file contains low level functions for interacting with the Discord | ||||
| // data websocket interface. | ||||
|  | ||||
| package discordgo | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"compress/zlib" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"reflect" | ||||
| 	"runtime" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gorilla/websocket" | ||||
| ) | ||||
|  | ||||
| type resumePacket struct { | ||||
| 	Op   int `json:"op"` | ||||
| 	Data struct { | ||||
| 		Token     string `json:"token"` | ||||
| 		SessionID string `json:"session_id"` | ||||
| 		Sequence  int    `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() | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	if s.wsConn != nil { | ||||
| 		err = errors.New("Web socket already opened.") | ||||
| 		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 = fmt.Sprintf("%s?v=4&encoding=json", s.gateway) | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| 	} | ||||
|  | ||||
| 	if s.sessionID != "" && s.sequence > 0 { | ||||
|  | ||||
| 		p := resumePacket{} | ||||
| 		p.Op = 6 | ||||
| 		p.Data.Token = s.Token | ||||
| 		p.Data.SessionID = s.sessionID | ||||
| 		p.Data.Sequence = s.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.Unlock() | ||||
|  | ||||
| 	s.initialize() | ||||
| 	s.log(LogInformational, "emit connect event") | ||||
| 	s.handle(&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 int `json:"d"` | ||||
| } | ||||
|  | ||||
| // 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{}, i time.Duration) { | ||||
|  | ||||
| 	s.log(LogInformational, "called") | ||||
|  | ||||
| 	if listening == nil || wsConn == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var err error | ||||
| 	ticker := time.NewTicker(i * time.Millisecond) | ||||
|  | ||||
| 	for { | ||||
|  | ||||
| 		s.log(LogInformational, "sending gateway websocket heartbeat seq %d", s.sequence) | ||||
| 		s.wsMutex.Lock() | ||||
| 		err = wsConn.WriteJSON(heartbeatOp{1, s.sequence}) | ||||
| 		s.wsMutex.Unlock() | ||||
| 		if err != nil { | ||||
| 			s.log(LogError, "error sending heartbeat to gateway %s, %s", s.gateway, err) | ||||
| 			s.Lock() | ||||
| 			s.DataReady = false | ||||
| 			s.Unlock() | ||||
| 			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:"idle_since"` | ||||
| 	Game      *Game `json:"game"` | ||||
| } | ||||
|  | ||||
| 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 errors.New("no websocket connection exists") | ||||
| 	} | ||||
|  | ||||
| 	var usd updateStatusData | ||||
| 	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, "") | ||||
| } | ||||
|  | ||||
| // 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, 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 { | ||||
| 		// TODO | ||||
| 	} | ||||
|  | ||||
| 	// 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 | ||||
| 	} | ||||
|  | ||||
| 	// 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 | ||||
| 	s.sequence = e.Sequence | ||||
|  | ||||
| 	// Map event to registered event handlers and pass it along | ||||
| 	// to any registered functions | ||||
| 	i := eventToInterface[e.Type] | ||||
| 	if i != nil { | ||||
|  | ||||
| 		// Create a new instance of the event type. | ||||
| 		i = reflect.New(reflect.TypeOf(i)).Interface() | ||||
|  | ||||
| 		// Attempt to unmarshal our event. | ||||
| 		if err = json.Unmarshal(e.RawData, i); 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. | ||||
| 		go s.handle(i) | ||||
|  | ||||
| 	} else { | ||||
| 		s.log(LogWarning, "unknown event: Op: %d, Seq: %d, Type: %s, Data: %s", e.Operation, e.Sequence, e.Type, string(e.RawData)) | ||||
| 	} | ||||
|  | ||||
| 	// Emit event to the OnEvent handler | ||||
| 	e.Struct = i | ||||
| 	go s.handle(e) | ||||
| } | ||||
|  | ||||
| // ------------------------------------------------------------------------------------------------ | ||||
| // Code related to voice connections that initiate over the data websocket | ||||
| // ------------------------------------------------------------------------------------------------ | ||||
|  | ||||
| // A VoiceServerUpdate stores the data received during the Voice Server Update | ||||
| // data websocket event. This data is used during the initial Voice Channel | ||||
| // join handshaking. | ||||
| type VoiceServerUpdate struct { | ||||
| 	Token    string `json:"token"` | ||||
| 	GuildID  string `json:"guild_id"` | ||||
| 	Endpoint string `json:"endpoint"` | ||||
| } | ||||
|  | ||||
| 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") | ||||
|  | ||||
| 	voice, _ = s.VoiceConnections[gID] | ||||
|  | ||||
| 	if voice == nil { | ||||
| 		voice = &VoiceConnection{} | ||||
| 		s.VoiceConnections[gID] = voice | ||||
| 	} | ||||
|  | ||||
| 	voice.GuildID = gID | ||||
| 	voice.ChannelID = cID | ||||
| 	voice.deaf = deaf | ||||
| 	voice.mute = mute | ||||
| 	voice.session = s | ||||
|  | ||||
| 	// 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(se *Session, 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 | ||||
| 	voice, exists := s.VoiceConnections[st.GuildID] | ||||
| 	if !exists { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Need to have this happen at login and store it in the Session | ||||
| 	// TODO : This should be done upon connecting to Discord, or | ||||
| 	// be moved to a small helper function | ||||
| 	self, err := s.User("@me") // TODO: move to Login/New | ||||
| 	if err != nil { | ||||
| 		log.Println(err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// We only care about events that are about us | ||||
| 	if st.UserID != self.ID { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Store the SessionID for later use. | ||||
| 	voice.UserID = self.ID // TODO: Review | ||||
| 	voice.sessionID = st.SessionID | ||||
| } | ||||
|  | ||||
| // 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(se *Session, st *VoiceServerUpdate) { | ||||
|  | ||||
| 	s.log(LogInformational, "called") | ||||
|  | ||||
| 	voice, exists := s.VoiceConnections[st.GuildID] | ||||
|  | ||||
| 	// 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.token = st.Token | ||||
| 	voice.endpoint = st.Endpoint | ||||
| 	voice.GuildID = st.GuildID | ||||
|  | ||||
| 	// 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 errors.New("ShardID must be less than ShardCount") | ||||
| 		} | ||||
|  | ||||
| 		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. | ||||
| 				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 | ||||
| 			} | ||||
|  | ||||
| 			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. | ||||
| 		err := s.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) | ||||
| 		if err != nil { | ||||
| 			s.log(LogError, "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(LogError, "error closing websocket, %s", err) | ||||
| 		} | ||||
|  | ||||
| 		s.wsConn = nil | ||||
| 	} | ||||
|  | ||||
| 	s.Unlock() | ||||
|  | ||||
| 	s.log(LogInformational, "emit disconnect event") | ||||
| 	s.handle(&Disconnect{}) | ||||
|  | ||||
| 	return | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Wim
					Wim