forked from lug/matterbridge
		
	Use own forks for logrus-prefixed-formatter and discordgo
This commit is contained in:
		| @@ -8,9 +8,9 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/fsnotify/fsnotify" | 	"github.com/fsnotify/fsnotify" | ||||||
|  | 	prefixed "github.com/matterbridge/logrus-prefixed-formatter" | ||||||
| 	log "github.com/sirupsen/logrus" | 	log "github.com/sirupsen/logrus" | ||||||
| 	"github.com/spf13/viper" | 	"github.com/spf13/viper" | ||||||
| 	prefixed "github.com/x-cray/logrus-prefixed-formatter" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ import ( | |||||||
| 	"github.com/42wim/matterbridge/bridge" | 	"github.com/42wim/matterbridge/bridge" | ||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/42wim/matterbridge/bridge/helper" | 	"github.com/42wim/matterbridge/bridge/helper" | ||||||
| 	"github.com/bwmarrin/discordgo" | 	"github.com/matterbridge/discordgo" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const MessageLength = 1950 | const MessageLength = 1950 | ||||||
|   | |||||||
| @@ -9,8 +9,8 @@ import ( | |||||||
| 	"github.com/42wim/matterbridge/bridge/config" | 	"github.com/42wim/matterbridge/bridge/config" | ||||||
| 	"github.com/42wim/matterbridge/gateway" | 	"github.com/42wim/matterbridge/gateway" | ||||||
| 	"github.com/google/gops/agent" | 	"github.com/google/gops/agent" | ||||||
|  | 	prefixed "github.com/matterbridge/logrus-prefixed-formatter" | ||||||
| 	log "github.com/sirupsen/logrus" | 	log "github.com/sirupsen/logrus" | ||||||
| 	prefixed "github.com/x-cray/logrus-prefixed-formatter" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
|   | |||||||
| @@ -13,8 +13,8 @@ import ( | |||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	prefixed "github.com/matterbridge/logrus-prefixed-formatter" | ||||||
| 	log "github.com/sirupsen/logrus" | 	log "github.com/sirupsen/logrus" | ||||||
| 	prefixed "github.com/x-cray/logrus-prefixed-formatter" |  | ||||||
|  |  | ||||||
| 	"github.com/gorilla/websocket" | 	"github.com/gorilla/websocket" | ||||||
| 	"github.com/hashicorp/golang-lru" | 	"github.com/hashicorp/golang-lru" | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								vendor/github.com/matterbridge/discordgo/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/matterbridge/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. | ||||||
|  |  | ||||||
							
								
								
									
										146
									
								
								vendor/github.com/matterbridge/discordgo/discord.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								vendor/github.com/matterbridge/discordgo/discord.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | |||||||
|  | // 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 ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) | ||||||
|  | const VERSION = "0.18.0" | ||||||
|  |  | ||||||
|  | // ErrMFA will be risen by New when the user has 2FA. | ||||||
|  | var ErrMFA = errors.New("account has 2FA enabled") | ||||||
|  |  | ||||||
|  | // New creates a new Discord session and will automate some startup | ||||||
|  | // tasks if given enough information to do so.  Currently you can pass zero | ||||||
|  | // arguments and it will return an empty Discord session. | ||||||
|  | // There are 3 ways to call New: | ||||||
|  | //     With a single auth token - All requests will use the token blindly, | ||||||
|  | //         no verification of the token will be done and requests may fail. | ||||||
|  | //         IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT ` | ||||||
|  | //         eg: `"Bot <token>"` | ||||||
|  | //     With an email and password - Discord will sign in with the provided | ||||||
|  | //         credentials. | ||||||
|  | //     With an email, password and auth token - Discord will verify the auth | ||||||
|  | //         token, if it is invalid it will sign in with the provided | ||||||
|  | //         credentials. This is the Discord recommended way to sign in. | ||||||
|  | // | ||||||
|  | // NOTE: While email/pass authentication is supported by DiscordGo it is | ||||||
|  | // HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token | ||||||
|  | // and then use that authentication token for all future connections. | ||||||
|  | // Also, doing any form of automation with a user (non Bot) account may result | ||||||
|  | // in that account being permanently banned from Discord. | ||||||
|  | func New(args ...interface{}) (s *Session, err error) { | ||||||
|  |  | ||||||
|  | 	// Create an empty Session interface. | ||||||
|  | 	s = &Session{ | ||||||
|  | 		State:                  NewState(), | ||||||
|  | 		Ratelimiter:            NewRatelimiter(), | ||||||
|  | 		StateEnabled:           true, | ||||||
|  | 		Compress:               true, | ||||||
|  | 		ShouldReconnectOnError: true, | ||||||
|  | 		ShardID:                0, | ||||||
|  | 		ShardCount:             1, | ||||||
|  | 		MaxRestRetries:         3, | ||||||
|  | 		Client:                 &http.Client{Timeout: (20 * time.Second)}, | ||||||
|  | 		sequence:               new(int64), | ||||||
|  | 		LastHeartbeatAck:       time.Now().UTC(), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If no arguments are passed return the empty Session interface. | ||||||
|  | 	if args == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Variables used below when parsing func arguments | ||||||
|  | 	var auth, pass string | ||||||
|  |  | ||||||
|  | 	// Parse passed arguments | ||||||
|  | 	for _, arg := range args { | ||||||
|  |  | ||||||
|  | 		switch v := arg.(type) { | ||||||
|  |  | ||||||
|  | 		case []string: | ||||||
|  | 			if len(v) > 3 { | ||||||
|  | 				err = fmt.Errorf("too many string parameters provided") | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// First string is either token or username | ||||||
|  | 			if len(v) > 0 { | ||||||
|  | 				auth = v[0] | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// If second string exists, it must be a password. | ||||||
|  | 			if len(v) > 1 { | ||||||
|  | 				pass = v[1] | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// If third string exists, it must be an auth token. | ||||||
|  | 			if len(v) > 2 { | ||||||
|  | 				s.Token = v[2] | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		case string: | ||||||
|  | 			// First string must be either auth token or username. | ||||||
|  | 			// Second string must be a password. | ||||||
|  | 			// Only 2 input strings are supported. | ||||||
|  |  | ||||||
|  | 			if auth == "" { | ||||||
|  | 				auth = v | ||||||
|  | 			} else if pass == "" { | ||||||
|  | 				pass = v | ||||||
|  | 			} else if s.Token == "" { | ||||||
|  | 				s.Token = v | ||||||
|  | 			} else { | ||||||
|  | 				err = fmt.Errorf("too many string parameters provided") | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			//		case Config: | ||||||
|  | 			// TODO: Parse configuration struct | ||||||
|  |  | ||||||
|  | 		default: | ||||||
|  | 			err = fmt.Errorf("unsupported parameter type provided") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If only one string was provided, assume it is an auth token. | ||||||
|  | 	// Otherwise get auth token from Discord, if a token was specified | ||||||
|  | 	// Discord will verify it for free, or log the user in if it is | ||||||
|  | 	// invalid. | ||||||
|  | 	if pass == "" { | ||||||
|  | 		s.Token = auth | ||||||
|  | 	} else { | ||||||
|  | 		err = s.Login(auth, pass) | ||||||
|  | 		if err != nil || s.Token == "" { | ||||||
|  | 			if s.MFA { | ||||||
|  | 				err = ErrMFA | ||||||
|  | 			} else { | ||||||
|  | 				err = fmt.Errorf("Unable to fetch discord authentication token. %v", err) | ||||||
|  | 			} | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// The Session is now able to have RestAPI methods called on it. | ||||||
|  | 	// It is recommended that you now call Open() so that events will trigger. | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										136
									
								
								vendor/github.com/matterbridge/discordgo/endpoints.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								vendor/github.com/matterbridge/discordgo/endpoints.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | // 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 | ||||||
|  |  | ||||||
|  | // APIVersion is the Discord API version used for the REST and Websocket API. | ||||||
|  | var APIVersion = "6" | ||||||
|  |  | ||||||
|  | // Known Discord API Endpoints. | ||||||
|  | var ( | ||||||
|  | 	EndpointStatus     = "https://status.discordapp.com/api/v2/" | ||||||
|  | 	EndpointSm         = EndpointStatus + "scheduled-maintenances/" | ||||||
|  | 	EndpointSmActive   = EndpointSm + "active.json" | ||||||
|  | 	EndpointSmUpcoming = EndpointSm + "upcoming.json" | ||||||
|  |  | ||||||
|  | 	EndpointDiscord    = "https://discordapp.com/" | ||||||
|  | 	EndpointAPI        = EndpointDiscord + "api/v" + APIVersion + "/" | ||||||
|  | 	EndpointGuilds     = EndpointAPI + "guilds/" | ||||||
|  | 	EndpointChannels   = EndpointAPI + "channels/" | ||||||
|  | 	EndpointUsers      = EndpointAPI + "users/" | ||||||
|  | 	EndpointGateway    = EndpointAPI + "gateway" | ||||||
|  | 	EndpointGatewayBot = EndpointGateway + "/bot" | ||||||
|  | 	EndpointWebhooks   = EndpointAPI + "webhooks/" | ||||||
|  |  | ||||||
|  | 	EndpointCDN             = "https://cdn.discordapp.com/" | ||||||
|  | 	EndpointCDNAttachments  = EndpointCDN + "attachments/" | ||||||
|  | 	EndpointCDNAvatars      = EndpointCDN + "avatars/" | ||||||
|  | 	EndpointCDNIcons        = EndpointCDN + "icons/" | ||||||
|  | 	EndpointCDNSplashes     = EndpointCDN + "splashes/" | ||||||
|  | 	EndpointCDNChannelIcons = EndpointCDN + "channel-icons/" | ||||||
|  |  | ||||||
|  | 	EndpointAuth           = EndpointAPI + "auth/" | ||||||
|  | 	EndpointLogin          = EndpointAuth + "login" | ||||||
|  | 	EndpointLogout         = EndpointAuth + "logout" | ||||||
|  | 	EndpointVerify         = EndpointAuth + "verify" | ||||||
|  | 	EndpointVerifyResend   = EndpointAuth + "verify/resend" | ||||||
|  | 	EndpointForgotPassword = EndpointAuth + "forgot" | ||||||
|  | 	EndpointResetPassword  = EndpointAuth + "reset" | ||||||
|  | 	EndpointRegister       = EndpointAuth + "register" | ||||||
|  |  | ||||||
|  | 	EndpointVoice        = EndpointAPI + "/voice/" | ||||||
|  | 	EndpointVoiceRegions = EndpointVoice + "regions" | ||||||
|  | 	EndpointVoiceIce     = EndpointVoice + "ice" | ||||||
|  |  | ||||||
|  | 	EndpointTutorial           = EndpointAPI + "tutorial/" | ||||||
|  | 	EndpointTutorialIndicators = EndpointTutorial + "indicators" | ||||||
|  |  | ||||||
|  | 	EndpointTrack        = EndpointAPI + "track" | ||||||
|  | 	EndpointSso          = EndpointAPI + "sso" | ||||||
|  | 	EndpointReport       = EndpointAPI + "report" | ||||||
|  | 	EndpointIntegrations = EndpointAPI + "integrations" | ||||||
|  |  | ||||||
|  | 	EndpointUser               = func(uID string) string { return EndpointUsers + uID } | ||||||
|  | 	EndpointUserAvatar         = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" } | ||||||
|  | 	EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" } | ||||||
|  | 	EndpointUserSettings       = func(uID string) string { return EndpointUsers + uID + "/settings" } | ||||||
|  | 	EndpointUserGuilds         = func(uID string) string { return EndpointUsers + uID + "/guilds" } | ||||||
|  | 	EndpointUserGuild          = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } | ||||||
|  | 	EndpointUserGuildSettings  = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID + "/settings" } | ||||||
|  | 	EndpointUserChannels       = func(uID string) string { return EndpointUsers + uID + "/channels" } | ||||||
|  | 	EndpointUserDevices        = func(uID string) string { return EndpointUsers + uID + "/devices" } | ||||||
|  | 	EndpointUserConnections    = func(uID string) string { return EndpointUsers + uID + "/connections" } | ||||||
|  | 	EndpointUserNotes          = func(uID string) string { return EndpointUsers + "@me/notes/" + uID } | ||||||
|  |  | ||||||
|  | 	EndpointGuild                = func(gID string) string { return EndpointGuilds + gID } | ||||||
|  | 	EndpointGuildChannels        = func(gID string) string { return EndpointGuilds + gID + "/channels" } | ||||||
|  | 	EndpointGuildMembers         = func(gID string) string { return EndpointGuilds + gID + "/members" } | ||||||
|  | 	EndpointGuildMember          = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID } | ||||||
|  | 	EndpointGuildMemberRole      = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID } | ||||||
|  | 	EndpointGuildBans            = func(gID string) string { return EndpointGuilds + gID + "/bans" } | ||||||
|  | 	EndpointGuildBan             = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID } | ||||||
|  | 	EndpointGuildIntegrations    = func(gID string) string { return EndpointGuilds + gID + "/integrations" } | ||||||
|  | 	EndpointGuildIntegration     = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID } | ||||||
|  | 	EndpointGuildIntegrationSync = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID + "/sync" } | ||||||
|  | 	EndpointGuildRoles           = func(gID string) string { return EndpointGuilds + gID + "/roles" } | ||||||
|  | 	EndpointGuildRole            = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID } | ||||||
|  | 	EndpointGuildInvites         = func(gID string) string { return EndpointGuilds + gID + "/invites" } | ||||||
|  | 	EndpointGuildEmbed           = func(gID string) string { return EndpointGuilds + gID + "/embed" } | ||||||
|  | 	EndpointGuildPrune           = func(gID string) string { return EndpointGuilds + gID + "/prune" } | ||||||
|  | 	EndpointGuildIcon            = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" } | ||||||
|  | 	EndpointGuildSplash          = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" } | ||||||
|  | 	EndpointGuildWebhooks        = func(gID string) string { return EndpointGuilds + gID + "/webhooks" } | ||||||
|  |  | ||||||
|  | 	EndpointChannel                   = func(cID string) string { return EndpointChannels + cID } | ||||||
|  | 	EndpointChannelPermissions        = func(cID string) string { return EndpointChannels + cID + "/permissions" } | ||||||
|  | 	EndpointChannelPermission         = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID } | ||||||
|  | 	EndpointChannelInvites            = func(cID string) string { return EndpointChannels + cID + "/invites" } | ||||||
|  | 	EndpointChannelTyping             = func(cID string) string { return EndpointChannels + cID + "/typing" } | ||||||
|  | 	EndpointChannelMessages           = func(cID string) string { return EndpointChannels + cID + "/messages" } | ||||||
|  | 	EndpointChannelMessage            = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID } | ||||||
|  | 	EndpointChannelMessageAck         = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" } | ||||||
|  | 	EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" } | ||||||
|  | 	EndpointChannelMessagesPins       = func(cID string) string { return EndpointChannel(cID) + "/pins" } | ||||||
|  | 	EndpointChannelMessagePin         = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } | ||||||
|  |  | ||||||
|  | 	EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" } | ||||||
|  |  | ||||||
|  | 	EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" } | ||||||
|  | 	EndpointWebhook         = func(wID string) string { return EndpointWebhooks + wID } | ||||||
|  | 	EndpointWebhookToken    = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token } | ||||||
|  |  | ||||||
|  | 	EndpointMessageReactionsAll = func(cID, mID string) string { | ||||||
|  | 		return EndpointChannelMessage(cID, mID) + "/reactions" | ||||||
|  | 	} | ||||||
|  | 	EndpointMessageReactions = func(cID, mID, eID string) string { | ||||||
|  | 		return EndpointChannelMessage(cID, mID) + "/reactions/" + eID | ||||||
|  | 	} | ||||||
|  | 	EndpointMessageReaction = func(cID, mID, eID, uID string) string { | ||||||
|  | 		return EndpointMessageReactions(cID, mID, eID) + "/" + uID | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	EndpointRelationships       = func() string { return EndpointUsers + "@me" + "/relationships" } | ||||||
|  | 	EndpointRelationship        = func(uID string) string { return EndpointRelationships() + "/" + uID } | ||||||
|  | 	EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" } | ||||||
|  |  | ||||||
|  | 	EndpointGuildCreate = EndpointAPI + "guilds" | ||||||
|  |  | ||||||
|  | 	EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID } | ||||||
|  |  | ||||||
|  | 	EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" } | ||||||
|  |  | ||||||
|  | 	EndpointEmoji = func(eID string) string { return 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" } | ||||||
|  | ) | ||||||
							
								
								
									
										238
									
								
								vendor/github.com/matterbridge/discordgo/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								vendor/github.com/matterbridge/discordgo/event.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,238 @@ | |||||||
|  | package discordgo | ||||||
|  |  | ||||||
|  | // EventHandler is an interface for Discord events. | ||||||
|  | type EventHandler interface { | ||||||
|  | 	// Type returns the type of event this handler belongs to. | ||||||
|  | 	Type() string | ||||||
|  |  | ||||||
|  | 	// Handle is called whenever an event of Type() happens. | ||||||
|  | 	// It is the receivers responsibility to type assert that the interface | ||||||
|  | 	// is the expected struct. | ||||||
|  | 	Handle(*Session, interface{}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EventInterfaceProvider is an interface for providing empty interfaces for | ||||||
|  | // Discord events. | ||||||
|  | type EventInterfaceProvider interface { | ||||||
|  | 	// Type is the type of event this handler belongs to. | ||||||
|  | 	Type() string | ||||||
|  |  | ||||||
|  | 	// New returns a new instance of the struct this event handler handles. | ||||||
|  | 	// This is called once per event. | ||||||
|  | 	// The struct is provided to all handlers of the same Type(). | ||||||
|  | 	New() interface{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // interfaceEventType is the event handler type for interface{} events. | ||||||
|  | const interfaceEventType = "__INTERFACE__" | ||||||
|  |  | ||||||
|  | // interfaceEventHandler is an event handler for interface{} events. | ||||||
|  | type interfaceEventHandler func(*Session, interface{}) | ||||||
|  |  | ||||||
|  | // Type returns the event type for interface{} events. | ||||||
|  | func (eh interfaceEventHandler) Type() string { | ||||||
|  | 	return interfaceEventType | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Handle is the handler for an interface{} event. | ||||||
|  | func (eh interfaceEventHandler) Handle(s *Session, i interface{}) { | ||||||
|  | 	eh(s, i) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var registeredInterfaceProviders = map[string]EventInterfaceProvider{} | ||||||
|  |  | ||||||
|  | // registerInterfaceProvider registers a provider so that DiscordGo can | ||||||
|  | // access it's New() method. | ||||||
|  | func registerInterfaceProvider(eh EventInterfaceProvider) { | ||||||
|  | 	if _, ok := registeredInterfaceProviders[eh.Type()]; ok { | ||||||
|  | 		return | ||||||
|  | 		// XXX: | ||||||
|  | 		// if we should error here, we need to do something with it. | ||||||
|  | 		// fmt.Errorf("event %s already registered", eh.Type()) | ||||||
|  | 	} | ||||||
|  | 	registeredInterfaceProviders[eh.Type()] = eh | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // eventHandlerInstance is a wrapper around an event handler, as functions | ||||||
|  | // cannot be compared directly. | ||||||
|  | type eventHandlerInstance struct { | ||||||
|  | 	eventHandler EventHandler | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // addEventHandler adds an event handler that will be fired anytime | ||||||
|  | // the Discord WSAPI matching eventHandler.Type() fires. | ||||||
|  | func (s *Session) addEventHandler(eventHandler EventHandler) func() { | ||||||
|  | 	s.handlersMu.Lock() | ||||||
|  | 	defer s.handlersMu.Unlock() | ||||||
|  |  | ||||||
|  | 	if s.handlers == nil { | ||||||
|  | 		s.handlers = map[string][]*eventHandlerInstance{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ehi := &eventHandlerInstance{eventHandler} | ||||||
|  | 	s.handlers[eventHandler.Type()] = append(s.handlers[eventHandler.Type()], ehi) | ||||||
|  |  | ||||||
|  | 	return func() { | ||||||
|  | 		s.removeEventHandlerInstance(eventHandler.Type(), ehi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // addEventHandler adds an event handler that will be fired the next time | ||||||
|  | // the Discord WSAPI matching eventHandler.Type() fires. | ||||||
|  | func (s *Session) addEventHandlerOnce(eventHandler EventHandler) func() { | ||||||
|  | 	s.handlersMu.Lock() | ||||||
|  | 	defer s.handlersMu.Unlock() | ||||||
|  |  | ||||||
|  | 	if s.onceHandlers == nil { | ||||||
|  | 		s.onceHandlers = map[string][]*eventHandlerInstance{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ehi := &eventHandlerInstance{eventHandler} | ||||||
|  | 	s.onceHandlers[eventHandler.Type()] = append(s.onceHandlers[eventHandler.Type()], ehi) | ||||||
|  |  | ||||||
|  | 	return func() { | ||||||
|  | 		s.removeEventHandlerInstance(eventHandler.Type(), ehi) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddHandler allows you to add an event handler that will be fired anytime | ||||||
|  | // the Discord WSAPI event that matches the function fires. | ||||||
|  | // events.go contains all the Discord WSAPI events that can be fired. | ||||||
|  | // eg: | ||||||
|  | //     Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||||
|  | //     }) | ||||||
|  | // | ||||||
|  | // or: | ||||||
|  | //     Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) { | ||||||
|  | //     }) | ||||||
|  | // The return value of this method is a function, that when called will remove the | ||||||
|  | // event handler. | ||||||
|  | func (s *Session) AddHandler(handler interface{}) func() { | ||||||
|  | 	eh := handlerForInterface(handler) | ||||||
|  |  | ||||||
|  | 	if eh == nil { | ||||||
|  | 		s.log(LogError, "Invalid handler type, handler will never be called") | ||||||
|  | 		return func() {} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return s.addEventHandler(eh) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddHandlerOnce allows you to add an event handler that will be fired the next time | ||||||
|  | // the Discord WSAPI event that matches the function fires. | ||||||
|  | // See AddHandler for more details. | ||||||
|  | func (s *Session) AddHandlerOnce(handler interface{}) func() { | ||||||
|  | 	eh := handlerForInterface(handler) | ||||||
|  |  | ||||||
|  | 	if eh == nil { | ||||||
|  | 		s.log(LogError, "Invalid handler type, handler will never be called") | ||||||
|  | 		return func() {} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return s.addEventHandlerOnce(eh) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // removeEventHandler instance removes an event handler instance. | ||||||
|  | func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance) { | ||||||
|  | 	s.handlersMu.Lock() | ||||||
|  | 	defer s.handlersMu.Unlock() | ||||||
|  |  | ||||||
|  | 	handlers := s.handlers[t] | ||||||
|  | 	for i := range handlers { | ||||||
|  | 		if handlers[i] == ehi { | ||||||
|  | 			s.handlers[t] = append(handlers[:i], handlers[i+1:]...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	onceHandlers := s.onceHandlers[t] | ||||||
|  | 	for i := range onceHandlers { | ||||||
|  | 		if onceHandlers[i] == ehi { | ||||||
|  | 			s.onceHandlers[t] = append(onceHandlers[:i], handlers[i+1:]...) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Handles calling permanent and once handlers for an event type. | ||||||
|  | func (s *Session) handle(t string, i interface{}) { | ||||||
|  | 	for _, eh := range s.handlers[t] { | ||||||
|  | 		if s.SyncEvents { | ||||||
|  | 			eh.eventHandler.Handle(s, i) | ||||||
|  | 		} else { | ||||||
|  | 			go eh.eventHandler.Handle(s, i) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(s.onceHandlers[t]) > 0 { | ||||||
|  | 		for _, eh := range s.onceHandlers[t] { | ||||||
|  | 			if s.SyncEvents { | ||||||
|  | 				eh.eventHandler.Handle(s, i) | ||||||
|  | 			} else { | ||||||
|  | 				go eh.eventHandler.Handle(s, i) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		s.onceHandlers[t] = nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Handles an event type by calling internal methods, firing handlers and firing the | ||||||
|  | // interface{} event. | ||||||
|  | func (s *Session) handleEvent(t string, i interface{}) { | ||||||
|  | 	s.handlersMu.RLock() | ||||||
|  | 	defer s.handlersMu.RUnlock() | ||||||
|  |  | ||||||
|  | 	// All events are dispatched internally first. | ||||||
|  | 	s.onInterface(i) | ||||||
|  |  | ||||||
|  | 	// Then they are dispatched to anyone handling interface{} events. | ||||||
|  | 	s.handle(interfaceEventType, i) | ||||||
|  |  | ||||||
|  | 	// Finally they are dispatched to any typed handlers. | ||||||
|  | 	s.handle(t, i) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // setGuildIds will set the GuildID on all the members of a guild. | ||||||
|  | // This is done as event data does not have it set. | ||||||
|  | func setGuildIds(g *Guild) { | ||||||
|  | 	for _, c := range g.Channels { | ||||||
|  | 		c.GuildID = g.ID | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, m := range g.Members { | ||||||
|  | 		m.GuildID = g.ID | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, vs := range g.VoiceStates { | ||||||
|  | 		vs.GuildID = g.ID | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // onInterface handles all internal events and routes them to the appropriate internal handler. | ||||||
|  | func (s *Session) onInterface(i interface{}) { | ||||||
|  | 	switch t := i.(type) { | ||||||
|  | 	case *Ready: | ||||||
|  | 		for _, g := range t.Guilds { | ||||||
|  | 			setGuildIds(g) | ||||||
|  | 		} | ||||||
|  | 		s.onReady(t) | ||||||
|  | 	case *GuildCreate: | ||||||
|  | 		setGuildIds(t.Guild) | ||||||
|  | 	case *GuildUpdate: | ||||||
|  | 		setGuildIds(t.Guild) | ||||||
|  | 	case *VoiceServerUpdate: | ||||||
|  | 		go s.onVoiceServerUpdate(t) | ||||||
|  | 	case *VoiceStateUpdate: | ||||||
|  | 		go s.onVoiceStateUpdate(t) | ||||||
|  | 	} | ||||||
|  | 	err := s.State.OnInterface(s, i) | ||||||
|  | 	if err != nil { | ||||||
|  | 		s.log(LogDebug, "error dispatching internal event, %s", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // onReady handles the ready event. | ||||||
|  | func (s *Session) onReady(r *Ready) { | ||||||
|  |  | ||||||
|  | 	// Store the SessionID within the Session struct. | ||||||
|  | 	s.sessionID = r.SessionID | ||||||
|  | } | ||||||
							
								
								
									
										1030
									
								
								vendor/github.com/matterbridge/discordgo/eventhandlers.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1030
									
								
								vendor/github.com/matterbridge/discordgo/eventhandlers.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										253
									
								
								vendor/github.com/matterbridge/discordgo/events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								vendor/github.com/matterbridge/discordgo/events.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,253 @@ | |||||||
|  | package discordgo | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // This file contains all the possible structs that can be | ||||||
|  | // handled by AddHandler/EventHandler. | ||||||
|  | // DO NOT ADD ANYTHING BUT EVENT HANDLER STRUCTS TO THIS FILE. | ||||||
|  | //go:generate go run tools/cmd/eventhandlers/main.go | ||||||
|  |  | ||||||
|  | // Connect is the data for a Connect event. | ||||||
|  | // This is a sythetic event and is not dispatched by Discord. | ||||||
|  | type Connect struct{} | ||||||
|  |  | ||||||
|  | // Disconnect is the data for a Disconnect event. | ||||||
|  | // This is a sythetic event and is not dispatched by Discord. | ||||||
|  | type Disconnect struct{} | ||||||
|  |  | ||||||
|  | // RateLimit is the data for a RateLimit event. | ||||||
|  | // This is a sythetic event and is not dispatched by Discord. | ||||||
|  | type RateLimit struct { | ||||||
|  | 	*TooManyRequests | ||||||
|  | 	URL string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Event provides a basic initial struct for all websocket events. | ||||||
|  | type Event struct { | ||||||
|  | 	Operation int             `json:"op"` | ||||||
|  | 	Sequence  int64           `json:"s"` | ||||||
|  | 	Type      string          `json:"t"` | ||||||
|  | 	RawData   json.RawMessage `json:"d"` | ||||||
|  | 	// Struct contains one of the other types in this file. | ||||||
|  | 	Struct interface{} `json:"-"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A Ready stores all data for the websocket READY event. | ||||||
|  | type Ready struct { | ||||||
|  | 	Version         int          `json:"v"` | ||||||
|  | 	SessionID       string       `json:"session_id"` | ||||||
|  | 	User            *User        `json:"user"` | ||||||
|  | 	ReadState       []*ReadState `json:"read_state"` | ||||||
|  | 	PrivateChannels []*Channel   `json:"private_channels"` | ||||||
|  | 	Guilds          []*Guild     `json:"guilds"` | ||||||
|  |  | ||||||
|  | 	// Undocumented fields | ||||||
|  | 	Settings          *Settings            `json:"user_settings"` | ||||||
|  | 	UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"` | ||||||
|  | 	Relationships     []*Relationship      `json:"relationships"` | ||||||
|  | 	Presences         []*Presence          `json:"presences"` | ||||||
|  | 	Notes             map[string]string    `json:"notes"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChannelCreate is the data for a ChannelCreate event. | ||||||
|  | type ChannelCreate struct { | ||||||
|  | 	*Channel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChannelUpdate is the data for a ChannelUpdate event. | ||||||
|  | type ChannelUpdate struct { | ||||||
|  | 	*Channel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChannelDelete is the data for a ChannelDelete event. | ||||||
|  | type ChannelDelete struct { | ||||||
|  | 	*Channel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChannelPinsUpdate stores data for a ChannelPinsUpdate event. | ||||||
|  | type ChannelPinsUpdate struct { | ||||||
|  | 	LastPinTimestamp string `json:"last_pin_timestamp"` | ||||||
|  | 	ChannelID        string `json:"channel_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildCreate is the data for a GuildCreate event. | ||||||
|  | type GuildCreate struct { | ||||||
|  | 	*Guild | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildUpdate is the data for a GuildUpdate event. | ||||||
|  | type GuildUpdate struct { | ||||||
|  | 	*Guild | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildDelete is the data for a GuildDelete event. | ||||||
|  | type GuildDelete struct { | ||||||
|  | 	*Guild | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildBanAdd is the data for a GuildBanAdd event. | ||||||
|  | type GuildBanAdd struct { | ||||||
|  | 	User    *User  `json:"user"` | ||||||
|  | 	GuildID string `json:"guild_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildBanRemove is the data for a GuildBanRemove event. | ||||||
|  | type GuildBanRemove struct { | ||||||
|  | 	User    *User  `json:"user"` | ||||||
|  | 	GuildID string `json:"guild_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildMemberAdd is the data for a GuildMemberAdd event. | ||||||
|  | type GuildMemberAdd struct { | ||||||
|  | 	*Member | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildMemberUpdate is the data for a GuildMemberUpdate event. | ||||||
|  | type GuildMemberUpdate struct { | ||||||
|  | 	*Member | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildMemberRemove is the data for a GuildMemberRemove event. | ||||||
|  | type GuildMemberRemove struct { | ||||||
|  | 	*Member | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildRoleCreate is the data for a GuildRoleCreate event. | ||||||
|  | type GuildRoleCreate struct { | ||||||
|  | 	*GuildRole | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildRoleUpdate is the data for a GuildRoleUpdate event. | ||||||
|  | type GuildRoleUpdate struct { | ||||||
|  | 	*GuildRole | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildRoleDelete is the data for a GuildRoleDelete event. | ||||||
|  | type GuildRoleDelete struct { | ||||||
|  | 	RoleID  string `json:"role_id"` | ||||||
|  | 	GuildID string `json:"guild_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildEmojisUpdate is the data for a guild emoji update event. | ||||||
|  | type GuildEmojisUpdate struct { | ||||||
|  | 	GuildID string   `json:"guild_id"` | ||||||
|  | 	Emojis  []*Emoji `json:"emojis"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildMembersChunk is the data for a GuildMembersChunk event. | ||||||
|  | type GuildMembersChunk struct { | ||||||
|  | 	GuildID string    `json:"guild_id"` | ||||||
|  | 	Members []*Member `json:"members"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event. | ||||||
|  | type GuildIntegrationsUpdate struct { | ||||||
|  | 	GuildID string `json:"guild_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageAck is the data for a MessageAck event. | ||||||
|  | type MessageAck struct { | ||||||
|  | 	MessageID string `json:"message_id"` | ||||||
|  | 	ChannelID string `json:"channel_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageCreate is the data for a MessageCreate event. | ||||||
|  | type MessageCreate struct { | ||||||
|  | 	*Message | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageUpdate is the data for a MessageUpdate event. | ||||||
|  | type MessageUpdate struct { | ||||||
|  | 	*Message | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageDelete is the data for a MessageDelete event. | ||||||
|  | type MessageDelete struct { | ||||||
|  | 	*Message | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageReactionAdd is the data for a MessageReactionAdd event. | ||||||
|  | type MessageReactionAdd struct { | ||||||
|  | 	*MessageReaction | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageReactionRemove is the data for a MessageReactionRemove event. | ||||||
|  | type MessageReactionRemove struct { | ||||||
|  | 	*MessageReaction | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageReactionRemoveAll is the data for a MessageReactionRemoveAll event. | ||||||
|  | type MessageReactionRemoveAll struct { | ||||||
|  | 	*MessageReaction | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PresencesReplace is the data for a PresencesReplace event. | ||||||
|  | type PresencesReplace []*Presence | ||||||
|  |  | ||||||
|  | // PresenceUpdate is the data for a PresenceUpdate event. | ||||||
|  | type PresenceUpdate struct { | ||||||
|  | 	Presence | ||||||
|  | 	GuildID string   `json:"guild_id"` | ||||||
|  | 	Roles   []string `json:"roles"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Resumed is the data for a Resumed event. | ||||||
|  | type Resumed struct { | ||||||
|  | 	Trace []string `json:"_trace"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RelationshipAdd is the data for a RelationshipAdd event. | ||||||
|  | type RelationshipAdd struct { | ||||||
|  | 	*Relationship | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RelationshipRemove is the data for a RelationshipRemove event. | ||||||
|  | type RelationshipRemove struct { | ||||||
|  | 	*Relationship | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TypingStart is the data for a TypingStart event. | ||||||
|  | type TypingStart struct { | ||||||
|  | 	UserID    string `json:"user_id"` | ||||||
|  | 	ChannelID string `json:"channel_id"` | ||||||
|  | 	Timestamp int    `json:"timestamp"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UserUpdate is the data for a UserUpdate event. | ||||||
|  | type UserUpdate struct { | ||||||
|  | 	*User | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UserSettingsUpdate is the data for a UserSettingsUpdate event. | ||||||
|  | type UserSettingsUpdate map[string]interface{} | ||||||
|  |  | ||||||
|  | // UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event. | ||||||
|  | type UserGuildSettingsUpdate struct { | ||||||
|  | 	*UserGuildSettings | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UserNoteUpdate is the data for a UserNoteUpdate event. | ||||||
|  | type UserNoteUpdate struct { | ||||||
|  | 	ID   string `json:"id"` | ||||||
|  | 	Note string `json:"note"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // VoiceServerUpdate is the data for a VoiceServerUpdate event. | ||||||
|  | type VoiceServerUpdate struct { | ||||||
|  | 	Token    string `json:"token"` | ||||||
|  | 	GuildID  string `json:"guild_id"` | ||||||
|  | 	Endpoint string `json:"endpoint"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // VoiceStateUpdate is the data for a VoiceStateUpdate event. | ||||||
|  | type VoiceStateUpdate struct { | ||||||
|  | 	*VoiceState | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageDeleteBulk is the data for a MessageDeleteBulk event | ||||||
|  | type MessageDeleteBulk struct { | ||||||
|  | 	Messages  []string `json:"ids"` | ||||||
|  | 	ChannelID string   `json:"channel_id"` | ||||||
|  | } | ||||||
							
								
								
									
										211
									
								
								vendor/github.com/matterbridge/discordgo/examples/airhorn/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								vendor/github.com/matterbridge/discordgo/examples/airhorn/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,211 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"os/signal" | ||||||
|  | 	"strings" | ||||||
|  | 	"syscall" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/bwmarrin/discordgo" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	flag.StringVar(&token, "t", "", "Bot Token") | ||||||
|  | 	flag.Parse() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var token string | ||||||
|  | var buffer = make([][]byte, 0) | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  |  | ||||||
|  | 	if token == "" { | ||||||
|  | 		fmt.Println("No token provided. Please run: airhorn -t <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 bot token. | ||||||
|  | 	dg, err := discordgo.New("Bot " + token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("Error creating Discord session: ", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Register ready as a callback for the ready events. | ||||||
|  | 	dg.AddHandler(ready) | ||||||
|  |  | ||||||
|  | 	// Register messageCreate as a callback for the messageCreate events. | ||||||
|  | 	dg.AddHandler(messageCreate) | ||||||
|  |  | ||||||
|  | 	// Register guildCreate as a callback for the guildCreate events. | ||||||
|  | 	dg.AddHandler(guildCreate) | ||||||
|  |  | ||||||
|  | 	// Open the websocket and begin listening. | ||||||
|  | 	err = dg.Open() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("Error opening Discord session: ", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Wait here until CTRL-C or other term signal is received. | ||||||
|  | 	fmt.Println("Airhorn is now running.  Press CTRL-C to exit.") | ||||||
|  | 	sc := make(chan os.Signal, 1) | ||||||
|  | 	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) | ||||||
|  | 	<-sc | ||||||
|  |  | ||||||
|  | 	// Cleanly close down the Discord session. | ||||||
|  | 	dg.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This function will be called (due to AddHandler above) when the bot receives | ||||||
|  | // the "ready" event from Discord. | ||||||
|  | func ready(s *discordgo.Session, event *discordgo.Ready) { | ||||||
|  |  | ||||||
|  | 	// Set the playing status. | ||||||
|  | 	s.UpdateStatus(0, "!airhorn") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This function will be called (due to AddHandler above) every time a new | ||||||
|  | // message is created on any channel that the autenticated bot has access to. | ||||||
|  | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||||
|  |  | ||||||
|  | 	// Ignore all messages created by the bot itself | ||||||
|  | 	// This isn't required in this specific example but it's a good practice. | ||||||
|  | 	if m.Author.ID == s.State.User.ID { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// check if the message is "!airhorn" | ||||||
|  | 	if strings.HasPrefix(m.Content, "!airhorn") { | ||||||
|  |  | ||||||
|  | 		// Find the channel that the message came from. | ||||||
|  | 		c, err := s.State.Channel(m.ChannelID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// Could not find channel. | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Find the guild for that channel. | ||||||
|  | 		g, err := s.State.Guild(c.GuildID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// Could not find guild. | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Look for the message sender in that guild's current voice states. | ||||||
|  | 		for _, vs := range g.VoiceStates { | ||||||
|  | 			if vs.UserID == m.Author.ID { | ||||||
|  | 				err = playSound(s, g.ID, vs.ChannelID) | ||||||
|  | 				if err != nil { | ||||||
|  | 					fmt.Println("Error playing sound:", err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This function will be called (due to AddHandler above) every time a new | ||||||
|  | // guild is joined. | ||||||
|  | func guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) { | ||||||
|  |  | ||||||
|  | 	if event.Guild.Unavailable { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, channel := range event.Guild.Channels { | ||||||
|  | 		if channel.ID == event.Guild.ID { | ||||||
|  | 			_, _ = s.ChannelMessageSend(channel.ID, "Airhorn is ready! Type !airhorn while in a voice channel to play a sound.") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // loadSound attempts to load an encoded sound file from disk. | ||||||
|  | func loadSound() error { | ||||||
|  |  | ||||||
|  | 	file, err := os.Open("airhorn.dca") | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("Error opening dca file :", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var opuslen int16 | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		// Read opus frame length from dca file. | ||||||
|  | 		err = binary.Read(file, binary.LittleEndian, &opuslen) | ||||||
|  |  | ||||||
|  | 		// If this is the end of the file, just return. | ||||||
|  | 		if err == io.EOF || err == io.ErrUnexpectedEOF { | ||||||
|  | 			err := file.Close() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println("Error reading from dca file :", err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Read encoded pcm from dca file. | ||||||
|  | 		InBuf := make([]byte, opuslen) | ||||||
|  | 		err = binary.Read(file, binary.LittleEndian, &InBuf) | ||||||
|  |  | ||||||
|  | 		// Should not be any end of file errors | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println("Error reading from dca file :", err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Append encoded pcm data to the buffer. | ||||||
|  | 		buffer = append(buffer, InBuf) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // playSound plays the current buffer to the provided channel. | ||||||
|  | func playSound(s *discordgo.Session, guildID, channelID string) (err error) { | ||||||
|  |  | ||||||
|  | 	// Join the provided voice channel. | ||||||
|  | 	vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Sleep for a specified amount of time before playing the sound | ||||||
|  | 	time.Sleep(250 * time.Millisecond) | ||||||
|  |  | ||||||
|  | 	// Start speaking. | ||||||
|  | 	vc.Speaking(true) | ||||||
|  |  | ||||||
|  | 	// Send the buffer data. | ||||||
|  | 	for _, buff := range buffer { | ||||||
|  | 		vc.OpusSend <- buff | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Stop speaking | ||||||
|  | 	vc.Speaking(false) | ||||||
|  |  | ||||||
|  | 	// Sleep for a specificed amount of time before ending. | ||||||
|  | 	time.Sleep(250 * time.Millisecond) | ||||||
|  |  | ||||||
|  | 	// Disconnect from the provided voice channel. | ||||||
|  | 	vc.Disconnect() | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								vendor/github.com/matterbridge/discordgo/examples/appmaker/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/github.com/matterbridge/discordgo/examples/appmaker/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/bwmarrin/discordgo" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Variables used for command line options | ||||||
|  | var ( | ||||||
|  | 	Token    string | ||||||
|  | 	Name     string | ||||||
|  | 	DeleteID string | ||||||
|  | 	ListOnly bool | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  |  | ||||||
|  | 	flag.StringVar(&Token, "t", "", "Owner Account Token") | ||||||
|  | 	flag.StringVar(&Name, "n", "", "Name to give App/Bot") | ||||||
|  | 	flag.StringVar(&DeleteID, "d", "", "Application ID to delete") | ||||||
|  | 	flag.BoolVar(&ListOnly, "l", false, "List Applications Only") | ||||||
|  | 	flag.Parse() | ||||||
|  |  | ||||||
|  | 	if Token == "" { | ||||||
|  | 		flag.Usage() | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	// Create a new Discord session using the provided login information. | ||||||
|  | 	dg, err := discordgo.New(Token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("error creating Discord session,", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If -l set, only display a list of existing applications | ||||||
|  | 	// for the given account. | ||||||
|  | 	if ListOnly { | ||||||
|  |  | ||||||
|  | 		aps, err := dg.Applications() | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println("error fetching applications,", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, v := range aps { | ||||||
|  | 			fmt.Println("-----------------------------------------------------") | ||||||
|  | 			b, _ := json.MarshalIndent(v, "", " ") | ||||||
|  | 			fmt.Println(string(b)) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// if -d set, delete the given Application | ||||||
|  | 	if DeleteID != "" { | ||||||
|  | 		err = dg.ApplicationDelete(DeleteID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println("error deleting application,", err) | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if Name == "" { | ||||||
|  | 		flag.Usage() | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Create a new application. | ||||||
|  | 	ap := &discordgo.Application{} | ||||||
|  | 	ap.Name = Name | ||||||
|  | 	ap, err = dg.ApplicationCreate(ap) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("error creating new application,", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fmt.Printf("Application created successfully:\n") | ||||||
|  | 	b, _ := json.MarshalIndent(ap, "", " ") | ||||||
|  | 	fmt.Println(string(b)) | ||||||
|  |  | ||||||
|  | 	// Create the bot account under the application we just created | ||||||
|  | 	bot, err := dg.ApplicationBotCreate(ap.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("error creating bot account,", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fmt.Printf("Bot account created successfully.\n") | ||||||
|  | 	b, _ = json.MarshalIndent(bot, "", " ") | ||||||
|  | 	fmt.Println(string(b)) | ||||||
|  |  | ||||||
|  | 	fmt.Println("Please save the above posted info in a secure place.") | ||||||
|  | 	fmt.Println("You will need that information to login with your bot account.") | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								vendor/github.com/matterbridge/discordgo/examples/avatar/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								vendor/github.com/matterbridge/discordgo/examples/avatar/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/bwmarrin/discordgo" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Variables used for command line parameters | ||||||
|  | var ( | ||||||
|  | 	Token      string | ||||||
|  | 	AvatarFile string | ||||||
|  | 	AvatarURL  string | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  |  | ||||||
|  | 	flag.StringVar(&Token, "t", "", "Bot Token") | ||||||
|  | 	flag.StringVar(&AvatarFile, "f", "", "Avatar File Name") | ||||||
|  | 	flag.StringVar(&AvatarURL, "u", "", "URL to the avatar image") | ||||||
|  | 	flag.Parse() | ||||||
|  |  | ||||||
|  | 	if Token == "" || (AvatarFile == "" && AvatarURL == "") { | ||||||
|  | 		flag.Usage() | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  |  | ||||||
|  | 	// Create a new Discord session using the provided login information. | ||||||
|  | 	dg, err := discordgo.New("Bot " + Token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("error creating Discord session,", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Declare these here so they can be used in the below two if blocks and | ||||||
|  | 	// still carry over to the end of this function. | ||||||
|  | 	var base64img string | ||||||
|  | 	var contentType string | ||||||
|  |  | ||||||
|  | 	// If we're using a URL link for the Avatar | ||||||
|  | 	if AvatarURL != "" { | ||||||
|  |  | ||||||
|  | 		resp, err := http.Get(AvatarURL) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println("Error retrieving the file, ", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		defer func() { | ||||||
|  | 			_ = resp.Body.Close() | ||||||
|  | 		}() | ||||||
|  |  | ||||||
|  | 		img, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println("Error reading the response, ", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		contentType = http.DetectContentType(img) | ||||||
|  | 		base64img = base64.StdEncoding.EncodeToString(img) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If we're using a local file for the Avatar | ||||||
|  | 	if AvatarFile != "" { | ||||||
|  | 		img, err := ioutil.ReadFile(AvatarFile) | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		contentType = http.DetectContentType(img) | ||||||
|  | 		base64img = base64.StdEncoding.EncodeToString(img) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Now lets format our base64 image into the proper format Discord wants | ||||||
|  | 	// and then call UserUpdate to set it as our user's Avatar. | ||||||
|  | 	avatar := fmt.Sprintf("data:%s;base64,%s", contentType, base64img) | ||||||
|  | 	_, err = dg.UserUpdate("", "", "", avatar, "") | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								vendor/github.com/matterbridge/discordgo/examples/mytoken/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/matterbridge/discordgo/examples/mytoken/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  |  | ||||||
|  | 	"github.com/bwmarrin/discordgo" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Variables used for command line parameters | ||||||
|  | var ( | ||||||
|  | 	Email    string | ||||||
|  | 	Password string | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  |  | ||||||
|  | 	flag.StringVar(&Email, "e", "", "Account Email") | ||||||
|  | 	flag.StringVar(&Password, "p", "", "Account Password") | ||||||
|  | 	flag.Parse() | ||||||
|  |  | ||||||
|  | 	if Email == "" || Password == "" { | ||||||
|  | 		flag.Usage() | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  |  | ||||||
|  | 	// Create a new Discord session using the provided login information. | ||||||
|  | 	dg, err := discordgo.New(Email, Password) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("error creating Discord session,", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Print out your token. | ||||||
|  | 	fmt.Printf("Your Authentication Token is:\n\n%s\n", dg.Token) | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								vendor/github.com/matterbridge/discordgo/examples/pingpong/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								vendor/github.com/matterbridge/discordgo/examples/pingpong/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"os/signal" | ||||||
|  | 	"syscall" | ||||||
|  |  | ||||||
|  | 	"github.com/bwmarrin/discordgo" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Variables used for command line parameters | ||||||
|  | var ( | ||||||
|  | 	Token string | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  |  | ||||||
|  | 	flag.StringVar(&Token, "t", "", "Bot Token") | ||||||
|  | 	flag.Parse() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  |  | ||||||
|  | 	// Create a new Discord session using the provided bot token. | ||||||
|  | 	dg, err := discordgo.New("Bot " + Token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("error creating Discord session,", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Register the messageCreate func as a callback for MessageCreate events. | ||||||
|  | 	dg.AddHandler(messageCreate) | ||||||
|  |  | ||||||
|  | 	// Open a websocket connection to Discord and begin listening. | ||||||
|  | 	err = dg.Open() | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println("error opening connection,", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Wait here until CTRL-C or other term signal is received. | ||||||
|  | 	fmt.Println("Bot is now running.  Press CTRL-C to exit.") | ||||||
|  | 	sc := make(chan os.Signal, 1) | ||||||
|  | 	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) | ||||||
|  | 	<-sc | ||||||
|  |  | ||||||
|  | 	// Cleanly close down the Discord session. | ||||||
|  | 	dg.Close() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This function will be called (due to AddHandler above) every time a new | ||||||
|  | // message is created on any channel that the autenticated bot has access to. | ||||||
|  | func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||||
|  |  | ||||||
|  | 	// Ignore all messages created by the bot itself | ||||||
|  | 	// This isn't required in this specific example but it's a good practice. | ||||||
|  | 	if m.Author.ID == s.State.User.ID { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// If the message is "ping" reply with "Pong!" | ||||||
|  | 	if m.Content == "ping" { | ||||||
|  | 		s.ChannelMessageSend(m.ChannelID, "Pong!") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If the message is "pong" reply with "Ping!" | ||||||
|  | 	if m.Content == "pong" { | ||||||
|  | 		s.ChannelMessageSend(m.ChannelID, "Ping!") | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										103
									
								
								vendor/github.com/matterbridge/discordgo/logging.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								vendor/github.com/matterbridge/discordgo/logging.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | // 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 returned to a calling function. | ||||||
|  | 	LogWarning | ||||||
|  |  | ||||||
|  | 	// LogInformational level is used for normal non-error activity | ||||||
|  | 	LogInformational | ||||||
|  |  | ||||||
|  | 	// LogDebug level is for very detailed non-error activity.  This is | ||||||
|  | 	// very spammy and will impact performance. | ||||||
|  | 	LogDebug | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Logger can be used to replace the standard logging for discordgo | ||||||
|  | var Logger func(msgL, caller int, format string, a ...interface{}) | ||||||
|  |  | ||||||
|  | // msglog provides package wide logging 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 separated list of values to pass | ||||||
|  | func msglog(msgL, caller int, format string, a ...interface{}) { | ||||||
|  |  | ||||||
|  | 	if Logger != nil { | ||||||
|  | 		Logger(msgL, caller, format, a...) | ||||||
|  | 	} else { | ||||||
|  |  | ||||||
|  | 		pc, file, line, _ := runtime.Caller(caller) | ||||||
|  |  | ||||||
|  | 		files := strings.Split(file, "/") | ||||||
|  | 		file = files[len(files)-1] | ||||||
|  |  | ||||||
|  | 		name := runtime.FuncForPC(pc).Name() | ||||||
|  | 		fns := strings.Split(name, ".") | ||||||
|  | 		name = fns[len(fns)-1] | ||||||
|  |  | ||||||
|  | 		msg := fmt.Sprintf(format, a...) | ||||||
|  |  | ||||||
|  | 		log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // helper function that wraps msglog for the Session struct | ||||||
|  | // This adds a check to insure the message is only logged | ||||||
|  | // if the session log level is equal or higher than the | ||||||
|  | // message log level | ||||||
|  | func (s *Session) log(msgL int, format string, a ...interface{}) { | ||||||
|  |  | ||||||
|  | 	if msgL > s.LogLevel { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	msglog(msgL, 2, format, a...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // helper function that wraps msglog for the VoiceConnection struct | ||||||
|  | // This adds a check to insure the message is only logged | ||||||
|  | // if the voice connection log level is equal or higher than the | ||||||
|  | // message log level | ||||||
|  | func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) { | ||||||
|  |  | ||||||
|  | 	if msgL > v.LogLevel { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	msglog(msgL, 2, format, a...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // printJSON is a helper function to display JSON data in a easy to read format. | ||||||
|  | /* NOT USED ATM | ||||||
|  | func printJSON(body []byte) { | ||||||
|  | 	var prettyJSON bytes.Buffer | ||||||
|  | 	error := json.Indent(&prettyJSON, body, "", "\t") | ||||||
|  | 	if error != nil { | ||||||
|  | 		log.Print("JSON parse error: ", error) | ||||||
|  | 	} | ||||||
|  | 	log.Println(string(prettyJSON.Bytes())) | ||||||
|  | } | ||||||
|  | */ | ||||||
							
								
								
									
										252
									
								
								vendor/github.com/matterbridge/discordgo/message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								vendor/github.com/matterbridge/discordgo/message.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,252 @@ | |||||||
|  | // 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 ( | ||||||
|  | 	"io" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // MessageType is the type of Message | ||||||
|  | type MessageType int | ||||||
|  |  | ||||||
|  | // Block contains the valid known MessageType values | ||||||
|  | const ( | ||||||
|  | 	MessageTypeDefault MessageType = iota | ||||||
|  | 	MessageTypeRecipientAdd | ||||||
|  | 	MessageTypeRecipientRemove | ||||||
|  | 	MessageTypeCall | ||||||
|  | 	MessageTypeChannelNameChange | ||||||
|  | 	MessageTypeChannelIconChange | ||||||
|  | 	MessageTypeChannelPinnedMessage | ||||||
|  | 	MessageTypeGuildMemberJoin | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // A Message stores all data related to a specific Discord message. | ||||||
|  | type Message struct { | ||||||
|  | 	ID              string               `json:"id"` | ||||||
|  | 	ChannelID       string               `json:"channel_id"` | ||||||
|  | 	Content         string               `json:"content"` | ||||||
|  | 	Timestamp       Timestamp            `json:"timestamp"` | ||||||
|  | 	EditedTimestamp Timestamp            `json:"edited_timestamp"` | ||||||
|  | 	MentionRoles    []string             `json:"mention_roles"` | ||||||
|  | 	Tts             bool                 `json:"tts"` | ||||||
|  | 	MentionEveryone bool                 `json:"mention_everyone"` | ||||||
|  | 	Author          *User                `json:"author"` | ||||||
|  | 	Attachments     []*MessageAttachment `json:"attachments"` | ||||||
|  | 	Embeds          []*MessageEmbed      `json:"embeds"` | ||||||
|  | 	Mentions        []*User              `json:"mentions"` | ||||||
|  | 	Reactions       []*MessageReactions  `json:"reactions"` | ||||||
|  | 	Type            MessageType          `json:"type"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // File stores info about files you e.g. send in messages. | ||||||
|  | type File struct { | ||||||
|  | 	Name        string | ||||||
|  | 	ContentType string | ||||||
|  | 	Reader      io.Reader | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageSend stores all parameters you can send with ChannelMessageSendComplex. | ||||||
|  | type MessageSend struct { | ||||||
|  | 	Content string        `json:"content,omitempty"` | ||||||
|  | 	Embed   *MessageEmbed `json:"embed,omitempty"` | ||||||
|  | 	Tts     bool          `json:"tts"` | ||||||
|  | 	Files   []*File       `json:"-"` | ||||||
|  |  | ||||||
|  | 	// TODO: Remove this when compatibility is not required. | ||||||
|  | 	File *File `json:"-"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageEdit is used to chain parameters via ChannelMessageEditComplex, which | ||||||
|  | // is also where you should get the instance from. | ||||||
|  | type MessageEdit struct { | ||||||
|  | 	Content *string       `json:"content,omitempty"` | ||||||
|  | 	Embed   *MessageEmbed `json:"embed,omitempty"` | ||||||
|  |  | ||||||
|  | 	ID      string | ||||||
|  | 	Channel string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewMessageEdit returns a MessageEdit struct, initialized | ||||||
|  | // with the Channel and ID. | ||||||
|  | func NewMessageEdit(channelID string, messageID string) *MessageEdit { | ||||||
|  | 	return &MessageEdit{ | ||||||
|  | 		Channel: channelID, | ||||||
|  | 		ID:      messageID, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetContent is the same as setting the variable Content, | ||||||
|  | // except it doesn't take a pointer. | ||||||
|  | func (m *MessageEdit) SetContent(str string) *MessageEdit { | ||||||
|  | 	m.Content = &str | ||||||
|  | 	return m | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SetEmbed is a convenience function for setting the embed, | ||||||
|  | // so you can chain commands. | ||||||
|  | func (m *MessageEdit) SetEmbed(embed *MessageEmbed) *MessageEdit { | ||||||
|  | 	m.Embed = embed | ||||||
|  | 	return m | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A MessageAttachment stores data for message attachments. | ||||||
|  | type MessageAttachment struct { | ||||||
|  | 	ID       string `json:"id"` | ||||||
|  | 	URL      string `json:"url"` | ||||||
|  | 	ProxyURL string `json:"proxy_url"` | ||||||
|  | 	Filename string `json:"filename"` | ||||||
|  | 	Width    int    `json:"width"` | ||||||
|  | 	Height   int    `json:"height"` | ||||||
|  | 	Size     int    `json:"size"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageEmbedFooter is a part of a MessageEmbed struct. | ||||||
|  | type MessageEmbedFooter struct { | ||||||
|  | 	Text         string `json:"text,omitempty"` | ||||||
|  | 	IconURL      string `json:"icon_url,omitempty"` | ||||||
|  | 	ProxyIconURL string `json:"proxy_icon_url,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageEmbedImage is a part of a MessageEmbed struct. | ||||||
|  | type MessageEmbedImage struct { | ||||||
|  | 	URL      string `json:"url,omitempty"` | ||||||
|  | 	ProxyURL string `json:"proxy_url,omitempty"` | ||||||
|  | 	Width    int    `json:"width,omitempty"` | ||||||
|  | 	Height   int    `json:"height,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageEmbedThumbnail is a part of a MessageEmbed struct. | ||||||
|  | type MessageEmbedThumbnail struct { | ||||||
|  | 	URL      string `json:"url,omitempty"` | ||||||
|  | 	ProxyURL string `json:"proxy_url,omitempty"` | ||||||
|  | 	Width    int    `json:"width,omitempty"` | ||||||
|  | 	Height   int    `json:"height,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageEmbedVideo is a part of a MessageEmbed struct. | ||||||
|  | type MessageEmbedVideo struct { | ||||||
|  | 	URL      string `json:"url,omitempty"` | ||||||
|  | 	ProxyURL string `json:"proxy_url,omitempty"` | ||||||
|  | 	Width    int    `json:"width,omitempty"` | ||||||
|  | 	Height   int    `json:"height,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageEmbedProvider is a part of a MessageEmbed struct. | ||||||
|  | type MessageEmbedProvider struct { | ||||||
|  | 	URL  string `json:"url,omitempty"` | ||||||
|  | 	Name string `json:"name,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageEmbedAuthor is a part of a MessageEmbed struct. | ||||||
|  | type MessageEmbedAuthor struct { | ||||||
|  | 	URL          string `json:"url,omitempty"` | ||||||
|  | 	Name         string `json:"name,omitempty"` | ||||||
|  | 	IconURL      string `json:"icon_url,omitempty"` | ||||||
|  | 	ProxyIconURL string `json:"proxy_icon_url,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageEmbedField is a part of a MessageEmbed struct. | ||||||
|  | type MessageEmbedField struct { | ||||||
|  | 	Name   string `json:"name,omitempty"` | ||||||
|  | 	Value  string `json:"value,omitempty"` | ||||||
|  | 	Inline bool   `json:"inline,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // An MessageEmbed stores data for message embeds. | ||||||
|  | type MessageEmbed struct { | ||||||
|  | 	URL         string                 `json:"url,omitempty"` | ||||||
|  | 	Type        string                 `json:"type,omitempty"` | ||||||
|  | 	Title       string                 `json:"title,omitempty"` | ||||||
|  | 	Description string                 `json:"description,omitempty"` | ||||||
|  | 	Timestamp   string                 `json:"timestamp,omitempty"` | ||||||
|  | 	Color       int                    `json:"color,omitempty"` | ||||||
|  | 	Footer      *MessageEmbedFooter    `json:"footer,omitempty"` | ||||||
|  | 	Image       *MessageEmbedImage     `json:"image,omitempty"` | ||||||
|  | 	Thumbnail   *MessageEmbedThumbnail `json:"thumbnail,omitempty"` | ||||||
|  | 	Video       *MessageEmbedVideo     `json:"video,omitempty"` | ||||||
|  | 	Provider    *MessageEmbedProvider  `json:"provider,omitempty"` | ||||||
|  | 	Author      *MessageEmbedAuthor    `json:"author,omitempty"` | ||||||
|  | 	Fields      []*MessageEmbedField   `json:"fields,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageReactions holds a reactions object for a message. | ||||||
|  | type MessageReactions struct { | ||||||
|  | 	Count int    `json:"count"` | ||||||
|  | 	Me    bool   `json:"me"` | ||||||
|  | 	Emoji *Emoji `json:"emoji"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ContentWithMentionsReplaced will replace all @<id> mentions with the | ||||||
|  | // username of the mention. | ||||||
|  | func (m *Message) ContentWithMentionsReplaced() (content string) { | ||||||
|  | 	content = m.Content | ||||||
|  |  | ||||||
|  | 	for _, user := range m.Mentions { | ||||||
|  | 		content = strings.NewReplacer( | ||||||
|  | 			"<@"+user.ID+">", "@"+user.Username, | ||||||
|  | 			"<@!"+user.ID+">", "@"+user.Username, | ||||||
|  | 		).Replace(content) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var patternChannels = regexp.MustCompile("<#[^>]*>") | ||||||
|  |  | ||||||
|  | // ContentWithMoreMentionsReplaced will replace all @<id> mentions with the | ||||||
|  | // username of the mention, but also role IDs and more. | ||||||
|  | func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, err error) { | ||||||
|  | 	content = m.Content | ||||||
|  |  | ||||||
|  | 	if !s.StateEnabled { | ||||||
|  | 		content = m.ContentWithMentionsReplaced() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	channel, err := s.State.Channel(m.ChannelID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		content = m.ContentWithMentionsReplaced() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, user := range m.Mentions { | ||||||
|  | 		nick := user.Username | ||||||
|  |  | ||||||
|  | 		member, err := s.State.Member(channel.GuildID, user.ID) | ||||||
|  | 		if err == nil && member.Nick != "" { | ||||||
|  | 			nick = member.Nick | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		content = strings.NewReplacer( | ||||||
|  | 			"<@"+user.ID+">", "@"+user.Username, | ||||||
|  | 			"<@!"+user.ID+">", "@"+nick, | ||||||
|  | 		).Replace(content) | ||||||
|  | 	} | ||||||
|  | 	for _, roleID := range m.MentionRoles { | ||||||
|  | 		role, err := s.State.Role(channel.GuildID, roleID) | ||||||
|  | 		if err != nil || !role.Mentionable { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		content = strings.Replace(content, "<@&"+role.ID+">", "@"+role.Name, -1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string { | ||||||
|  | 		channel, err := s.State.Channel(mention[2 : len(mention)-1]) | ||||||
|  | 		if err != nil || channel.Type == ChannelTypeGuildVoice { | ||||||
|  | 			return mention | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return "#" + channel.Name | ||||||
|  | 	}) | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										126
									
								
								vendor/github.com/matterbridge/discordgo/oauth2.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								vendor/github.com/matterbridge/discordgo/oauth2.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | |||||||
|  | // 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"` | ||||||
|  | 	BotRequireCodeGrant bool      `json:"bot_require_code_grant,omitempty"` | ||||||
|  | 	BotPublic           bool      `json:"bot_public,omitempty"` | ||||||
|  | 	RPCApplicationState int       `json:"rpc_application_state,omitempty"` | ||||||
|  | 	Flags               int       `json:"flags,omitempty"` | ||||||
|  | 	Owner               *User     `json:"owner"` | ||||||
|  | 	Bot                 *User     `json:"bot"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Application returns an Application structure of a specific Application | ||||||
|  | //   appID : The ID of an Application | ||||||
|  | func (s *Session) Application(appID string) (st *Application, err error) { | ||||||
|  |  | ||||||
|  | 	body, err := s.RequestWithBucketID("GET", EndpointApplication(appID), nil, EndpointApplication("")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = unmarshal(body, &st) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Applications returns all applications for the authenticated user | ||||||
|  | func (s *Session) Applications() (st []*Application, err error) { | ||||||
|  |  | ||||||
|  | 	body, err := s.RequestWithBucketID("GET", EndpointApplications, nil, EndpointApplications) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = unmarshal(body, &st) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ApplicationCreate creates a new Application | ||||||
|  | //    name : Name of Application / Bot | ||||||
|  | //    uris : Redirect URIs (Not required) | ||||||
|  | func (s *Session) ApplicationCreate(ap *Application) (st *Application, err error) { | ||||||
|  |  | ||||||
|  | 	data := struct { | ||||||
|  | 		Name         string    `json:"name"` | ||||||
|  | 		Description  string    `json:"description"` | ||||||
|  | 		RedirectURIs *[]string `json:"redirect_uris,omitempty"` | ||||||
|  | 	}{ap.Name, ap.Description, ap.RedirectURIs} | ||||||
|  |  | ||||||
|  | 	body, err := s.RequestWithBucketID("POST", EndpointApplications, data, EndpointApplications) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = unmarshal(body, &st) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ApplicationUpdate updates an existing Application | ||||||
|  | //   var : desc | ||||||
|  | func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Application, err error) { | ||||||
|  |  | ||||||
|  | 	data := struct { | ||||||
|  | 		Name         string    `json:"name"` | ||||||
|  | 		Description  string    `json:"description"` | ||||||
|  | 		RedirectURIs *[]string `json:"redirect_uris,omitempty"` | ||||||
|  | 	}{ap.Name, ap.Description, ap.RedirectURIs} | ||||||
|  |  | ||||||
|  | 	body, err := s.RequestWithBucketID("PUT", EndpointApplication(appID), data, EndpointApplication("")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = unmarshal(body, &st) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ApplicationDelete deletes an existing Application | ||||||
|  | //   appID : The ID of an Application | ||||||
|  | func (s *Session) ApplicationDelete(appID string) (err error) { | ||||||
|  |  | ||||||
|  | 	_, err = s.RequestWithBucketID("DELETE", EndpointApplication(appID), nil, EndpointApplication("")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  | // Code specific to Discord OAuth2 Application Bots | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | // ApplicationBotCreate creates an Application Bot Account | ||||||
|  | // | ||||||
|  | //   appID : The ID of an Application | ||||||
|  | // | ||||||
|  | // NOTE: func name may change, if I can think up something better. | ||||||
|  | func (s *Session) ApplicationBotCreate(appID string) (st *User, err error) { | ||||||
|  |  | ||||||
|  | 	body, err := s.RequestWithBucketID("POST", EndpointApplicationsBot(appID), nil, EndpointApplicationsBot("")) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = unmarshal(body, &st) | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										194
									
								
								vendor/github.com/matterbridge/discordgo/ratelimit.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								vendor/github.com/matterbridge/discordgo/ratelimit.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,194 @@ | |||||||
|  | package discordgo | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/http" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // customRateLimit holds information for defining a custom rate limit | ||||||
|  | type customRateLimit struct { | ||||||
|  | 	suffix   string | ||||||
|  | 	requests int | ||||||
|  | 	reset    time.Duration | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RateLimiter holds all ratelimit buckets | ||||||
|  | type RateLimiter struct { | ||||||
|  | 	sync.Mutex | ||||||
|  | 	global           *int64 | ||||||
|  | 	buckets          map[string]*Bucket | ||||||
|  | 	globalRateLimit  time.Duration | ||||||
|  | 	customRateLimits []*customRateLimit | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewRatelimiter returns a new RateLimiter | ||||||
|  | func NewRatelimiter() *RateLimiter { | ||||||
|  |  | ||||||
|  | 	return &RateLimiter{ | ||||||
|  | 		buckets: make(map[string]*Bucket), | ||||||
|  | 		global:  new(int64), | ||||||
|  | 		customRateLimits: []*customRateLimit{ | ||||||
|  | 			&customRateLimit{ | ||||||
|  | 				suffix:   "//reactions//", | ||||||
|  | 				requests: 1, | ||||||
|  | 				reset:    200 * time.Millisecond, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetBucket retrieves or creates a bucket | ||||||
|  | func (r *RateLimiter) GetBucket(key string) *Bucket { | ||||||
|  | 	r.Lock() | ||||||
|  | 	defer r.Unlock() | ||||||
|  |  | ||||||
|  | 	if bucket, ok := r.buckets[key]; ok { | ||||||
|  | 		return bucket | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b := &Bucket{ | ||||||
|  | 		Remaining: 1, | ||||||
|  | 		Key:       key, | ||||||
|  | 		global:    r.global, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Check if there is a custom ratelimit set for this bucket ID. | ||||||
|  | 	for _, rl := range r.customRateLimits { | ||||||
|  | 		if strings.HasSuffix(b.Key, rl.suffix) { | ||||||
|  | 			b.customRateLimit = rl | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r.buckets[key] = b | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetWaitTime returns the duration you should wait for a Bucket | ||||||
|  | func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration { | ||||||
|  | 	// If we ran out of calls and the reset time is still ahead of us | ||||||
|  | 	// then we need to take it easy and relax a little | ||||||
|  | 	if b.Remaining < minRemaining && b.reset.After(time.Now()) { | ||||||
|  | 		return b.reset.Sub(time.Now()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Check for global ratelimits | ||||||
|  | 	sleepTo := time.Unix(0, atomic.LoadInt64(r.global)) | ||||||
|  | 	if now := time.Now(); now.Before(sleepTo) { | ||||||
|  | 		return sleepTo.Sub(now) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LockBucket Locks until a request can be made | ||||||
|  | func (r *RateLimiter) LockBucket(bucketID string) *Bucket { | ||||||
|  | 	return r.LockBucketObject(r.GetBucket(bucketID)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LockBucketObject Locks an already resolved bucket until a request can be made | ||||||
|  | func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket { | ||||||
|  | 	b.Lock() | ||||||
|  |  | ||||||
|  | 	if wait := r.GetWaitTime(b, 1); wait > 0 { | ||||||
|  | 		time.Sleep(wait) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b.Remaining-- | ||||||
|  | 	return b | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Bucket represents a ratelimit bucket, each bucket gets ratelimited individually (-global ratelimits) | ||||||
|  | type Bucket struct { | ||||||
|  | 	sync.Mutex | ||||||
|  | 	Key       string | ||||||
|  | 	Remaining int | ||||||
|  | 	limit     int | ||||||
|  | 	reset     time.Time | ||||||
|  | 	global    *int64 | ||||||
|  |  | ||||||
|  | 	lastReset       time.Time | ||||||
|  | 	customRateLimit *customRateLimit | ||||||
|  | 	Userdata        interface{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Release unlocks the bucket and reads the headers to update the buckets ratelimit info | ||||||
|  | // and locks up the whole thing in case if there's a global ratelimit. | ||||||
|  | func (b *Bucket) Release(headers http.Header) error { | ||||||
|  | 	defer b.Unlock() | ||||||
|  |  | ||||||
|  | 	// Check if the bucket uses a custom ratelimiter | ||||||
|  | 	if rl := b.customRateLimit; rl != nil { | ||||||
|  | 		if time.Now().Sub(b.lastReset) >= rl.reset { | ||||||
|  | 			b.Remaining = rl.requests - 1 | ||||||
|  | 			b.lastReset = time.Now() | ||||||
|  | 		} | ||||||
|  | 		if b.Remaining < 1 { | ||||||
|  | 			b.reset = time.Now().Add(rl.reset) | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if headers == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	remaining := headers.Get("X-RateLimit-Remaining") | ||||||
|  | 	reset := headers.Get("X-RateLimit-Reset") | ||||||
|  | 	global := headers.Get("X-RateLimit-Global") | ||||||
|  | 	retryAfter := headers.Get("Retry-After") | ||||||
|  |  | ||||||
|  | 	// Update global and per bucket reset time if the proper headers are available | ||||||
|  | 	// If global is set, then it will block all buckets until after Retry-After | ||||||
|  | 	// If Retry-After without global is provided it will use that for the new reset | ||||||
|  | 	// time since it's more accurate than X-RateLimit-Reset. | ||||||
|  | 	// If Retry-After after is not proided, it will update the reset time from X-RateLimit-Reset | ||||||
|  | 	if retryAfter != "" { | ||||||
|  | 		parsedAfter, err := strconv.ParseInt(retryAfter, 10, 64) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		resetAt := time.Now().Add(time.Duration(parsedAfter) * time.Millisecond) | ||||||
|  |  | ||||||
|  | 		// Lock either this single bucket or all buckets | ||||||
|  | 		if global != "" { | ||||||
|  | 			atomic.StoreInt64(b.global, resetAt.UnixNano()) | ||||||
|  | 		} else { | ||||||
|  | 			b.reset = resetAt | ||||||
|  | 		} | ||||||
|  | 	} else if reset != "" { | ||||||
|  | 		// Calculate the reset time by using the date header returned from discord | ||||||
|  | 		discordTime, err := http.ParseTime(headers.Get("Date")) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		unix, err := strconv.ParseInt(reset, 10, 64) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Calculate the time until reset and add it to the current local time | ||||||
|  | 		// some extra time is added because without it i still encountered 429's. | ||||||
|  | 		// The added amount is the lowest amount that gave no 429's | ||||||
|  | 		// in 1k requests | ||||||
|  | 		delta := time.Unix(unix, 0).Sub(discordTime) + time.Millisecond*250 | ||||||
|  | 		b.reset = time.Now().Add(delta) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Udpate remaining if header is present | ||||||
|  | 	if remaining != "" { | ||||||
|  | 		parsedRemaining, err := strconv.ParseInt(remaining, 10, 32) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		b.Remaining = int(parsedRemaining) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
							
								
								
									
										2049
									
								
								vendor/github.com/matterbridge/discordgo/restapi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2049
									
								
								vendor/github.com/matterbridge/discordgo/restapi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										990
									
								
								vendor/github.com/matterbridge/discordgo/state.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										990
									
								
								vendor/github.com/matterbridge/discordgo/state.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,990 @@ | |||||||
|  | // 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" | ||||||
|  | 	"sort" | ||||||
|  | 	"sync" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ErrNilState is returned when the state is nil. | ||||||
|  | var ErrNilState = errors.New("state not instantiated, please use discordgo.New() or assign Session.State") | ||||||
|  |  | ||||||
|  | // ErrStateNotFound is returned when the state cache | ||||||
|  | // requested is not found | ||||||
|  | var ErrStateNotFound = errors.New("state cache not found") | ||||||
|  |  | ||||||
|  | // A State contains the current known state. | ||||||
|  | // As discord sends this in a READY blob, it seems reasonable to simply | ||||||
|  | // use that struct as the data store. | ||||||
|  | type State struct { | ||||||
|  | 	sync.RWMutex | ||||||
|  | 	Ready | ||||||
|  |  | ||||||
|  | 	MaxMessageCount int | ||||||
|  | 	TrackChannels   bool | ||||||
|  | 	TrackEmojis     bool | ||||||
|  | 	TrackMembers    bool | ||||||
|  | 	TrackRoles      bool | ||||||
|  | 	TrackVoice      bool | ||||||
|  | 	TrackPresences  bool | ||||||
|  |  | ||||||
|  | 	guildMap   map[string]*Guild | ||||||
|  | 	channelMap map[string]*Channel | ||||||
|  | 	memberMap  map[string]map[string]*Member | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewState creates an empty state. | ||||||
|  | func NewState() *State { | ||||||
|  | 	return &State{ | ||||||
|  | 		Ready: Ready{ | ||||||
|  | 			PrivateChannels: []*Channel{}, | ||||||
|  | 			Guilds:          []*Guild{}, | ||||||
|  | 		}, | ||||||
|  | 		TrackChannels:  true, | ||||||
|  | 		TrackEmojis:    true, | ||||||
|  | 		TrackMembers:   true, | ||||||
|  | 		TrackRoles:     true, | ||||||
|  | 		TrackVoice:     true, | ||||||
|  | 		TrackPresences: true, | ||||||
|  | 		guildMap:       make(map[string]*Guild), | ||||||
|  | 		channelMap:     make(map[string]*Channel), | ||||||
|  | 		memberMap:      make(map[string]map[string]*Member), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *State) createMemberMap(guild *Guild) { | ||||||
|  | 	members := make(map[string]*Member) | ||||||
|  | 	for _, m := range guild.Members { | ||||||
|  | 		members[m.User.ID] = m | ||||||
|  | 	} | ||||||
|  | 	s.memberMap[guild.ID] = members | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildAdd adds a guild to the current world state, or | ||||||
|  | // updates it if it already exists. | ||||||
|  | func (s *State) GuildAdd(guild *Guild) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	// Update the channels to point to the right guild, adding them to the channelMap as we go | ||||||
|  | 	for _, c := range guild.Channels { | ||||||
|  | 		s.channelMap[c.ID] = c | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If this guild contains a new member slice, we must regenerate the member map so the pointers stay valid | ||||||
|  | 	if guild.Members != nil { | ||||||
|  | 		s.createMemberMap(guild) | ||||||
|  | 	} else if _, ok := s.memberMap[guild.ID]; !ok { | ||||||
|  | 		// Even if we have no new member slice, we still initialize the member map for this guild if it doesn't exist | ||||||
|  | 		s.memberMap[guild.ID] = make(map[string]*Member) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if g, ok := s.guildMap[guild.ID]; ok { | ||||||
|  | 		// We are about to replace `g` in the state with `guild`, but first we need to | ||||||
|  | 		// make sure we preserve any fields that the `guild` doesn't contain from `g`. | ||||||
|  | 		if guild.Roles == nil { | ||||||
|  | 			guild.Roles = g.Roles | ||||||
|  | 		} | ||||||
|  | 		if guild.Emojis == nil { | ||||||
|  | 			guild.Emojis = g.Emojis | ||||||
|  | 		} | ||||||
|  | 		if guild.Members == nil { | ||||||
|  | 			guild.Members = g.Members | ||||||
|  | 		} | ||||||
|  | 		if guild.Presences == nil { | ||||||
|  | 			guild.Presences = g.Presences | ||||||
|  | 		} | ||||||
|  | 		if guild.Channels == nil { | ||||||
|  | 			guild.Channels = g.Channels | ||||||
|  | 		} | ||||||
|  | 		if guild.VoiceStates == nil { | ||||||
|  | 			guild.VoiceStates = g.VoiceStates | ||||||
|  | 		} | ||||||
|  | 		*g = *guild | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Guilds = append(s.Guilds, guild) | ||||||
|  | 	s.guildMap[guild.ID] = guild | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildRemove removes a guild from current world state. | ||||||
|  | func (s *State) GuildRemove(guild *Guild) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := s.Guild(guild.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	delete(s.guildMap, guild.ID) | ||||||
|  |  | ||||||
|  | 	for i, g := range s.Guilds { | ||||||
|  | 		if g.ID == guild.ID { | ||||||
|  | 			s.Guilds = append(s.Guilds[:i], s.Guilds[i+1:]...) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Guild gets a guild by ID. | ||||||
|  | // Useful for querying if @me is in a guild: | ||||||
|  | //     _, err := discordgo.Session.State.Guild(guildID) | ||||||
|  | //     isInGuild := err == nil | ||||||
|  | func (s *State) Guild(guildID string) (*Guild, error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil, ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	defer s.RUnlock() | ||||||
|  |  | ||||||
|  | 	if g, ok := s.guildMap[guildID]; ok { | ||||||
|  | 		return g, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PresenceAdd adds a presence to the current world state, or | ||||||
|  | // updates it if it already exists. | ||||||
|  | func (s *State) PresenceAdd(guildID string, presence *Presence) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(guildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	for i, p := range guild.Presences { | ||||||
|  | 		if p.User.ID == presence.User.ID { | ||||||
|  | 			//guild.Presences[i] = presence | ||||||
|  |  | ||||||
|  | 			//Update status | ||||||
|  | 			guild.Presences[i].Game = presence.Game | ||||||
|  | 			guild.Presences[i].Roles = presence.Roles | ||||||
|  | 			if presence.Status != "" { | ||||||
|  | 				guild.Presences[i].Status = presence.Status | ||||||
|  | 			} | ||||||
|  | 			if presence.Nick != "" { | ||||||
|  | 				guild.Presences[i].Nick = presence.Nick | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			//Update the optionally sent user information | ||||||
|  | 			//ID Is a mandatory field so you should not need to check if it is empty | ||||||
|  | 			guild.Presences[i].User.ID = presence.User.ID | ||||||
|  |  | ||||||
|  | 			if presence.User.Avatar != "" { | ||||||
|  | 				guild.Presences[i].User.Avatar = presence.User.Avatar | ||||||
|  | 			} | ||||||
|  | 			if presence.User.Discriminator != "" { | ||||||
|  | 				guild.Presences[i].User.Discriminator = presence.User.Discriminator | ||||||
|  | 			} | ||||||
|  | 			if presence.User.Email != "" { | ||||||
|  | 				guild.Presences[i].User.Email = presence.User.Email | ||||||
|  | 			} | ||||||
|  | 			if presence.User.Token != "" { | ||||||
|  | 				guild.Presences[i].User.Token = presence.User.Token | ||||||
|  | 			} | ||||||
|  | 			if presence.User.Username != "" { | ||||||
|  | 				guild.Presences[i].User.Username = presence.User.Username | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild.Presences = append(guild.Presences, presence) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PresenceRemove removes a presence from the current world state. | ||||||
|  | func (s *State) PresenceRemove(guildID string, presence *Presence) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(guildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	for i, p := range guild.Presences { | ||||||
|  | 		if p.User.ID == presence.User.ID { | ||||||
|  | 			guild.Presences = append(guild.Presences[:i], guild.Presences[i+1:]...) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Presence gets a presence by ID from a guild. | ||||||
|  | func (s *State) Presence(guildID, userID string) (*Presence, error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil, ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(guildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, p := range guild.Presences { | ||||||
|  | 		if p.User.ID == userID { | ||||||
|  | 			return p, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TODO: Consider moving Guild state update methods onto *Guild. | ||||||
|  |  | ||||||
|  | // MemberAdd adds a member to the current world state, or | ||||||
|  | // updates it if it already exists. | ||||||
|  | func (s *State) MemberAdd(member *Member) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(member.GuildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	members, ok := s.memberMap[member.GuildID] | ||||||
|  | 	if !ok { | ||||||
|  | 		return ErrStateNotFound | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m, ok := members[member.User.ID] | ||||||
|  | 	if !ok { | ||||||
|  | 		members[member.User.ID] = member | ||||||
|  | 		guild.Members = append(guild.Members, member) | ||||||
|  | 	} else { | ||||||
|  | 		*m = *member // Update the actual data, which will also update the member pointer in the slice | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MemberRemove removes a member from current world state. | ||||||
|  | func (s *State) MemberRemove(member *Member) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(member.GuildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	members, ok := s.memberMap[member.GuildID] | ||||||
|  | 	if !ok { | ||||||
|  | 		return ErrStateNotFound | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, ok = members[member.User.ID] | ||||||
|  | 	if !ok { | ||||||
|  | 		return ErrStateNotFound | ||||||
|  | 	} | ||||||
|  | 	delete(members, member.User.ID) | ||||||
|  |  | ||||||
|  | 	for i, m := range guild.Members { | ||||||
|  | 		if m.User.ID == member.User.ID { | ||||||
|  | 			guild.Members = append(guild.Members[:i], guild.Members[i+1:]...) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Member gets a member by ID from a guild. | ||||||
|  | func (s *State) Member(guildID, userID string) (*Member, error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil, ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	defer s.RUnlock() | ||||||
|  |  | ||||||
|  | 	members, ok := s.memberMap[guildID] | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, ErrStateNotFound | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	m, ok := members[userID] | ||||||
|  | 	if ok { | ||||||
|  | 		return m, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RoleAdd adds a role to the current world state, or | ||||||
|  | // updates it if it already exists. | ||||||
|  | func (s *State) RoleAdd(guildID string, role *Role) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(guildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	for i, r := range guild.Roles { | ||||||
|  | 		if r.ID == role.ID { | ||||||
|  | 			guild.Roles[i] = role | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild.Roles = append(guild.Roles, role) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RoleRemove removes a role from current world state by ID. | ||||||
|  | func (s *State) RoleRemove(guildID, roleID string) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(guildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	for i, r := range guild.Roles { | ||||||
|  | 		if r.ID == roleID { | ||||||
|  | 			guild.Roles = append(guild.Roles[:i], guild.Roles[i+1:]...) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Role gets a role by ID from a guild. | ||||||
|  | func (s *State) Role(guildID, roleID string) (*Role, error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil, ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(guildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	defer s.RUnlock() | ||||||
|  |  | ||||||
|  | 	for _, r := range guild.Roles { | ||||||
|  | 		if r.ID == roleID { | ||||||
|  | 			return r, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChannelAdd adds a channel to the current world state, or | ||||||
|  | // updates it if it already exists. | ||||||
|  | // Channels may exist either as PrivateChannels or inside | ||||||
|  | // a guild. | ||||||
|  | func (s *State) ChannelAdd(channel *Channel) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	// If the channel exists, replace it | ||||||
|  | 	if c, ok := s.channelMap[channel.ID]; ok { | ||||||
|  | 		if channel.Messages == nil { | ||||||
|  | 			channel.Messages = c.Messages | ||||||
|  | 		} | ||||||
|  | 		if channel.PermissionOverwrites == nil { | ||||||
|  | 			channel.PermissionOverwrites = c.PermissionOverwrites | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		*c = *channel | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM { | ||||||
|  | 		s.PrivateChannels = append(s.PrivateChannels, channel) | ||||||
|  | 	} else { | ||||||
|  | 		guild, ok := s.guildMap[channel.GuildID] | ||||||
|  | 		if !ok { | ||||||
|  | 			return ErrStateNotFound | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		guild.Channels = append(guild.Channels, channel) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.channelMap[channel.ID] = channel | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChannelRemove removes a channel from current world state. | ||||||
|  | func (s *State) ChannelRemove(channel *Channel) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, err := s.Channel(channel.ID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if channel.Type == ChannelTypeDM || channel.Type == ChannelTypeGroupDM { | ||||||
|  | 		s.Lock() | ||||||
|  | 		defer s.Unlock() | ||||||
|  |  | ||||||
|  | 		for i, c := range s.PrivateChannels { | ||||||
|  | 			if c.ID == channel.ID { | ||||||
|  | 				s.PrivateChannels = append(s.PrivateChannels[:i], s.PrivateChannels[i+1:]...) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		guild, err := s.Guild(channel.GuildID) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		s.Lock() | ||||||
|  | 		defer s.Unlock() | ||||||
|  |  | ||||||
|  | 		for i, c := range guild.Channels { | ||||||
|  | 			if c.ID == channel.ID { | ||||||
|  | 				guild.Channels = append(guild.Channels[:i], guild.Channels[i+1:]...) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	delete(s.channelMap, channel.ID) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GuildChannel gets a channel by ID from a guild. | ||||||
|  | // This method is Deprecated, use Channel(channelID) | ||||||
|  | func (s *State) GuildChannel(guildID, channelID string) (*Channel, error) { | ||||||
|  | 	return s.Channel(channelID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PrivateChannel gets a private channel by ID. | ||||||
|  | // This method is Deprecated, use Channel(channelID) | ||||||
|  | func (s *State) PrivateChannel(channelID string) (*Channel, error) { | ||||||
|  | 	return s.Channel(channelID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Channel gets a channel by ID, it will look in all guilds and private channels. | ||||||
|  | func (s *State) Channel(channelID string) (*Channel, error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil, ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	defer s.RUnlock() | ||||||
|  |  | ||||||
|  | 	if c, ok := s.channelMap[channelID]; ok { | ||||||
|  | 		return c, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Emoji returns an emoji for a guild and emoji id. | ||||||
|  | func (s *State) Emoji(guildID, emojiID string) (*Emoji, error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil, ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(guildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	defer s.RUnlock() | ||||||
|  |  | ||||||
|  | 	for _, e := range guild.Emojis { | ||||||
|  | 		if e.ID == emojiID { | ||||||
|  | 			return e, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EmojiAdd adds an emoji to the current world state. | ||||||
|  | func (s *State) EmojiAdd(guildID string, emoji *Emoji) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(guildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	for i, e := range guild.Emojis { | ||||||
|  | 		if e.ID == emoji.ID { | ||||||
|  | 			guild.Emojis[i] = emoji | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild.Emojis = append(guild.Emojis, emoji) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // EmojisAdd adds multiple emojis to the world state. | ||||||
|  | func (s *State) EmojisAdd(guildID string, emojis []*Emoji) error { | ||||||
|  | 	for _, e := range emojis { | ||||||
|  | 		if err := s.EmojiAdd(guildID, e); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageAdd adds a message to the current world state, or updates it if it exists. | ||||||
|  | // If the channel cannot be found, the message is discarded. | ||||||
|  | // Messages are kept in state up to s.MaxMessageCount | ||||||
|  | func (s *State) MessageAdd(message *Message) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c, err := s.Channel(message.ChannelID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	// If the message exists, merge in the new message contents. | ||||||
|  | 	for _, m := range c.Messages { | ||||||
|  | 		if m.ID == message.ID { | ||||||
|  | 			if message.Content != "" { | ||||||
|  | 				m.Content = message.Content | ||||||
|  | 			} | ||||||
|  | 			if message.EditedTimestamp != "" { | ||||||
|  | 				m.EditedTimestamp = message.EditedTimestamp | ||||||
|  | 			} | ||||||
|  | 			if message.Mentions != nil { | ||||||
|  | 				m.Mentions = message.Mentions | ||||||
|  | 			} | ||||||
|  | 			if message.Embeds != nil { | ||||||
|  | 				m.Embeds = message.Embeds | ||||||
|  | 			} | ||||||
|  | 			if message.Attachments != nil { | ||||||
|  | 				m.Attachments = message.Attachments | ||||||
|  | 			} | ||||||
|  | 			if message.Timestamp != "" { | ||||||
|  | 				m.Timestamp = message.Timestamp | ||||||
|  | 			} | ||||||
|  | 			if message.Author != nil { | ||||||
|  | 				m.Author = message.Author | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.Messages = append(c.Messages, message) | ||||||
|  |  | ||||||
|  | 	if len(c.Messages) > s.MaxMessageCount { | ||||||
|  | 		c.Messages = c.Messages[len(c.Messages)-s.MaxMessageCount:] | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageRemove removes a message from the world state. | ||||||
|  | func (s *State) MessageRemove(message *Message) error { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return s.messageRemoveByID(message.ChannelID, message.ID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // messageRemoveByID removes a message by channelID and messageID from the world state. | ||||||
|  | func (s *State) messageRemoveByID(channelID, messageID string) error { | ||||||
|  | 	c, err := s.Channel(channelID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	for i, m := range c.Messages { | ||||||
|  | 		if m.ID == messageID { | ||||||
|  | 			c.Messages = append(c.Messages[:i], c.Messages[i+1:]...) | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *State) voiceStateUpdate(update *VoiceStateUpdate) error { | ||||||
|  | 	guild, err := s.Guild(update.GuildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	// Handle Leaving Channel | ||||||
|  | 	if update.ChannelID == "" { | ||||||
|  | 		for i, state := range guild.VoiceStates { | ||||||
|  | 			if state.UserID == update.UserID { | ||||||
|  | 				guild.VoiceStates = append(guild.VoiceStates[:i], guild.VoiceStates[i+1:]...) | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		for i, state := range guild.VoiceStates { | ||||||
|  | 			if state.UserID == update.UserID { | ||||||
|  | 				guild.VoiceStates[i] = update.VoiceState | ||||||
|  | 				return nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		guild.VoiceStates = append(guild.VoiceStates, update.VoiceState) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Message gets a message by channel and message ID. | ||||||
|  | func (s *State) Message(channelID, messageID string) (*Message, error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil, ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c, err := s.Channel(channelID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	defer s.RUnlock() | ||||||
|  |  | ||||||
|  | 	for _, m := range c.Messages { | ||||||
|  | 		if m.ID == messageID { | ||||||
|  | 			return m, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, ErrStateNotFound | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // OnReady takes a Ready event and updates all internal state. | ||||||
|  | func (s *State) onReady(se *Session, r *Ready) (err error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	// We must track at least the current user for Voice, even | ||||||
|  | 	// if state is disabled, store the bare essentials. | ||||||
|  | 	if !se.StateEnabled { | ||||||
|  | 		ready := Ready{ | ||||||
|  | 			Version:   r.Version, | ||||||
|  | 			SessionID: r.SessionID, | ||||||
|  | 			User:      r.User, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		s.Ready = ready | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Ready = *r | ||||||
|  |  | ||||||
|  | 	for _, g := range s.Guilds { | ||||||
|  | 		s.guildMap[g.ID] = g | ||||||
|  | 		s.createMemberMap(g) | ||||||
|  |  | ||||||
|  | 		for _, c := range g.Channels { | ||||||
|  | 			s.channelMap[c.ID] = c | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, c := range s.PrivateChannels { | ||||||
|  | 		s.channelMap[c.ID] = c | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // OnInterface handles all events related to states. | ||||||
|  | func (s *State) OnInterface(se *Session, i interface{}) (err error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	r, ok := i.(*Ready) | ||||||
|  | 	if ok { | ||||||
|  | 		return s.onReady(se, r) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !se.StateEnabled { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch t := i.(type) { | ||||||
|  | 	case *GuildCreate: | ||||||
|  | 		err = s.GuildAdd(t.Guild) | ||||||
|  | 	case *GuildUpdate: | ||||||
|  | 		err = s.GuildAdd(t.Guild) | ||||||
|  | 	case *GuildDelete: | ||||||
|  | 		err = s.GuildRemove(t.Guild) | ||||||
|  | 	case *GuildMemberAdd: | ||||||
|  | 		if s.TrackMembers { | ||||||
|  | 			err = s.MemberAdd(t.Member) | ||||||
|  | 		} | ||||||
|  | 	case *GuildMemberUpdate: | ||||||
|  | 		if s.TrackMembers { | ||||||
|  | 			err = s.MemberAdd(t.Member) | ||||||
|  | 		} | ||||||
|  | 	case *GuildMemberRemove: | ||||||
|  | 		if s.TrackMembers { | ||||||
|  | 			err = s.MemberRemove(t.Member) | ||||||
|  | 		} | ||||||
|  | 	case *GuildMembersChunk: | ||||||
|  | 		if s.TrackMembers { | ||||||
|  | 			for i := range t.Members { | ||||||
|  | 				t.Members[i].GuildID = t.GuildID | ||||||
|  | 				err = s.MemberAdd(t.Members[i]) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case *GuildRoleCreate: | ||||||
|  | 		if s.TrackRoles { | ||||||
|  | 			err = s.RoleAdd(t.GuildID, t.Role) | ||||||
|  | 		} | ||||||
|  | 	case *GuildRoleUpdate: | ||||||
|  | 		if s.TrackRoles { | ||||||
|  | 			err = s.RoleAdd(t.GuildID, t.Role) | ||||||
|  | 		} | ||||||
|  | 	case *GuildRoleDelete: | ||||||
|  | 		if s.TrackRoles { | ||||||
|  | 			err = s.RoleRemove(t.GuildID, t.RoleID) | ||||||
|  | 		} | ||||||
|  | 	case *GuildEmojisUpdate: | ||||||
|  | 		if s.TrackEmojis { | ||||||
|  | 			err = s.EmojisAdd(t.GuildID, t.Emojis) | ||||||
|  | 		} | ||||||
|  | 	case *ChannelCreate: | ||||||
|  | 		if s.TrackChannels { | ||||||
|  | 			err = s.ChannelAdd(t.Channel) | ||||||
|  | 		} | ||||||
|  | 	case *ChannelUpdate: | ||||||
|  | 		if s.TrackChannels { | ||||||
|  | 			err = s.ChannelAdd(t.Channel) | ||||||
|  | 		} | ||||||
|  | 	case *ChannelDelete: | ||||||
|  | 		if s.TrackChannels { | ||||||
|  | 			err = s.ChannelRemove(t.Channel) | ||||||
|  | 		} | ||||||
|  | 	case *MessageCreate: | ||||||
|  | 		if s.MaxMessageCount != 0 { | ||||||
|  | 			err = s.MessageAdd(t.Message) | ||||||
|  | 		} | ||||||
|  | 	case *MessageUpdate: | ||||||
|  | 		if s.MaxMessageCount != 0 { | ||||||
|  | 			err = s.MessageAdd(t.Message) | ||||||
|  | 		} | ||||||
|  | 	case *MessageDelete: | ||||||
|  | 		if s.MaxMessageCount != 0 { | ||||||
|  | 			err = s.MessageRemove(t.Message) | ||||||
|  | 		} | ||||||
|  | 	case *MessageDeleteBulk: | ||||||
|  | 		if s.MaxMessageCount != 0 { | ||||||
|  | 			for _, mID := range t.Messages { | ||||||
|  | 				s.messageRemoveByID(t.ChannelID, mID) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	case *VoiceStateUpdate: | ||||||
|  | 		if s.TrackVoice { | ||||||
|  | 			err = s.voiceStateUpdate(t) | ||||||
|  | 		} | ||||||
|  | 	case *PresenceUpdate: | ||||||
|  | 		if s.TrackPresences { | ||||||
|  | 			s.PresenceAdd(t.GuildID, &t.Presence) | ||||||
|  | 		} | ||||||
|  | 		if s.TrackMembers { | ||||||
|  | 			if t.Status == StatusOffline { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			var m *Member | ||||||
|  | 			m, err = s.Member(t.GuildID, t.User.ID) | ||||||
|  |  | ||||||
|  | 			if err != nil { | ||||||
|  | 				// Member not found; this is a user coming online | ||||||
|  | 				m = &Member{ | ||||||
|  | 					GuildID: t.GuildID, | ||||||
|  | 					Nick:    t.Nick, | ||||||
|  | 					User:    t.User, | ||||||
|  | 					Roles:   t.Roles, | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 			} else { | ||||||
|  |  | ||||||
|  | 				if t.Nick != "" { | ||||||
|  | 					m.Nick = t.Nick | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if t.User.Username != "" { | ||||||
|  | 					m.User.Username = t.User.Username | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// PresenceUpdates always contain a list of roles, so there's no need to check for an empty list here | ||||||
|  | 				m.Roles = t.Roles | ||||||
|  |  | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			err = s.MemberAdd(m) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UserChannelPermissions returns the permission of a user in a channel. | ||||||
|  | // userID    : The ID of the user to calculate permissions for. | ||||||
|  | // channelID : The ID of the channel to calculate permission for. | ||||||
|  | func (s *State) UserChannelPermissions(userID, channelID string) (apermissions int, err error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return 0, ErrNilState | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	channel, err := s.Channel(channelID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(channel.GuildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if userID == guild.OwnerID { | ||||||
|  | 		apermissions = PermissionAll | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	member, err := s.Member(guild.ID, userID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return memberPermissions(guild, channel, member), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UserColor returns the color of a user in a channel. | ||||||
|  | // While colors are defined at a Guild level, determining for a channel is more useful in message handlers. | ||||||
|  | // 0 is returned in cases of error, which is the color of @everyone. | ||||||
|  | // userID    : The ID of the user to calculate the color for. | ||||||
|  | // channelID   : The ID of the channel to calculate the color for. | ||||||
|  | func (s *State) UserColor(userID, channelID string) int { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	channel, err := s.Channel(channelID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	guild, err := s.Guild(channel.GuildID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	member, err := s.Member(guild.ID, userID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	roles := Roles(guild.Roles) | ||||||
|  | 	sort.Sort(roles) | ||||||
|  |  | ||||||
|  | 	for _, role := range roles { | ||||||
|  | 		for _, roleID := range member.Roles { | ||||||
|  | 			if role.ID == roleID { | ||||||
|  | 				if role.Color != 0 { | ||||||
|  | 					return role.Color | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0 | ||||||
|  | } | ||||||
							
								
								
									
										686
									
								
								vendor/github.com/matterbridge/discordgo/structs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										686
									
								
								vendor/github.com/matterbridge/discordgo/structs.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,686 @@ | |||||||
|  | // 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" | ||||||
|  | 	"net/http" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/gorilla/websocket" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // A Session represents a connection to the Discord API. | ||||||
|  | type Session struct { | ||||||
|  | 	sync.RWMutex | ||||||
|  |  | ||||||
|  | 	// General configurable settings. | ||||||
|  |  | ||||||
|  | 	// Authentication token for this session | ||||||
|  | 	Token string | ||||||
|  | 	MFA   bool | ||||||
|  |  | ||||||
|  | 	// Debug for printing JSON request/responses | ||||||
|  | 	Debug    bool // Deprecated, will be removed. | ||||||
|  | 	LogLevel int | ||||||
|  |  | ||||||
|  | 	// Should the session reconnect the websocket on errors. | ||||||
|  | 	ShouldReconnectOnError bool | ||||||
|  |  | ||||||
|  | 	// Should the session request compressed websocket data. | ||||||
|  | 	Compress bool | ||||||
|  |  | ||||||
|  | 	// Sharding | ||||||
|  | 	ShardID    int | ||||||
|  | 	ShardCount int | ||||||
|  |  | ||||||
|  | 	// Should state tracking be enabled. | ||||||
|  | 	// State tracking is the best way for getting the the users | ||||||
|  | 	// active guilds and the members of the guilds. | ||||||
|  | 	StateEnabled bool | ||||||
|  |  | ||||||
|  | 	// Whether or not to call event handlers synchronously. | ||||||
|  | 	// e.g false = launch event handlers in their own goroutines. | ||||||
|  | 	SyncEvents bool | ||||||
|  |  | ||||||
|  | 	// Exposed but should not be modified by User. | ||||||
|  |  | ||||||
|  | 	// Whether the Data Websocket is ready | ||||||
|  | 	DataReady bool // NOTE: Maye be deprecated soon | ||||||
|  |  | ||||||
|  | 	// Max number of REST API retries | ||||||
|  | 	MaxRestRetries int | ||||||
|  |  | ||||||
|  | 	// Status stores the currect status of the websocket connection | ||||||
|  | 	// this is being tested, may stay, may go away. | ||||||
|  | 	status int32 | ||||||
|  |  | ||||||
|  | 	// Whether the Voice Websocket is ready | ||||||
|  | 	VoiceReady bool // NOTE: Deprecated. | ||||||
|  |  | ||||||
|  | 	// Whether the UDP Connection is ready | ||||||
|  | 	UDPReady bool // NOTE: Deprecated | ||||||
|  |  | ||||||
|  | 	// Stores a mapping of guild id's to VoiceConnections | ||||||
|  | 	VoiceConnections map[string]*VoiceConnection | ||||||
|  |  | ||||||
|  | 	// Managed state object, updated internally with events when | ||||||
|  | 	// StateEnabled is true. | ||||||
|  | 	State *State | ||||||
|  |  | ||||||
|  | 	// The http client used for REST requests | ||||||
|  | 	Client *http.Client | ||||||
|  |  | ||||||
|  | 	// Stores the last HeartbeatAck that was recieved (in UTC) | ||||||
|  | 	LastHeartbeatAck time.Time | ||||||
|  |  | ||||||
|  | 	// used to deal with rate limits | ||||||
|  | 	Ratelimiter *RateLimiter | ||||||
|  |  | ||||||
|  | 	// Event handlers | ||||||
|  | 	handlersMu   sync.RWMutex | ||||||
|  | 	handlers     map[string][]*eventHandlerInstance | ||||||
|  | 	onceHandlers map[string][]*eventHandlerInstance | ||||||
|  |  | ||||||
|  | 	// The websocket connection. | ||||||
|  | 	wsConn *websocket.Conn | ||||||
|  |  | ||||||
|  | 	// When nil, the session is not listening. | ||||||
|  | 	listening chan interface{} | ||||||
|  |  | ||||||
|  | 	// sequence tracks the current gateway api websocket sequence number | ||||||
|  | 	sequence *int64 | ||||||
|  |  | ||||||
|  | 	// stores sessions current Discord Gateway | ||||||
|  | 	gateway string | ||||||
|  |  | ||||||
|  | 	// stores session ID of current Gateway connection | ||||||
|  | 	sessionID string | ||||||
|  |  | ||||||
|  | 	// used to make sure gateway websocket writes do not happen concurrently | ||||||
|  | 	wsMutex sync.Mutex | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A VoiceRegion stores data for a specific voice region server. | ||||||
|  | type VoiceRegion struct { | ||||||
|  | 	ID       string `json:"id"` | ||||||
|  | 	Name     string `json:"name"` | ||||||
|  | 	Hostname string `json:"sample_hostname"` | ||||||
|  | 	Port     int    `json:"sample_port"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A VoiceICE stores data for voice ICE servers. | ||||||
|  | type VoiceICE struct { | ||||||
|  | 	TTL     string       `json:"ttl"` | ||||||
|  | 	Servers []*ICEServer `json:"servers"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A ICEServer stores data for a specific voice ICE server. | ||||||
|  | type ICEServer struct { | ||||||
|  | 	URL        string `json:"url"` | ||||||
|  | 	Username   string `json:"username"` | ||||||
|  | 	Credential string `json:"credential"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A Invite stores all data related to a specific Discord Guild or Channel invite. | ||||||
|  | type Invite struct { | ||||||
|  | 	Guild     *Guild    `json:"guild"` | ||||||
|  | 	Channel   *Channel  `json:"channel"` | ||||||
|  | 	Inviter   *User     `json:"inviter"` | ||||||
|  | 	Code      string    `json:"code"` | ||||||
|  | 	CreatedAt Timestamp `json:"created_at"` | ||||||
|  | 	MaxAge    int       `json:"max_age"` | ||||||
|  | 	Uses      int       `json:"uses"` | ||||||
|  | 	MaxUses   int       `json:"max_uses"` | ||||||
|  | 	Revoked   bool      `json:"revoked"` | ||||||
|  | 	Temporary bool      `json:"temporary"` | ||||||
|  | 	Unique    bool      `json:"unique"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChannelType is the type of a Channel | ||||||
|  | type ChannelType int | ||||||
|  |  | ||||||
|  | // Block contains known ChannelType values | ||||||
|  | const ( | ||||||
|  | 	ChannelTypeGuildText ChannelType = iota | ||||||
|  | 	ChannelTypeDM | ||||||
|  | 	ChannelTypeGuildVoice | ||||||
|  | 	ChannelTypeGroupDM | ||||||
|  | 	ChannelTypeGuildCategory | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // A Channel holds all data related to an individual Discord channel. | ||||||
|  | type Channel struct { | ||||||
|  | 	ID                   string                 `json:"id"` | ||||||
|  | 	GuildID              string                 `json:"guild_id"` | ||||||
|  | 	Name                 string                 `json:"name"` | ||||||
|  | 	Topic                string                 `json:"topic"` | ||||||
|  | 	Type                 ChannelType            `json:"type"` | ||||||
|  | 	LastMessageID        string                 `json:"last_message_id"` | ||||||
|  | 	NSFW                 bool                   `json:"nsfw"` | ||||||
|  | 	Position             int                    `json:"position"` | ||||||
|  | 	Bitrate              int                    `json:"bitrate"` | ||||||
|  | 	Recipients           []*User                `json:"recipients"` | ||||||
|  | 	Messages             []*Message             `json:"-"` | ||||||
|  | 	PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites"` | ||||||
|  | 	ParentID             string                 `json:"parent_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A ChannelEdit holds Channel Feild data for a channel edit. | ||||||
|  | type ChannelEdit struct { | ||||||
|  | 	Name                 string                 `json:"name,omitempty"` | ||||||
|  | 	Topic                string                 `json:"topic,omitempty"` | ||||||
|  | 	NSFW                 bool                   `json:"nsfw,omitempty"` | ||||||
|  | 	Position             int                    `json:"position"` | ||||||
|  | 	Bitrate              int                    `json:"bitrate,omitempty"` | ||||||
|  | 	UserLimit            int                    `json:"user_limit,omitempty"` | ||||||
|  | 	PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` | ||||||
|  | 	ParentID             string                 `json:"parent_id,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 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"` | ||||||
|  | 	Animated      bool     `json:"animated"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // APIName returns an correctly formatted API name for use in the MessageReactions endpoints. | ||||||
|  | func (e *Emoji) APIName() string { | ||||||
|  | 	if e.ID != "" && e.Name != "" { | ||||||
|  | 		return e.Name + ":" + e.ID | ||||||
|  | 	} | ||||||
|  | 	if e.Name != "" { | ||||||
|  | 		return e.Name | ||||||
|  | 	} | ||||||
|  | 	return e.ID | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // VerificationLevel type definition | ||||||
|  | type VerificationLevel int | ||||||
|  |  | ||||||
|  | // Constants for VerificationLevel levels from 0 to 3 inclusive | ||||||
|  | const ( | ||||||
|  | 	VerificationLevelNone VerificationLevel = iota | ||||||
|  | 	VerificationLevelLow | ||||||
|  | 	VerificationLevelMedium | ||||||
|  | 	VerificationLevelHigh | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // A Guild holds all data related to a specific Discord Guild.  Guilds are also | ||||||
|  | // sometimes referred to as Servers in the Discord client. | ||||||
|  | type Guild struct { | ||||||
|  | 	ID                          string            `json:"id"` | ||||||
|  | 	Name                        string            `json:"name"` | ||||||
|  | 	Icon                        string            `json:"icon"` | ||||||
|  | 	Region                      string            `json:"region"` | ||||||
|  | 	AfkChannelID                string            `json:"afk_channel_id"` | ||||||
|  | 	EmbedChannelID              string            `json:"embed_channel_id"` | ||||||
|  | 	OwnerID                     string            `json:"owner_id"` | ||||||
|  | 	JoinedAt                    Timestamp         `json:"joined_at"` | ||||||
|  | 	Splash                      string            `json:"splash"` | ||||||
|  | 	AfkTimeout                  int               `json:"afk_timeout"` | ||||||
|  | 	MemberCount                 int               `json:"member_count"` | ||||||
|  | 	VerificationLevel           VerificationLevel `json:"verification_level"` | ||||||
|  | 	EmbedEnabled                bool              `json:"embed_enabled"` | ||||||
|  | 	Large                       bool              `json:"large"` // ?? | ||||||
|  | 	DefaultMessageNotifications int               `json:"default_message_notifications"` | ||||||
|  | 	Roles                       []*Role           `json:"roles"` | ||||||
|  | 	Emojis                      []*Emoji          `json:"emojis"` | ||||||
|  | 	Members                     []*Member         `json:"members"` | ||||||
|  | 	Presences                   []*Presence       `json:"presences"` | ||||||
|  | 	Channels                    []*Channel        `json:"channels"` | ||||||
|  | 	VoiceStates                 []*VoiceState     `json:"voice_states"` | ||||||
|  | 	Unavailable                 bool              `json:"unavailable"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A UserGuild holds a brief version of a Guild | ||||||
|  | type UserGuild struct { | ||||||
|  | 	ID          string `json:"id"` | ||||||
|  | 	Name        string `json:"name"` | ||||||
|  | 	Icon        string `json:"icon"` | ||||||
|  | 	Owner       bool   `json:"owner"` | ||||||
|  | 	Permissions int    `json:"permissions"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildParams stores all the data needed to update discord guild settings | ||||||
|  | type GuildParams struct { | ||||||
|  | 	Name                        string             `json:"name,omitempty"` | ||||||
|  | 	Region                      string             `json:"region,omitempty"` | ||||||
|  | 	VerificationLevel           *VerificationLevel `json:"verification_level,omitempty"` | ||||||
|  | 	DefaultMessageNotifications int                `json:"default_message_notifications,omitempty"` // TODO: Separate type? | ||||||
|  | 	AfkChannelID                string             `json:"afk_channel_id,omitempty"` | ||||||
|  | 	AfkTimeout                  int                `json:"afk_timeout,omitempty"` | ||||||
|  | 	Icon                        string             `json:"icon,omitempty"` | ||||||
|  | 	OwnerID                     string             `json:"owner_id,omitempty"` | ||||||
|  | 	Splash                      string             `json:"splash,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A Role stores information about Discord guild member roles. | ||||||
|  | type Role struct { | ||||||
|  | 	ID          string `json:"id"` | ||||||
|  | 	Name        string `json:"name"` | ||||||
|  | 	Managed     bool   `json:"managed"` | ||||||
|  | 	Mentionable bool   `json:"mentionable"` | ||||||
|  | 	Hoist       bool   `json:"hoist"` | ||||||
|  | 	Color       int    `json:"color"` | ||||||
|  | 	Position    int    `json:"position"` | ||||||
|  | 	Permissions int    `json:"permissions"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Roles are a collection of Role | ||||||
|  | type Roles []*Role | ||||||
|  |  | ||||||
|  | func (r Roles) Len() int { | ||||||
|  | 	return len(r) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r Roles) Less(i, j int) bool { | ||||||
|  | 	return r[i].Position > r[j].Position | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r Roles) Swap(i, j int) { | ||||||
|  | 	r[i], r[j] = r[j], r[i] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A VoiceState stores the voice states of Guilds | ||||||
|  | type VoiceState struct { | ||||||
|  | 	UserID    string `json:"user_id"` | ||||||
|  | 	SessionID string `json:"session_id"` | ||||||
|  | 	ChannelID string `json:"channel_id"` | ||||||
|  | 	GuildID   string `json:"guild_id"` | ||||||
|  | 	Suppress  bool   `json:"suppress"` | ||||||
|  | 	SelfMute  bool   `json:"self_mute"` | ||||||
|  | 	SelfDeaf  bool   `json:"self_deaf"` | ||||||
|  | 	Mute      bool   `json:"mute"` | ||||||
|  | 	Deaf      bool   `json:"deaf"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A Presence stores the online, offline, or idle and game status of Guild members. | ||||||
|  | type Presence struct { | ||||||
|  | 	User   *User    `json:"user"` | ||||||
|  | 	Status Status   `json:"status"` | ||||||
|  | 	Game   *Game    `json:"game"` | ||||||
|  | 	Nick   string   `json:"nick"` | ||||||
|  | 	Roles  []string `json:"roles"` | ||||||
|  | 	Since  *int     `json:"since"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GameType is the type of "game" (see GameType* consts) in the Game struct | ||||||
|  | type GameType int | ||||||
|  |  | ||||||
|  | // Valid GameType values | ||||||
|  | const ( | ||||||
|  | 	GameTypeGame GameType = iota | ||||||
|  | 	GameTypeStreaming | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // A Game struct holds the name of the "playing .." game for a user | ||||||
|  | type Game struct { | ||||||
|  | 	Name          string     `json:"name"` | ||||||
|  | 	Type          GameType   `json:"type"` | ||||||
|  | 	URL           string     `json:"url,omitempty"` | ||||||
|  | 	Details       string     `json:"details,omitempty"` | ||||||
|  | 	State         string     `json:"state,omitempty"` | ||||||
|  | 	TimeStamps    TimeStamps `json:"timestamps,omitempty"` | ||||||
|  | 	Assets        Assets     `json:"assets,omitempty"` | ||||||
|  | 	ApplicationID string     `json:"application_id,omitempty"` | ||||||
|  | 	Instance      int8       `json:"instance,omitempty"` | ||||||
|  | 	// TODO: Party and Secrets (unknown structure) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A TimeStamps struct contains start and end times used in the rich presence "playing .." Game | ||||||
|  | type TimeStamps struct { | ||||||
|  | 	EndTimestamp   int64 `json:"end,omitempty"` | ||||||
|  | 	StartTimestamp int64 `json:"start,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnmarshalJSON unmarshals JSON into TimeStamps struct | ||||||
|  | func (t *TimeStamps) UnmarshalJSON(b []byte) error { | ||||||
|  | 	temp := struct { | ||||||
|  | 		End   float64 `json:"end,omitempty"` | ||||||
|  | 		Start float64 `json:"start,omitempty"` | ||||||
|  | 	}{} | ||||||
|  | 	err := json.Unmarshal(b, &temp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	t.EndTimestamp = int64(temp.End) | ||||||
|  | 	t.StartTimestamp = int64(temp.Start) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // An Assets struct contains assets and labels used in the rich presence "playing .." Game | ||||||
|  | type Assets struct { | ||||||
|  | 	LargeImageID string `json:"large_image,omitempty"` | ||||||
|  | 	SmallImageID string `json:"small_image,omitempty"` | ||||||
|  | 	LargeText    string `json:"large_text,omitempty"` | ||||||
|  | 	SmallText    string `json:"small_text,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A Member stores user information for Guild members. | ||||||
|  | type Member struct { | ||||||
|  | 	GuildID  string   `json:"guild_id"` | ||||||
|  | 	JoinedAt string   `json:"joined_at"` | ||||||
|  | 	Nick     string   `json:"nick"` | ||||||
|  | 	Deaf     bool     `json:"deaf"` | ||||||
|  | 	Mute     bool     `json:"mute"` | ||||||
|  | 	User     *User    `json:"user"` | ||||||
|  | 	Roles    []string `json:"roles"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A Settings stores data for a specific users Discord client settings. | ||||||
|  | type Settings struct { | ||||||
|  | 	RenderEmbeds           bool               `json:"render_embeds"` | ||||||
|  | 	InlineEmbedMedia       bool               `json:"inline_embed_media"` | ||||||
|  | 	InlineAttachmentMedia  bool               `json:"inline_attachment_media"` | ||||||
|  | 	EnableTtsCommand       bool               `json:"enable_tts_command"` | ||||||
|  | 	MessageDisplayCompact  bool               `json:"message_display_compact"` | ||||||
|  | 	ShowCurrentGame        bool               `json:"show_current_game"` | ||||||
|  | 	ConvertEmoticons       bool               `json:"convert_emoticons"` | ||||||
|  | 	Locale                 string             `json:"locale"` | ||||||
|  | 	Theme                  string             `json:"theme"` | ||||||
|  | 	GuildPositions         []string           `json:"guild_positions"` | ||||||
|  | 	RestrictedGuilds       []string           `json:"restricted_guilds"` | ||||||
|  | 	FriendSourceFlags      *FriendSourceFlags `json:"friend_source_flags"` | ||||||
|  | 	Status                 Status             `json:"status"` | ||||||
|  | 	DetectPlatformAccounts bool               `json:"detect_platform_accounts"` | ||||||
|  | 	DeveloperMode          bool               `json:"developer_mode"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Status type definition | ||||||
|  | type Status string | ||||||
|  |  | ||||||
|  | // Constants for Status with the different current available status | ||||||
|  | const ( | ||||||
|  | 	StatusOnline       Status = "online" | ||||||
|  | 	StatusIdle         Status = "idle" | ||||||
|  | 	StatusDoNotDisturb Status = "dnd" | ||||||
|  | 	StatusInvisible    Status = "invisible" | ||||||
|  | 	StatusOffline      Status = "offline" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // FriendSourceFlags stores ... TODO :) | ||||||
|  | type FriendSourceFlags struct { | ||||||
|  | 	All           bool `json:"all"` | ||||||
|  | 	MutualGuilds  bool `json:"mutual_guilds"` | ||||||
|  | 	MutualFriends bool `json:"mutual_friends"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A Relationship between the logged in user and Relationship.User | ||||||
|  | type Relationship struct { | ||||||
|  | 	User *User  `json:"user"` | ||||||
|  | 	Type int    `json:"type"` // 1 = friend, 2 = blocked, 3 = incoming friend req, 4 = sent friend req | ||||||
|  | 	ID   string `json:"id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A TooManyRequests struct holds information received from Discord | ||||||
|  | // when receiving a HTTP 429 response. | ||||||
|  | type TooManyRequests struct { | ||||||
|  | 	Bucket     string        `json:"bucket"` | ||||||
|  | 	Message    string        `json:"message"` | ||||||
|  | 	RetryAfter time.Duration `json:"retry_after"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A ReadState stores data on the read state of channels. | ||||||
|  | type ReadState struct { | ||||||
|  | 	MentionCount  int    `json:"mention_count"` | ||||||
|  | 	LastMessageID string `json:"last_message_id"` | ||||||
|  | 	ID            string `json:"id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // An Ack is used to ack messages | ||||||
|  | type Ack struct { | ||||||
|  | 	Token string `json:"token"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildRole stores data for guild roles. | ||||||
|  | type GuildRole struct { | ||||||
|  | 	Role    *Role  `json:"role"` | ||||||
|  | 	GuildID string `json:"guild_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildBan stores data for a guild ban. | ||||||
|  | type GuildBan struct { | ||||||
|  | 	Reason string `json:"reason"` | ||||||
|  | 	User   *User  `json:"user"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildIntegration stores data for a guild integration. | ||||||
|  | type GuildIntegration struct { | ||||||
|  | 	ID                string                   `json:"id"` | ||||||
|  | 	Name              string                   `json:"name"` | ||||||
|  | 	Type              string                   `json:"type"` | ||||||
|  | 	Enabled           bool                     `json:"enabled"` | ||||||
|  | 	Syncing           bool                     `json:"syncing"` | ||||||
|  | 	RoleID            string                   `json:"role_id"` | ||||||
|  | 	ExpireBehavior    int                      `json:"expire_behavior"` | ||||||
|  | 	ExpireGracePeriod int                      `json:"expire_grace_period"` | ||||||
|  | 	User              *User                    `json:"user"` | ||||||
|  | 	Account           *GuildIntegrationAccount `json:"account"` | ||||||
|  | 	SyncedAt          int                      `json:"synced_at"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildIntegrationAccount stores data for a guild integration account. | ||||||
|  | type GuildIntegrationAccount struct { | ||||||
|  | 	ID   string `json:"id"` | ||||||
|  | 	Name string `json:"name"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A GuildEmbed stores data for a guild embed. | ||||||
|  | type GuildEmbed struct { | ||||||
|  | 	Enabled   bool   `json:"enabled"` | ||||||
|  | 	ChannelID string `json:"channel_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A UserGuildSettingsChannelOverride stores data for a channel override for a users guild settings. | ||||||
|  | type UserGuildSettingsChannelOverride struct { | ||||||
|  | 	Muted                bool   `json:"muted"` | ||||||
|  | 	MessageNotifications int    `json:"message_notifications"` | ||||||
|  | 	ChannelID            string `json:"channel_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A UserGuildSettings stores data for a users guild settings. | ||||||
|  | type UserGuildSettings struct { | ||||||
|  | 	SupressEveryone      bool                                `json:"suppress_everyone"` | ||||||
|  | 	Muted                bool                                `json:"muted"` | ||||||
|  | 	MobilePush           bool                                `json:"mobile_push"` | ||||||
|  | 	MessageNotifications int                                 `json:"message_notifications"` | ||||||
|  | 	GuildID              string                              `json:"guild_id"` | ||||||
|  | 	ChannelOverrides     []*UserGuildSettingsChannelOverride `json:"channel_overrides"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A UserGuildSettingsEdit stores data for editing UserGuildSettings | ||||||
|  | type UserGuildSettingsEdit struct { | ||||||
|  | 	SupressEveryone      bool                                         `json:"suppress_everyone"` | ||||||
|  | 	Muted                bool                                         `json:"muted"` | ||||||
|  | 	MobilePush           bool                                         `json:"mobile_push"` | ||||||
|  | 	MessageNotifications int                                          `json:"message_notifications"` | ||||||
|  | 	ChannelOverrides     map[string]*UserGuildSettingsChannelOverride `json:"channel_overrides"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // An APIErrorMessage is an api error message returned from discord | ||||||
|  | type APIErrorMessage struct { | ||||||
|  | 	Code    int    `json:"code"` | ||||||
|  | 	Message string `json:"message"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Webhook stores the data for a webhook. | ||||||
|  | type Webhook struct { | ||||||
|  | 	ID        string `json:"id"` | ||||||
|  | 	GuildID   string `json:"guild_id"` | ||||||
|  | 	ChannelID string `json:"channel_id"` | ||||||
|  | 	User      *User  `json:"user"` | ||||||
|  | 	Name      string `json:"name"` | ||||||
|  | 	Avatar    string `json:"avatar"` | ||||||
|  | 	Token     string `json:"token"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WebhookParams is a struct for webhook params, used in the WebhookExecute command. | ||||||
|  | type WebhookParams struct { | ||||||
|  | 	Content   string          `json:"content,omitempty"` | ||||||
|  | 	Username  string          `json:"username,omitempty"` | ||||||
|  | 	AvatarURL string          `json:"avatar_url,omitempty"` | ||||||
|  | 	TTS       bool            `json:"tts,omitempty"` | ||||||
|  | 	File      string          `json:"file,omitempty"` | ||||||
|  | 	Embeds    []*MessageEmbed `json:"embeds,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // MessageReaction stores the data for a message reaction. | ||||||
|  | type MessageReaction struct { | ||||||
|  | 	UserID    string `json:"user_id"` | ||||||
|  | 	MessageID string `json:"message_id"` | ||||||
|  | 	Emoji     Emoji  `json:"emoji"` | ||||||
|  | 	ChannelID string `json:"channel_id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GatewayBotResponse stores the data for the gateway/bot response | ||||||
|  | type GatewayBotResponse struct { | ||||||
|  | 	URL    string `json:"url"` | ||||||
|  | 	Shards int    `json:"shards"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Constants for the different bit offsets of text channel permissions | ||||||
|  | const ( | ||||||
|  | 	PermissionReadMessages = 1 << (iota + 10) | ||||||
|  | 	PermissionSendMessages | ||||||
|  | 	PermissionSendTTSMessages | ||||||
|  | 	PermissionManageMessages | ||||||
|  | 	PermissionEmbedLinks | ||||||
|  | 	PermissionAttachFiles | ||||||
|  | 	PermissionReadMessageHistory | ||||||
|  | 	PermissionMentionEveryone | ||||||
|  | 	PermissionUseExternalEmojis | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Constants for the different bit offsets of voice permissions | ||||||
|  | const ( | ||||||
|  | 	PermissionVoiceConnect = 1 << (iota + 20) | ||||||
|  | 	PermissionVoiceSpeak | ||||||
|  | 	PermissionVoiceMuteMembers | ||||||
|  | 	PermissionVoiceDeafenMembers | ||||||
|  | 	PermissionVoiceMoveMembers | ||||||
|  | 	PermissionVoiceUseVAD | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Constants for general management. | ||||||
|  | const ( | ||||||
|  | 	PermissionChangeNickname = 1 << (iota + 26) | ||||||
|  | 	PermissionManageNicknames | ||||||
|  | 	PermissionManageRoles | ||||||
|  | 	PermissionManageWebhooks | ||||||
|  | 	PermissionManageEmojis | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Constants for the different bit offsets of general permissions | ||||||
|  | const ( | ||||||
|  | 	PermissionCreateInstantInvite = 1 << iota | ||||||
|  | 	PermissionKickMembers | ||||||
|  | 	PermissionBanMembers | ||||||
|  | 	PermissionAdministrator | ||||||
|  | 	PermissionManageChannels | ||||||
|  | 	PermissionManageServer | ||||||
|  | 	PermissionAddReactions | ||||||
|  | 	PermissionViewAuditLogs | ||||||
|  |  | ||||||
|  | 	PermissionAllText = PermissionReadMessages | | ||||||
|  | 		PermissionSendMessages | | ||||||
|  | 		PermissionSendTTSMessages | | ||||||
|  | 		PermissionManageMessages | | ||||||
|  | 		PermissionEmbedLinks | | ||||||
|  | 		PermissionAttachFiles | | ||||||
|  | 		PermissionReadMessageHistory | | ||||||
|  | 		PermissionMentionEveryone | ||||||
|  | 	PermissionAllVoice = PermissionVoiceConnect | | ||||||
|  | 		PermissionVoiceSpeak | | ||||||
|  | 		PermissionVoiceMuteMembers | | ||||||
|  | 		PermissionVoiceDeafenMembers | | ||||||
|  | 		PermissionVoiceMoveMembers | | ||||||
|  | 		PermissionVoiceUseVAD | ||||||
|  | 	PermissionAllChannel = PermissionAllText | | ||||||
|  | 		PermissionAllVoice | | ||||||
|  | 		PermissionCreateInstantInvite | | ||||||
|  | 		PermissionManageRoles | | ||||||
|  | 		PermissionManageChannels | | ||||||
|  | 		PermissionAddReactions | | ||||||
|  | 		PermissionViewAuditLogs | ||||||
|  | 	PermissionAll = PermissionAllChannel | | ||||||
|  | 		PermissionKickMembers | | ||||||
|  | 		PermissionBanMembers | | ||||||
|  | 		PermissionManageServer | | ||||||
|  | 		PermissionAdministrator | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Block contains Discord JSON Error Response codes | ||||||
|  | const ( | ||||||
|  | 	ErrCodeUnknownAccount     = 10001 | ||||||
|  | 	ErrCodeUnknownApplication = 10002 | ||||||
|  | 	ErrCodeUnknownChannel     = 10003 | ||||||
|  | 	ErrCodeUnknownGuild       = 10004 | ||||||
|  | 	ErrCodeUnknownIntegration = 10005 | ||||||
|  | 	ErrCodeUnknownInvite      = 10006 | ||||||
|  | 	ErrCodeUnknownMember      = 10007 | ||||||
|  | 	ErrCodeUnknownMessage     = 10008 | ||||||
|  | 	ErrCodeUnknownOverwrite   = 10009 | ||||||
|  | 	ErrCodeUnknownProvider    = 10010 | ||||||
|  | 	ErrCodeUnknownRole        = 10011 | ||||||
|  | 	ErrCodeUnknownToken       = 10012 | ||||||
|  | 	ErrCodeUnknownUser        = 10013 | ||||||
|  | 	ErrCodeUnknownEmoji       = 10014 | ||||||
|  |  | ||||||
|  | 	ErrCodeBotsCannotUseEndpoint  = 20001 | ||||||
|  | 	ErrCodeOnlyBotsCanUseEndpoint = 20002 | ||||||
|  |  | ||||||
|  | 	ErrCodeMaximumGuildsReached     = 30001 | ||||||
|  | 	ErrCodeMaximumFriendsReached    = 30002 | ||||||
|  | 	ErrCodeMaximumPinsReached       = 30003 | ||||||
|  | 	ErrCodeMaximumGuildRolesReached = 30005 | ||||||
|  | 	ErrCodeTooManyReactions         = 30010 | ||||||
|  |  | ||||||
|  | 	ErrCodeUnauthorized = 40001 | ||||||
|  |  | ||||||
|  | 	ErrCodeMissingAccess                             = 50001 | ||||||
|  | 	ErrCodeInvalidAccountType                        = 50002 | ||||||
|  | 	ErrCodeCannotExecuteActionOnDMChannel            = 50003 | ||||||
|  | 	ErrCodeEmbedCisabled                             = 50004 | ||||||
|  | 	ErrCodeCannotEditFromAnotherUser                 = 50005 | ||||||
|  | 	ErrCodeCannotSendEmptyMessage                    = 50006 | ||||||
|  | 	ErrCodeCannotSendMessagesToThisUser              = 50007 | ||||||
|  | 	ErrCodeCannotSendMessagesInVoiceChannel          = 50008 | ||||||
|  | 	ErrCodeChannelVerificationLevelTooHigh           = 50009 | ||||||
|  | 	ErrCodeOAuth2ApplicationDoesNotHaveBot           = 50010 | ||||||
|  | 	ErrCodeOAuth2ApplicationLimitReached             = 50011 | ||||||
|  | 	ErrCodeInvalidOAuthState                         = 50012 | ||||||
|  | 	ErrCodeMissingPermissions                        = 50013 | ||||||
|  | 	ErrCodeInvalidAuthenticationToken                = 50014 | ||||||
|  | 	ErrCodeNoteTooLong                               = 50015 | ||||||
|  | 	ErrCodeTooFewOrTooManyMessagesToDelete           = 50016 | ||||||
|  | 	ErrCodeCanOnlyPinMessageToOriginatingChannel     = 50019 | ||||||
|  | 	ErrCodeCannotExecuteActionOnSystemMessage        = 50021 | ||||||
|  | 	ErrCodeMessageProvidedTooOldForBulkDelete        = 50034 | ||||||
|  | 	ErrCodeInvalidFormBody                           = 50035 | ||||||
|  | 	ErrCodeInviteAcceptedToGuildApplicationsBotNotIn = 50036 | ||||||
|  |  | ||||||
|  | 	ErrCodeReactionBlocked = 90001 | ||||||
|  | ) | ||||||
							
								
								
									
										124
									
								
								vendor/github.com/matterbridge/discordgo/tools/cmd/eventhandlers/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								vendor/github.com/matterbridge/discordgo/tools/cmd/eventhandlers/main.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"go/format" | ||||||
|  | 	"go/parser" | ||||||
|  | 	"go/token" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"regexp" | ||||||
|  | 	"sort" | ||||||
|  | 	"strings" | ||||||
|  | 	"text/template" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var eventHandlerTmpl = template.Must(template.New("eventHandler").Funcs(template.FuncMap{ | ||||||
|  | 	"constName":      constName, | ||||||
|  | 	"isDiscordEvent": isDiscordEvent, | ||||||
|  | 	"privateName":    privateName, | ||||||
|  | }).Parse(`// Code generated by \"eventhandlers\"; DO NOT EDIT | ||||||
|  | // See events.go | ||||||
|  |  | ||||||
|  | package discordgo | ||||||
|  |  | ||||||
|  | // Following are all the event types. | ||||||
|  | // Event type values are used to match the events returned by Discord. | ||||||
|  | // EventTypes surrounded by __ are synthetic and are internal to DiscordGo. | ||||||
|  | const ({{range .}} | ||||||
|  |   {{privateName .}}EventType = "{{constName .}}"{{end}} | ||||||
|  | ) | ||||||
|  | {{range .}} | ||||||
|  | // {{privateName .}}EventHandler is an event handler for {{.}} events. | ||||||
|  | type {{privateName .}}EventHandler func(*Session, *{{.}}) | ||||||
|  |  | ||||||
|  | // Type returns the event type for {{.}} events. | ||||||
|  | func (eh {{privateName .}}EventHandler) Type() string { | ||||||
|  |   return {{privateName .}}EventType | ||||||
|  | } | ||||||
|  | {{if isDiscordEvent .}} | ||||||
|  | // New returns a new instance of {{.}}. | ||||||
|  | func (eh {{privateName .}}EventHandler) New() interface{} { | ||||||
|  |   return &{{.}}{} | ||||||
|  | }{{end}} | ||||||
|  | // Handle is the handler for {{.}} events. | ||||||
|  | func (eh {{privateName .}}EventHandler) Handle(s *Session, i interface{}) { | ||||||
|  |   if t, ok := i.(*{{.}}); ok { | ||||||
|  |     eh(s, t) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | {{end}} | ||||||
|  | func handlerForInterface(handler interface{}) EventHandler { | ||||||
|  |   switch v := handler.(type) { | ||||||
|  |   case func(*Session, interface{}): | ||||||
|  |     return interfaceEventHandler(v){{range .}} | ||||||
|  |   case func(*Session, *{{.}}): | ||||||
|  |     return {{privateName .}}EventHandler(v){{end}} | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { {{range .}}{{if isDiscordEvent .}} | ||||||
|  |   registerInterfaceProvider({{privateName .}}EventHandler(nil)){{end}}{{end}} | ||||||
|  | } | ||||||
|  | `)) | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	dir := filepath.Dir(".") | ||||||
|  |  | ||||||
|  | 	fs := token.NewFileSet() | ||||||
|  | 	parsedFile, err := parser.ParseFile(fs, "events.go", nil, 0) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("warning: internal error: could not parse events.go: %s", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	names := []string{} | ||||||
|  | 	for object := range parsedFile.Scope.Objects { | ||||||
|  | 		names = append(names, object) | ||||||
|  | 	} | ||||||
|  | 	sort.Strings(names) | ||||||
|  | 	eventHandlerTmpl.Execute(&buf, names) | ||||||
|  |  | ||||||
|  | 	src, err := format.Source(buf.Bytes()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println("warning: internal error: invalid Go generated:", err) | ||||||
|  | 		src = buf.Bytes() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = ioutil.WriteFile(filepath.Join(dir, strings.ToLower("eventhandlers.go")), src, 0644) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatal(buf, "writing output: %s", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var constRegexp = regexp.MustCompile("([a-z])([A-Z])") | ||||||
|  |  | ||||||
|  | func constCase(name string) string { | ||||||
|  | 	return strings.ToUpper(constRegexp.ReplaceAllString(name, "${1}_${2}")) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func isDiscordEvent(name string) bool { | ||||||
|  | 	switch { | ||||||
|  | 	case name == "Connect", name == "Disconnect", name == "Event", name == "RateLimit", name == "Interface": | ||||||
|  | 		return false | ||||||
|  | 	default: | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func constName(name string) string { | ||||||
|  | 	if !isDiscordEvent(name) { | ||||||
|  | 		return "__" + constCase(name) + "__" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return constCase(name) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func privateName(name string) string { | ||||||
|  | 	return strings.ToLower(string(name[0])) + name[1:] | ||||||
|  | } | ||||||
							
								
								
									
										58
									
								
								vendor/github.com/matterbridge/discordgo/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								vendor/github.com/matterbridge/discordgo/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | // 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 custom types, currently only a timestamp wrapper. | ||||||
|  |  | ||||||
|  | package discordgo | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Timestamp stores a timestamp, as sent by the Discord API. | ||||||
|  | type Timestamp string | ||||||
|  |  | ||||||
|  | // Parse parses a timestamp string into a time.Time object. | ||||||
|  | // The only time this can fail is if Discord changes their timestamp format. | ||||||
|  | func (t Timestamp) Parse() (time.Time, error) { | ||||||
|  | 	return time.Parse(time.RFC3339, string(t)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RESTError stores error information about a request with a bad response code. | ||||||
|  | // Message is not always present, there are cases where api calls can fail | ||||||
|  | // without returning a json message. | ||||||
|  | type RESTError struct { | ||||||
|  | 	Request      *http.Request | ||||||
|  | 	Response     *http.Response | ||||||
|  | 	ResponseBody []byte | ||||||
|  |  | ||||||
|  | 	Message *APIErrorMessage // Message may be nil. | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newRestError(req *http.Request, resp *http.Response, body []byte) *RESTError { | ||||||
|  | 	restErr := &RESTError{ | ||||||
|  | 		Request:      req, | ||||||
|  | 		Response:     resp, | ||||||
|  | 		ResponseBody: body, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Attempt to decode the error and assume no message was provided if it fails | ||||||
|  | 	var msg *APIErrorMessage | ||||||
|  | 	err := json.Unmarshal(body, &msg) | ||||||
|  | 	if err == nil { | ||||||
|  | 		restErr.Message = msg | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return restErr | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r RESTError) Error() string { | ||||||
|  | 	return fmt.Sprintf("HTTP %s, %s", r.Response.Status, r.ResponseBody) | ||||||
|  | } | ||||||
							
								
								
									
										47
									
								
								vendor/github.com/matterbridge/discordgo/user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								vendor/github.com/matterbridge/discordgo/user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | package discordgo | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // A User stores all data for an individual Discord user. | ||||||
|  | type User struct { | ||||||
|  | 	ID            string `json:"id"` | ||||||
|  | 	Email         string `json:"email"` | ||||||
|  | 	Username      string `json:"username"` | ||||||
|  | 	Avatar        string `json:"avatar"` | ||||||
|  | 	Discriminator string `json:"discriminator"` | ||||||
|  | 	Token         string `json:"token"` | ||||||
|  | 	Verified      bool   `json:"verified"` | ||||||
|  | 	MFAEnabled    bool   `json:"mfa_enabled"` | ||||||
|  | 	Bot           bool   `json:"bot"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // String returns a unique identifier of the form username#discriminator | ||||||
|  | func (u *User) String() string { | ||||||
|  | 	return fmt.Sprintf("%s#%s", u.Username, u.Discriminator) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Mention return a string which mentions the user | ||||||
|  | func (u *User) Mention() string { | ||||||
|  | 	return fmt.Sprintf("<@%s>", u.ID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AvatarURL returns a URL to the user's avatar. | ||||||
|  | //    size:    The size of the user's avatar as a power of two | ||||||
|  | //             if size is an empty string, no size parameter will | ||||||
|  | //             be added to the URL. | ||||||
|  | func (u *User) AvatarURL(size string) string { | ||||||
|  | 	var URL string | ||||||
|  | 	if strings.HasPrefix(u.Avatar, "a_") { | ||||||
|  | 		URL = EndpointUserAvatarAnimated(u.ID, u.Avatar) | ||||||
|  | 	} else { | ||||||
|  | 		URL = EndpointUserAvatar(u.ID, u.Avatar) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if size != "" { | ||||||
|  | 		return URL + "?size=" + size | ||||||
|  | 	} | ||||||
|  | 	return URL | ||||||
|  | } | ||||||
							
								
								
									
										886
									
								
								vendor/github.com/matterbridge/discordgo/voice.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										886
									
								
								vendor/github.com/matterbridge/discordgo/voice.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,886 @@ | |||||||
|  | // 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" | ||||||
|  | 	"net" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/gorilla/websocket" | ||||||
|  | 	"golang.org/x/crypto/nacl/secretbox" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  | // Code related to both VoiceConnection Websocket and UDP connections. | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | // A VoiceConnection struct holds all the data and functions related to a Discord Voice Connection. | ||||||
|  | type VoiceConnection struct { | ||||||
|  | 	sync.RWMutex | ||||||
|  |  | ||||||
|  | 	Debug        bool // If true, print extra logging -- DEPRECATED | ||||||
|  | 	LogLevel     int | ||||||
|  | 	Ready        bool // If true, voice is ready to send/receive audio | ||||||
|  | 	UserID       string | ||||||
|  | 	GuildID      string | ||||||
|  | 	ChannelID    string | ||||||
|  | 	deaf         bool | ||||||
|  | 	mute         bool | ||||||
|  | 	speaking     bool | ||||||
|  | 	reconnecting bool // If true, voice connection is trying to reconnect | ||||||
|  |  | ||||||
|  | 	OpusSend chan []byte  // Chan for sending opus audio | ||||||
|  | 	OpusRecv chan *Packet // Chan for receiving opus audio | ||||||
|  |  | ||||||
|  | 	wsConn  *websocket.Conn | ||||||
|  | 	wsMutex sync.Mutex | ||||||
|  | 	udpConn *net.UDPConn | ||||||
|  | 	session *Session | ||||||
|  |  | ||||||
|  | 	sessionID string | ||||||
|  | 	token     string | ||||||
|  | 	endpoint  string | ||||||
|  |  | ||||||
|  | 	// Used to send a close signal to goroutines | ||||||
|  | 	close chan struct{} | ||||||
|  |  | ||||||
|  | 	// Used to allow blocking until connected | ||||||
|  | 	connected chan bool | ||||||
|  |  | ||||||
|  | 	// Used to pass the sessionid from onVoiceStateUpdate | ||||||
|  | 	// sessionRecv chan string UNUSED ATM | ||||||
|  |  | ||||||
|  | 	op4 voiceOP4 | ||||||
|  | 	op2 voiceOP2 | ||||||
|  |  | ||||||
|  | 	voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // VoiceSpeakingUpdateHandler type provides a function definition for the | ||||||
|  | // VoiceSpeakingUpdate event | ||||||
|  | type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate) | ||||||
|  |  | ||||||
|  | // Speaking sends a speaking notification to Discord over the voice websocket. | ||||||
|  | // This must be sent as true prior to sending audio and should be set to false | ||||||
|  | // once finished sending audio. | ||||||
|  | //  b  : Send true if speaking, false if not. | ||||||
|  | func (v *VoiceConnection) Speaking(b bool) (err error) { | ||||||
|  |  | ||||||
|  | 	v.log(LogDebug, "called (%t)", b) | ||||||
|  |  | ||||||
|  | 	type voiceSpeakingData struct { | ||||||
|  | 		Speaking bool `json:"speaking"` | ||||||
|  | 		Delay    int  `json:"delay"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	type voiceSpeakingOp struct { | ||||||
|  | 		Op   int               `json:"op"` // Always 5 | ||||||
|  | 		Data voiceSpeakingData `json:"d"` | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v.wsConn == nil { | ||||||
|  | 		return fmt.Errorf("no VoiceConnection websocket") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data := voiceSpeakingOp{5, voiceSpeakingData{b, 0}} | ||||||
|  | 	v.wsMutex.Lock() | ||||||
|  | 	err = v.wsConn.WriteJSON(data) | ||||||
|  | 	v.wsMutex.Unlock() | ||||||
|  |  | ||||||
|  | 	v.Lock() | ||||||
|  | 	defer v.Unlock() | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.speaking = false | ||||||
|  | 		v.log(LogError, "Speaking() write json error:", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v.speaking = b | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChangeChannel sends Discord a request to change channels within a Guild | ||||||
|  | // !!! NOTE !!! This function may be removed in favour of just using ChannelVoiceJoin | ||||||
|  | func (v *VoiceConnection) ChangeChannel(channelID string, mute, deaf bool) (err error) { | ||||||
|  |  | ||||||
|  | 	v.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, mute, deaf}} | ||||||
|  | 	v.wsMutex.Lock() | ||||||
|  | 	err = v.session.wsConn.WriteJSON(data) | ||||||
|  | 	v.wsMutex.Unlock() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	v.ChannelID = channelID | ||||||
|  | 	v.deaf = deaf | ||||||
|  | 	v.mute = mute | ||||||
|  | 	v.speaking = false | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Disconnect disconnects from this voice channel and closes the websocket | ||||||
|  | // and udp connections to Discord. | ||||||
|  | // !!! NOTE !!! this function may be removed in favour of ChannelVoiceLeave | ||||||
|  | func (v *VoiceConnection) Disconnect() (err error) { | ||||||
|  |  | ||||||
|  | 	// Send a OP4 with a nil channel to disconnect | ||||||
|  | 	if v.sessionID != "" { | ||||||
|  | 		data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} | ||||||
|  | 		v.session.wsMutex.Lock() | ||||||
|  | 		err = v.session.wsConn.WriteJSON(data) | ||||||
|  | 		v.session.wsMutex.Unlock() | ||||||
|  | 		v.sessionID = "" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Close websocket and udp connections | ||||||
|  | 	v.Close() | ||||||
|  |  | ||||||
|  | 	v.log(LogInformational, "Deleting VoiceConnection %s", v.GuildID) | ||||||
|  |  | ||||||
|  | 	v.session.Lock() | ||||||
|  | 	delete(v.session.VoiceConnections, v.GuildID) | ||||||
|  | 	v.session.Unlock() | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close closes the voice ws and udp connections | ||||||
|  | func (v *VoiceConnection) Close() { | ||||||
|  |  | ||||||
|  | 	v.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	v.Lock() | ||||||
|  | 	defer v.Unlock() | ||||||
|  |  | ||||||
|  | 	v.Ready = false | ||||||
|  | 	v.speaking = false | ||||||
|  |  | ||||||
|  | 	if v.close != nil { | ||||||
|  | 		v.log(LogInformational, "closing v.close") | ||||||
|  | 		close(v.close) | ||||||
|  | 		v.close = nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v.udpConn != nil { | ||||||
|  | 		v.log(LogInformational, "closing udp") | ||||||
|  | 		err := v.udpConn.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			v.log(LogError, "error closing udp connection: ", err) | ||||||
|  | 		} | ||||||
|  | 		v.udpConn = nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v.wsConn != nil { | ||||||
|  | 		v.log(LogInformational, "sending close frame") | ||||||
|  |  | ||||||
|  | 		// To cleanly close a connection, a client should send a close | ||||||
|  | 		// frame and wait for the server to close the connection. | ||||||
|  | 		v.wsMutex.Lock() | ||||||
|  | 		err := v.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) | ||||||
|  | 		v.wsMutex.Unlock() | ||||||
|  | 		if err != nil { | ||||||
|  | 			v.log(LogError, "error closing websocket, %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// TODO: Wait for Discord to actually close the connection. | ||||||
|  | 		time.Sleep(1 * time.Second) | ||||||
|  |  | ||||||
|  | 		v.log(LogInformational, "closing websocket") | ||||||
|  | 		err = v.wsConn.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			v.log(LogError, "error closing websocket, %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		v.wsConn = nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // AddHandler adds a Handler for VoiceSpeakingUpdate events. | ||||||
|  | func (v *VoiceConnection) AddHandler(h VoiceSpeakingUpdateHandler) { | ||||||
|  | 	v.Lock() | ||||||
|  | 	defer v.Unlock() | ||||||
|  |  | ||||||
|  | 	v.voiceSpeakingUpdateHandlers = append(v.voiceSpeakingUpdateHandlers, h) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // VoiceSpeakingUpdate is a struct for a VoiceSpeakingUpdate event. | ||||||
|  | type VoiceSpeakingUpdate struct { | ||||||
|  | 	UserID   string `json:"user_id"` | ||||||
|  | 	SSRC     int    `json:"ssrc"` | ||||||
|  | 	Speaking bool   `json:"speaking"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  | // Unexported Internal Functions Below. | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | // A voiceOP4 stores the data for the voice operation 4 websocket event | ||||||
|  | // which provides us with the NaCl SecretBox encryption key | ||||||
|  | type voiceOP4 struct { | ||||||
|  | 	SecretKey [32]byte `json:"secret_key"` | ||||||
|  | 	Mode      string   `json:"mode"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A voiceOP2 stores the data for the voice operation 2 websocket event | ||||||
|  | // which is sort of like the voice READY packet | ||||||
|  | type voiceOP2 struct { | ||||||
|  | 	SSRC              uint32        `json:"ssrc"` | ||||||
|  | 	Port              int           `json:"port"` | ||||||
|  | 	Modes             []string      `json:"modes"` | ||||||
|  | 	HeartbeatInterval time.Duration `json:"heartbeat_interval"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WaitUntilConnected waits for the Voice Connection to | ||||||
|  | // become ready, if it does not become ready it returns an err | ||||||
|  | func (v *VoiceConnection) waitUntilConnected() error { | ||||||
|  |  | ||||||
|  | 	v.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	i := 0 | ||||||
|  | 	for { | ||||||
|  | 		v.RLock() | ||||||
|  | 		ready := v.Ready | ||||||
|  | 		v.RUnlock() | ||||||
|  | 		if ready { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if i > 10 { | ||||||
|  | 			return fmt.Errorf("timeout waiting for voice") | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		time.Sleep(1 * time.Second) | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Open opens a voice connection.  This should be called | ||||||
|  | // after VoiceChannelJoin is used and the data VOICE websocket events | ||||||
|  | // are captured. | ||||||
|  | func (v *VoiceConnection) open() (err error) { | ||||||
|  |  | ||||||
|  | 	v.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	v.Lock() | ||||||
|  | 	defer v.Unlock() | ||||||
|  |  | ||||||
|  | 	// Don't open a websocket if one is already open | ||||||
|  | 	if v.wsConn != nil { | ||||||
|  | 		v.log(LogWarning, "refusing to overwrite non-nil websocket") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// TODO temp? loop to wait for the SessionID | ||||||
|  | 	i := 0 | ||||||
|  | 	for { | ||||||
|  | 		if v.sessionID != "" { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if i > 20 { // only loop for up to 1 second total | ||||||
|  | 			return fmt.Errorf("did not receive voice Session ID in time") | ||||||
|  | 		} | ||||||
|  | 		time.Sleep(50 * time.Millisecond) | ||||||
|  | 		i++ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Connect to VoiceConnection Websocket | ||||||
|  | 	vg := fmt.Sprintf("wss://%s", strings.TrimSuffix(v.endpoint, ":80")) | ||||||
|  | 	v.log(LogInformational, "connecting to voice endpoint %s", vg) | ||||||
|  | 	v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.log(LogWarning, "error connecting to voice endpoint %s, %s", vg, err) | ||||||
|  | 		v.log(LogDebug, "voice struct: %#v\n", v) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	type voiceHandshakeData struct { | ||||||
|  | 		ServerID  string `json:"server_id"` | ||||||
|  | 		UserID    string `json:"user_id"` | ||||||
|  | 		SessionID string `json:"session_id"` | ||||||
|  | 		Token     string `json:"token"` | ||||||
|  | 	} | ||||||
|  | 	type voiceHandshakeOp struct { | ||||||
|  | 		Op   int                `json:"op"` // Always 0 | ||||||
|  | 		Data voiceHandshakeData `json:"d"` | ||||||
|  | 	} | ||||||
|  | 	data := voiceHandshakeOp{0, voiceHandshakeData{v.GuildID, v.UserID, v.sessionID, v.token}} | ||||||
|  |  | ||||||
|  | 	err = v.wsConn.WriteJSON(data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.log(LogWarning, "error sending init packet, %s", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v.close = make(chan struct{}) | ||||||
|  | 	go v.wsListen(v.wsConn, v.close) | ||||||
|  |  | ||||||
|  | 	// add loop/check for Ready bool here? | ||||||
|  | 	// then return false if not ready? | ||||||
|  | 	// but then wsListen will also err. | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // wsListen listens on the voice websocket for messages and passes them | ||||||
|  | // to the voice event handler.  This is automatically called by the Open func | ||||||
|  | func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}) { | ||||||
|  |  | ||||||
|  | 	v.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		_, message, err := v.wsConn.ReadMessage() | ||||||
|  | 		if err != nil { | ||||||
|  | 			// Detect if we have been closed manually. If a Close() has already | ||||||
|  | 			// happened, the websocket we are listening on will be different to the | ||||||
|  | 			// current session. | ||||||
|  | 			v.RLock() | ||||||
|  | 			sameConnection := v.wsConn == wsConn | ||||||
|  | 			v.RUnlock() | ||||||
|  | 			if sameConnection { | ||||||
|  |  | ||||||
|  | 				v.log(LogError, "voice endpoint %s websocket closed unexpectantly, %s", v.endpoint, err) | ||||||
|  |  | ||||||
|  | 				// Start reconnect goroutine then exit. | ||||||
|  | 				go v.reconnect() | ||||||
|  | 			} | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Pass received message to voice event handler | ||||||
|  | 		select { | ||||||
|  | 		case <-close: | ||||||
|  | 			return | ||||||
|  | 		default: | ||||||
|  | 			go v.onEvent(message) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // wsEvent handles any voice websocket events. This is only called by the | ||||||
|  | // wsListen() function. | ||||||
|  | func (v *VoiceConnection) onEvent(message []byte) { | ||||||
|  |  | ||||||
|  | 	v.log(LogDebug, "received: %s", string(message)) | ||||||
|  |  | ||||||
|  | 	var e Event | ||||||
|  | 	if err := json.Unmarshal(message, &e); err != nil { | ||||||
|  | 		v.log(LogError, "unmarshall error, %s", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch e.Operation { | ||||||
|  |  | ||||||
|  | 	case 2: // READY | ||||||
|  |  | ||||||
|  | 		if err := json.Unmarshal(e.RawData, &v.op2); err != nil { | ||||||
|  | 			v.log(LogError, "OP2 unmarshall error, %s, %s", err, string(e.RawData)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Start the voice websocket heartbeat to keep the connection alive | ||||||
|  | 		go v.wsHeartbeat(v.wsConn, v.close, v.op2.HeartbeatInterval) | ||||||
|  | 		// TODO monitor a chan/bool to verify this was successful | ||||||
|  |  | ||||||
|  | 		// Start the UDP connection | ||||||
|  | 		err := v.udpOpen() | ||||||
|  | 		if err != nil { | ||||||
|  | 			v.log(LogError, "error opening udp connection, %s", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Start the opusSender. | ||||||
|  | 		// TODO: Should we allow 48000/960 values to be user defined? | ||||||
|  | 		if v.OpusSend == nil { | ||||||
|  | 			v.OpusSend = make(chan []byte, 2) | ||||||
|  | 		} | ||||||
|  | 		go v.opusSender(v.udpConn, v.close, v.OpusSend, 48000, 960) | ||||||
|  |  | ||||||
|  | 		// Start the opusReceiver | ||||||
|  | 		if !v.deaf { | ||||||
|  | 			if v.OpusRecv == nil { | ||||||
|  | 				v.OpusRecv = make(chan *Packet, 2) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			go v.opusReceiver(v.udpConn, v.close, v.OpusRecv) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return | ||||||
|  |  | ||||||
|  | 	case 3: // HEARTBEAT response | ||||||
|  | 		// add code to use this to track latency? | ||||||
|  | 		return | ||||||
|  |  | ||||||
|  | 	case 4: // udp encryption secret key | ||||||
|  | 		v.Lock() | ||||||
|  | 		defer v.Unlock() | ||||||
|  |  | ||||||
|  | 		v.op4 = voiceOP4{} | ||||||
|  | 		if err := json.Unmarshal(e.RawData, &v.op4); err != nil { | ||||||
|  | 			v.log(LogError, "OP4 unmarshall error, %s, %s", err, string(e.RawData)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		return | ||||||
|  |  | ||||||
|  | 	case 5: | ||||||
|  | 		if len(v.voiceSpeakingUpdateHandlers) == 0 { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		voiceSpeakingUpdate := &VoiceSpeakingUpdate{} | ||||||
|  | 		if err := json.Unmarshal(e.RawData, voiceSpeakingUpdate); err != nil { | ||||||
|  | 			v.log(LogError, "OP5 unmarshall error, %s, %s", err, string(e.RawData)) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, h := range v.voiceSpeakingUpdateHandlers { | ||||||
|  | 			h(v, voiceSpeakingUpdate) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	default: | ||||||
|  | 		v.log(LogDebug, "unknown voice operation, %d, %s", e.Operation, string(e.RawData)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type voiceHeartbeatOp struct { | ||||||
|  | 	Op   int `json:"op"` // Always 3 | ||||||
|  | 	Data int `json:"d"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NOTE :: When a guild voice server changes how do we shut this down | ||||||
|  | // properly, so a new connection can be setup without fuss? | ||||||
|  | // | ||||||
|  | // wsHeartbeat sends regular heartbeats to voice Discord so it knows the client | ||||||
|  | // is still connected.  If you do not send these heartbeats Discord will | ||||||
|  | // disconnect the websocket connection after a few seconds. | ||||||
|  | func (v *VoiceConnection) wsHeartbeat(wsConn *websocket.Conn, close <-chan struct{}, i time.Duration) { | ||||||
|  |  | ||||||
|  | 	if close == nil || wsConn == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	ticker := time.NewTicker(i * time.Millisecond) | ||||||
|  | 	defer ticker.Stop() | ||||||
|  | 	for { | ||||||
|  | 		v.log(LogDebug, "sending heartbeat packet") | ||||||
|  | 		v.wsMutex.Lock() | ||||||
|  | 		err = wsConn.WriteJSON(voiceHeartbeatOp{3, int(time.Now().Unix())}) | ||||||
|  | 		v.wsMutex.Unlock() | ||||||
|  | 		if err != nil { | ||||||
|  | 			v.log(LogError, "error sending heartbeat to voice endpoint %s, %s", v.endpoint, err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case <-ticker.C: | ||||||
|  | 			// continue loop and send heartbeat | ||||||
|  | 		case <-close: | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  | // Code related to the VoiceConnection UDP connection | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | type voiceUDPData struct { | ||||||
|  | 	Address string `json:"address"` // Public IP of machine running this code | ||||||
|  | 	Port    uint16 `json:"port"`    // UDP Port of machine running this code | ||||||
|  | 	Mode    string `json:"mode"`    // always "xsalsa20_poly1305" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type voiceUDPD struct { | ||||||
|  | 	Protocol string       `json:"protocol"` // Always "udp" ? | ||||||
|  | 	Data     voiceUDPData `json:"data"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type voiceUDPOp struct { | ||||||
|  | 	Op   int       `json:"op"` // Always 1 | ||||||
|  | 	Data voiceUDPD `json:"d"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // udpOpen opens a UDP connection to the voice server and completes the | ||||||
|  | // initial required handshake.  This connection is left open in the session | ||||||
|  | // and can be used to send or receive audio.  This should only be called | ||||||
|  | // from voice.wsEvent OP2 | ||||||
|  | func (v *VoiceConnection) udpOpen() (err error) { | ||||||
|  |  | ||||||
|  | 	v.Lock() | ||||||
|  | 	defer v.Unlock() | ||||||
|  |  | ||||||
|  | 	if v.wsConn == nil { | ||||||
|  | 		return fmt.Errorf("nil voice websocket") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v.udpConn != nil { | ||||||
|  | 		return fmt.Errorf("udp connection already open") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v.close == nil { | ||||||
|  | 		return fmt.Errorf("nil close channel") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v.endpoint == "" { | ||||||
|  | 		return fmt.Errorf("empty endpoint") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	host := fmt.Sprintf("%s:%d", strings.TrimSuffix(v.endpoint, ":80"), v.op2.Port) | ||||||
|  | 	addr, err := net.ResolveUDPAddr("udp", host) | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.log(LogWarning, "error resolving udp host %s, %s", host, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	v.log(LogInformational, "connecting to udp addr %s", addr.String()) | ||||||
|  | 	v.udpConn, err = net.DialUDP("udp", nil, addr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.log(LogWarning, "error connecting to udp addr %s, %s", addr.String(), err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Create a 70 byte array and put the SSRC code from the Op 2 VoiceConnection event | ||||||
|  | 	// into it.  Then send that over the UDP connection to Discord | ||||||
|  | 	sb := make([]byte, 70) | ||||||
|  | 	binary.BigEndian.PutUint32(sb, v.op2.SSRC) | ||||||
|  | 	_, err = v.udpConn.Write(sb) | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.log(LogWarning, "udp write error to %s, %s", addr.String(), err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Create a 70 byte array and listen for the initial handshake response | ||||||
|  | 	// from Discord.  Once we get it parse the IP and PORT information out | ||||||
|  | 	// of the response.  This should be our public IP and PORT as Discord | ||||||
|  | 	// saw us. | ||||||
|  | 	rb := make([]byte, 70) | ||||||
|  | 	rlen, _, err := v.udpConn.ReadFromUDP(rb) | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.log(LogWarning, "udp read error, %s, %s", addr.String(), err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if rlen < 70 { | ||||||
|  | 		v.log(LogWarning, "received udp packet too small") | ||||||
|  | 		return fmt.Errorf("received udp packet too small") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Loop over position 4 through 20 to grab the IP address | ||||||
|  | 	// Should never be beyond position 20. | ||||||
|  | 	var ip string | ||||||
|  | 	for i := 4; i < 20; i++ { | ||||||
|  | 		if rb[i] == 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		ip += string(rb[i]) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Grab port from position 68 and 69 | ||||||
|  | 	port := binary.LittleEndian.Uint16(rb[68:70]) | ||||||
|  |  | ||||||
|  | 	// Take the data from above and send it back to Discord to finalize | ||||||
|  | 	// the UDP connection handshake. | ||||||
|  | 	data := voiceUDPOp{1, voiceUDPD{"udp", voiceUDPData{ip, port, "xsalsa20_poly1305"}}} | ||||||
|  |  | ||||||
|  | 	v.wsMutex.Lock() | ||||||
|  | 	err = v.wsConn.WriteJSON(data) | ||||||
|  | 	v.wsMutex.Unlock() | ||||||
|  | 	if err != nil { | ||||||
|  | 		v.log(LogWarning, "udp write error, %#v, %s", data, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// start udpKeepAlive | ||||||
|  | 	go v.udpKeepAlive(v.udpConn, v.close, 5*time.Second) | ||||||
|  | 	// TODO: find a way to check that it fired off okay | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // udpKeepAlive sends a udp packet to keep the udp connection open | ||||||
|  | // This is still a bit of a "proof of concept" | ||||||
|  | func (v *VoiceConnection) udpKeepAlive(udpConn *net.UDPConn, close <-chan struct{}, i time.Duration) { | ||||||
|  |  | ||||||
|  | 	if udpConn == nil || close == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	var sequence uint64 | ||||||
|  |  | ||||||
|  | 	packet := make([]byte, 8) | ||||||
|  |  | ||||||
|  | 	ticker := time.NewTicker(i) | ||||||
|  | 	defer ticker.Stop() | ||||||
|  | 	for { | ||||||
|  |  | ||||||
|  | 		binary.LittleEndian.PutUint64(packet, sequence) | ||||||
|  | 		sequence++ | ||||||
|  |  | ||||||
|  | 		_, err = udpConn.Write(packet) | ||||||
|  | 		if err != nil { | ||||||
|  | 			v.log(LogError, "write error, %s", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case <-ticker.C: | ||||||
|  | 			// continue loop and send keepalive | ||||||
|  | 		case <-close: | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // opusSender will listen on the given channel and send any | ||||||
|  | // pre-encoded opus audio to Discord.  Supposedly. | ||||||
|  | func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}, opus <-chan []byte, rate, size int) { | ||||||
|  |  | ||||||
|  | 	if udpConn == nil || close == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// VoiceConnection is now ready to receive audio packets | ||||||
|  | 	// TODO: this needs reviewed as I think there must be a better way. | ||||||
|  | 	v.Lock() | ||||||
|  | 	v.Ready = true | ||||||
|  | 	v.Unlock() | ||||||
|  | 	defer func() { | ||||||
|  | 		v.Lock() | ||||||
|  | 		v.Ready = false | ||||||
|  | 		v.Unlock() | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	var sequence uint16 | ||||||
|  | 	var timestamp uint32 | ||||||
|  | 	var recvbuf []byte | ||||||
|  | 	var ok bool | ||||||
|  | 	udpHeader := make([]byte, 12) | ||||||
|  | 	var nonce [24]byte | ||||||
|  |  | ||||||
|  | 	// build the parts that don't change in the udpHeader | ||||||
|  | 	udpHeader[0] = 0x80 | ||||||
|  | 	udpHeader[1] = 0x78 | ||||||
|  | 	binary.BigEndian.PutUint32(udpHeader[8:], v.op2.SSRC) | ||||||
|  |  | ||||||
|  | 	// start a send loop that loops until buf chan is closed | ||||||
|  | 	ticker := time.NewTicker(time.Millisecond * time.Duration(size/(rate/1000))) | ||||||
|  | 	defer ticker.Stop() | ||||||
|  | 	for { | ||||||
|  |  | ||||||
|  | 		// Get data from chan.  If chan is closed, return. | ||||||
|  | 		select { | ||||||
|  | 		case <-close: | ||||||
|  | 			return | ||||||
|  | 		case recvbuf, ok = <-opus: | ||||||
|  | 			if !ok { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			// else, continue loop | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		v.RLock() | ||||||
|  | 		speaking := v.speaking | ||||||
|  | 		v.RUnlock() | ||||||
|  | 		if !speaking { | ||||||
|  | 			err := v.Speaking(true) | ||||||
|  | 			if err != nil { | ||||||
|  | 				v.log(LogError, "error sending speaking packet, %s", err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Add sequence and timestamp to udpPacket | ||||||
|  | 		binary.BigEndian.PutUint16(udpHeader[2:], sequence) | ||||||
|  | 		binary.BigEndian.PutUint32(udpHeader[4:], timestamp) | ||||||
|  |  | ||||||
|  | 		// encrypt the opus data | ||||||
|  | 		copy(nonce[:], udpHeader) | ||||||
|  | 		v.RLock() | ||||||
|  | 		sendbuf := secretbox.Seal(udpHeader, recvbuf, &nonce, &v.op4.SecretKey) | ||||||
|  | 		v.RUnlock() | ||||||
|  |  | ||||||
|  | 		// block here until we're exactly at the right time :) | ||||||
|  | 		// Then send rtp audio packet to Discord over UDP | ||||||
|  | 		select { | ||||||
|  | 		case <-close: | ||||||
|  | 			return | ||||||
|  | 		case <-ticker.C: | ||||||
|  | 			// continue | ||||||
|  | 		} | ||||||
|  | 		_, err := udpConn.Write(sendbuf) | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			v.log(LogError, "udp write error, %s", err) | ||||||
|  | 			v.log(LogDebug, "voice struct: %#v\n", v) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (sequence) == 0xFFFF { | ||||||
|  | 			sequence = 0 | ||||||
|  | 		} else { | ||||||
|  | 			sequence++ | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (timestamp + uint32(size)) >= 0xFFFFFFFF { | ||||||
|  | 			timestamp = 0 | ||||||
|  | 		} else { | ||||||
|  | 			timestamp += uint32(size) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A Packet contains the headers and content of a received voice packet. | ||||||
|  | type Packet struct { | ||||||
|  | 	SSRC      uint32 | ||||||
|  | 	Sequence  uint16 | ||||||
|  | 	Timestamp uint32 | ||||||
|  | 	Type      []byte | ||||||
|  | 	Opus      []byte | ||||||
|  | 	PCM       []int16 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // opusReceiver listens on the UDP socket for incoming packets | ||||||
|  | // and sends them across the given channel | ||||||
|  | // NOTE :: This function may change names later. | ||||||
|  | func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct{}, c chan *Packet) { | ||||||
|  |  | ||||||
|  | 	if udpConn == nil || close == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	recvbuf := make([]byte, 1024) | ||||||
|  | 	var nonce [24]byte | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		rlen, err := udpConn.Read(recvbuf) | ||||||
|  | 		if err != nil { | ||||||
|  | 			// Detect if we have been closed manually. If a Close() has already | ||||||
|  | 			// happened, the udp connection we are listening on will be different | ||||||
|  | 			// to the current session. | ||||||
|  | 			v.RLock() | ||||||
|  | 			sameConnection := v.udpConn == udpConn | ||||||
|  | 			v.RUnlock() | ||||||
|  | 			if sameConnection { | ||||||
|  |  | ||||||
|  | 				v.log(LogError, "udp read error, %s, %s", v.endpoint, err) | ||||||
|  | 				v.log(LogDebug, "voice struct: %#v\n", v) | ||||||
|  |  | ||||||
|  | 				go v.reconnect() | ||||||
|  | 			} | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case <-close: | ||||||
|  | 			return | ||||||
|  | 		default: | ||||||
|  | 			// continue loop | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// For now, skip anything except audio. | ||||||
|  | 		if rlen < 12 || (recvbuf[0] != 0x80 && recvbuf[0] != 0x90) { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// build a audio packet struct | ||||||
|  | 		p := Packet{} | ||||||
|  | 		p.Type = recvbuf[0:2] | ||||||
|  | 		p.Sequence = binary.BigEndian.Uint16(recvbuf[2:4]) | ||||||
|  | 		p.Timestamp = binary.BigEndian.Uint32(recvbuf[4:8]) | ||||||
|  | 		p.SSRC = binary.BigEndian.Uint32(recvbuf[8:12]) | ||||||
|  | 		// decrypt opus data | ||||||
|  | 		copy(nonce[:], recvbuf[0:12]) | ||||||
|  | 		p.Opus, _ = secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey) | ||||||
|  |  | ||||||
|  | 		if len(p.Opus) > 8 && recvbuf[0] == 0x90 { | ||||||
|  | 			// Extension bit is set, first 8 bytes is the extended header | ||||||
|  | 			p.Opus = p.Opus[8:] | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if c != nil { | ||||||
|  | 			select { | ||||||
|  | 			case c <- &p: | ||||||
|  | 			case <-close: | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Reconnect will close down a voice connection then immediately try to | ||||||
|  | // reconnect to that session. | ||||||
|  | // NOTE : This func is messy and a WIP while I find what works. | ||||||
|  | // It will be cleaned up once a proven stable option is flushed out. | ||||||
|  | // aka: this is ugly shit code, please don't judge too harshly. | ||||||
|  | func (v *VoiceConnection) reconnect() { | ||||||
|  |  | ||||||
|  | 	v.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	v.Lock() | ||||||
|  | 	if v.reconnecting { | ||||||
|  | 		v.log(LogInformational, "already reconnecting to channel %s, exiting", v.ChannelID) | ||||||
|  | 		v.Unlock() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	v.reconnecting = true | ||||||
|  | 	v.Unlock() | ||||||
|  |  | ||||||
|  | 	defer func() { v.reconnecting = false }() | ||||||
|  |  | ||||||
|  | 	// Close any currently open connections | ||||||
|  | 	v.Close() | ||||||
|  |  | ||||||
|  | 	wait := time.Duration(1) | ||||||
|  | 	for { | ||||||
|  |  | ||||||
|  | 		<-time.After(wait * time.Second) | ||||||
|  | 		wait *= 2 | ||||||
|  | 		if wait > 600 { | ||||||
|  | 			wait = 600 | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if v.session.DataReady == false || v.session.wsConn == nil { | ||||||
|  | 			v.log(LogInformational, "cannot reconnect to channel %s with unready session", v.ChannelID) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		v.log(LogInformational, "trying to reconnect to channel %s", v.ChannelID) | ||||||
|  |  | ||||||
|  | 		_, err := v.session.ChannelVoiceJoin(v.GuildID, v.ChannelID, v.mute, v.deaf) | ||||||
|  | 		if err == nil { | ||||||
|  | 			v.log(LogInformational, "successfully reconnected to channel %s", v.ChannelID) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		v.log(LogInformational, "error reconnecting to channel %s, %s", v.ChannelID, err) | ||||||
|  |  | ||||||
|  | 		// if the reconnect above didn't work lets just send a disconnect | ||||||
|  | 		// packet to reset things. | ||||||
|  | 		// Send a OP4 with a nil channel to disconnect | ||||||
|  | 		data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} | ||||||
|  | 		v.session.wsMutex.Lock() | ||||||
|  | 		err = v.session.wsConn.WriteJSON(data) | ||||||
|  | 		v.session.wsMutex.Unlock() | ||||||
|  | 		if err != nil { | ||||||
|  | 			v.log(LogError, "error sending disconnect packet, %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										845
									
								
								vendor/github.com/matterbridge/discordgo/wsapi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										845
									
								
								vendor/github.com/matterbridge/discordgo/wsapi.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,845 @@ | |||||||
|  | // 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" | ||||||
|  | 	"net/http" | ||||||
|  | 	"runtime" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/gorilla/websocket" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ErrWSAlreadyOpen is thrown when you attempt to open | ||||||
|  | // a websocket that already is open. | ||||||
|  | var ErrWSAlreadyOpen = errors.New("web socket already opened") | ||||||
|  |  | ||||||
|  | // ErrWSNotFound is thrown when you attempt to use a websocket | ||||||
|  | // that doesn't exist | ||||||
|  | var ErrWSNotFound = errors.New("no websocket connection exists") | ||||||
|  |  | ||||||
|  | // ErrWSShardBounds is thrown when you try to use a shard ID that is | ||||||
|  | // less than the total shard count | ||||||
|  | var ErrWSShardBounds = errors.New("ShardID must be less than ShardCount") | ||||||
|  |  | ||||||
|  | type resumePacket struct { | ||||||
|  | 	Op   int `json:"op"` | ||||||
|  | 	Data struct { | ||||||
|  | 		Token     string `json:"token"` | ||||||
|  | 		SessionID string `json:"session_id"` | ||||||
|  | 		Sequence  int64  `json:"seq"` | ||||||
|  | 	} `json:"d"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Open creates a websocket connection to Discord. | ||||||
|  | // See: https://discordapp.com/developers/docs/topics/gateway#connecting | ||||||
|  | func (s *Session) Open() error { | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	// Prevent Open or other major Session functions from | ||||||
|  | 	// being called while Open is still running. | ||||||
|  | 	s.Lock() | ||||||
|  | 	defer s.Unlock() | ||||||
|  |  | ||||||
|  | 	// If the websock is already open, bail out here. | ||||||
|  | 	if s.wsConn != nil { | ||||||
|  | 		return ErrWSAlreadyOpen | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Get the gateway to use for the Websocket connection | ||||||
|  | 	if s.gateway == "" { | ||||||
|  | 		s.gateway, err = s.Gateway() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Add the version and encoding to the URL | ||||||
|  | 		s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Connect to the Gateway | ||||||
|  | 	s.log(LogInformational, "connecting to gateway %s", s.gateway) | ||||||
|  | 	header := http.Header{} | ||||||
|  | 	header.Add("accept-encoding", "zlib") | ||||||
|  | 	s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header) | ||||||
|  | 	if err != nil { | ||||||
|  | 		s.log(LogWarning, "error connecting to gateway %s, %s", s.gateway, err) | ||||||
|  | 		s.gateway = "" // clear cached gateway | ||||||
|  | 		s.wsConn = nil // Just to be safe. | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	defer func() { | ||||||
|  | 		// because of this, all code below must set err to the error | ||||||
|  | 		// when exiting with an error :)  Maybe someone has a better | ||||||
|  | 		// way :) | ||||||
|  | 		if err != nil { | ||||||
|  | 			s.wsConn.Close() | ||||||
|  | 			s.wsConn = nil | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// The first response from Discord should be an Op 10 (Hello) Packet. | ||||||
|  | 	// When processed by onEvent the heartbeat goroutine will be started. | ||||||
|  | 	mt, m, err := s.wsConn.ReadMessage() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	e, err := s.onEvent(mt, m) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if e.Operation != 10 { | ||||||
|  | 		err = fmt.Errorf("expecting Op 10, got Op %d instead", e.Operation) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	s.log(LogInformational, "Op 10 Hello Packet received from Discord") | ||||||
|  | 	s.LastHeartbeatAck = time.Now().UTC() | ||||||
|  | 	var h helloOp | ||||||
|  | 	if err = json.Unmarshal(e.RawData, &h); err != nil { | ||||||
|  | 		err = fmt.Errorf("error unmarshalling helloOp, %s", err) | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Now we send either an Op 2 Identity if this is a brand new | ||||||
|  | 	// connection or Op 6 Resume if we are resuming an existing connection. | ||||||
|  | 	sequence := atomic.LoadInt64(s.sequence) | ||||||
|  | 	if s.sessionID == "" && sequence == 0 { | ||||||
|  |  | ||||||
|  | 		// Send Op 2 Identity Packet | ||||||
|  | 		err = s.identify() | ||||||
|  | 		if err != nil { | ||||||
|  | 			err = fmt.Errorf("error sending identify packet to gateway, %s, %s", s.gateway, err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} else { | ||||||
|  |  | ||||||
|  | 		// Send Op 6 Resume Packet | ||||||
|  | 		p := resumePacket{} | ||||||
|  | 		p.Op = 6 | ||||||
|  | 		p.Data.Token = s.Token | ||||||
|  | 		p.Data.SessionID = s.sessionID | ||||||
|  | 		p.Data.Sequence = sequence | ||||||
|  |  | ||||||
|  | 		s.log(LogInformational, "sending resume packet to gateway") | ||||||
|  | 		s.wsMutex.Lock() | ||||||
|  | 		err = s.wsConn.WriteJSON(p) | ||||||
|  | 		s.wsMutex.Unlock() | ||||||
|  | 		if err != nil { | ||||||
|  | 			err = fmt.Errorf("error sending gateway resume packet, %s, %s", s.gateway, err) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// A basic state is a hard requirement for Voice. | ||||||
|  | 	// We create it here so the below READY/RESUMED packet can populate | ||||||
|  | 	// the state :) | ||||||
|  | 	// XXX: Move to New() func? | ||||||
|  | 	if s.State == nil { | ||||||
|  | 		state := NewState() | ||||||
|  | 		state.TrackChannels = false | ||||||
|  | 		state.TrackEmojis = false | ||||||
|  | 		state.TrackMembers = false | ||||||
|  | 		state.TrackRoles = false | ||||||
|  | 		state.TrackVoice = false | ||||||
|  | 		s.State = state | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Now Discord should send us a READY or RESUMED packet. | ||||||
|  | 	mt, m, err = s.wsConn.ReadMessage() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	e, err = s.onEvent(mt, m) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if e.Type != `READY` && e.Type != `RESUMED` { | ||||||
|  | 		// This is not fatal, but it does not follow their API documentation. | ||||||
|  | 		s.log(LogWarning, "Expected READY/RESUMED, instead got:\n%#v\n", e) | ||||||
|  | 	} | ||||||
|  | 	s.log(LogInformational, "First Packet:\n%#v\n", e) | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "We are now connected to Discord, emitting connect event") | ||||||
|  | 	s.handleEvent(connectEventType, &Connect{}) | ||||||
|  |  | ||||||
|  | 	// A VoiceConnections map is a hard requirement for Voice. | ||||||
|  | 	// XXX: can this be moved to when opening a voice connection? | ||||||
|  | 	if s.VoiceConnections == nil { | ||||||
|  | 		s.log(LogInformational, "creating new VoiceConnections map") | ||||||
|  | 		s.VoiceConnections = make(map[string]*VoiceConnection) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Create listening chan outside of listen, as it needs to happen inside the | ||||||
|  | 	// mutex lock and needs to exist before calling heartbeat and listen | ||||||
|  | 	// go rountines. | ||||||
|  | 	s.listening = make(chan interface{}) | ||||||
|  |  | ||||||
|  | 	// Start sending heartbeats and reading messages from Discord. | ||||||
|  | 	go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval) | ||||||
|  | 	go s.listen(s.wsConn, s.listening) | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "exiting") | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // listen polls the websocket connection for events, it will stop when the | ||||||
|  | // listening channel is closed, or an error occurs. | ||||||
|  | func (s *Session) listen(wsConn *websocket.Conn, listening <-chan interface{}) { | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  |  | ||||||
|  | 		messageType, message, err := wsConn.ReadMessage() | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  |  | ||||||
|  | 			// Detect if we have been closed manually. If a Close() has already | ||||||
|  | 			// happened, the websocket we are listening on will be different to | ||||||
|  | 			// the current session. | ||||||
|  | 			s.RLock() | ||||||
|  | 			sameConnection := s.wsConn == wsConn | ||||||
|  | 			s.RUnlock() | ||||||
|  |  | ||||||
|  | 			if sameConnection { | ||||||
|  |  | ||||||
|  | 				s.log(LogWarning, "error reading from gateway %s websocket, %s", s.gateway, err) | ||||||
|  | 				// There has been an error reading, close the websocket so that | ||||||
|  | 				// OnDisconnect event is emitted. | ||||||
|  | 				err := s.Close() | ||||||
|  | 				if err != nil { | ||||||
|  | 					s.log(LogWarning, "error closing session connection, %s", err) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				s.log(LogInformational, "calling reconnect() now") | ||||||
|  | 				s.reconnect() | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  |  | ||||||
|  | 		case <-listening: | ||||||
|  | 			return | ||||||
|  |  | ||||||
|  | 		default: | ||||||
|  | 			s.onEvent(messageType, message) | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type heartbeatOp struct { | ||||||
|  | 	Op   int   `json:"op"` | ||||||
|  | 	Data int64 `json:"d"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type helloOp struct { | ||||||
|  | 	HeartbeatInterval time.Duration `json:"heartbeat_interval"` | ||||||
|  | 	Trace             []string      `json:"_trace"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FailedHeartbeatAcks is the Number of heartbeat intervals to wait until forcing a connection restart. | ||||||
|  | const FailedHeartbeatAcks time.Duration = 5 * time.Millisecond | ||||||
|  |  | ||||||
|  | // heartbeat sends regular heartbeats to Discord so it knows the client | ||||||
|  | // is still connected.  If you do not send these heartbeats Discord will | ||||||
|  | // disconnect the websocket connection after a few seconds. | ||||||
|  | func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}, heartbeatIntervalMsec time.Duration) { | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	if listening == nil || wsConn == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	ticker := time.NewTicker(heartbeatIntervalMsec * time.Millisecond) | ||||||
|  | 	defer ticker.Stop() | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		s.RLock() | ||||||
|  | 		last := s.LastHeartbeatAck | ||||||
|  | 		s.RUnlock() | ||||||
|  | 		sequence := atomic.LoadInt64(s.sequence) | ||||||
|  | 		s.log(LogInformational, "sending gateway websocket heartbeat seq %d", sequence) | ||||||
|  | 		s.wsMutex.Lock() | ||||||
|  | 		err = wsConn.WriteJSON(heartbeatOp{1, sequence}) | ||||||
|  | 		s.wsMutex.Unlock() | ||||||
|  | 		if err != nil || time.Now().UTC().Sub(last) > (heartbeatIntervalMsec*FailedHeartbeatAcks) { | ||||||
|  | 			if err != nil { | ||||||
|  | 				s.log(LogError, "error sending heartbeat to gateway %s, %s", s.gateway, err) | ||||||
|  | 			} else { | ||||||
|  | 				s.log(LogError, "haven't gotten a heartbeat ACK in %v, triggering a reconnection", time.Now().UTC().Sub(last)) | ||||||
|  | 			} | ||||||
|  | 			s.Close() | ||||||
|  | 			s.reconnect() | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		s.Lock() | ||||||
|  | 		s.DataReady = true | ||||||
|  | 		s.Unlock() | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case <-ticker.C: | ||||||
|  | 			// continue loop and send heartbeat | ||||||
|  | 		case <-listening: | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UpdateStatusData ia provided to UpdateStatusComplex() | ||||||
|  | type UpdateStatusData struct { | ||||||
|  | 	IdleSince *int   `json:"since"` | ||||||
|  | 	Game      *Game  `json:"game"` | ||||||
|  | 	AFK       bool   `json:"afk"` | ||||||
|  | 	Status    string `json:"status"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type updateStatusOp struct { | ||||||
|  | 	Op   int              `json:"op"` | ||||||
|  | 	Data UpdateStatusData `json:"d"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UpdateStreamingStatus is used to update the user's streaming status. | ||||||
|  | // If idle>0 then set status to idle. | ||||||
|  | // If game!="" then set game. | ||||||
|  | // If game!="" and url!="" then set the status type to streaming with the URL set. | ||||||
|  | // if otherwise, set status to active, and no game. | ||||||
|  | func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err error) { | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	usd := UpdateStatusData{ | ||||||
|  | 		Status: "online", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if idle > 0 { | ||||||
|  | 		usd.IdleSince = &idle | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if game != "" { | ||||||
|  | 		gameType := GameTypeGame | ||||||
|  | 		if url != "" { | ||||||
|  | 			gameType = GameTypeStreaming | ||||||
|  | 		} | ||||||
|  | 		usd.Game = &Game{ | ||||||
|  | 			Name: game, | ||||||
|  | 			Type: gameType, | ||||||
|  | 			URL:  url, | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return s.UpdateStatusComplex(usd) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UpdateStatusComplex allows for sending the raw status update data untouched by discordgo. | ||||||
|  | func (s *Session) UpdateStatusComplex(usd UpdateStatusData) (err error) { | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	defer s.RUnlock() | ||||||
|  | 	if s.wsConn == nil { | ||||||
|  | 		return ErrWSNotFound | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.wsMutex.Lock() | ||||||
|  | 	err = s.wsConn.WriteJSON(updateStatusOp{3, usd}) | ||||||
|  | 	s.wsMutex.Unlock() | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UpdateStatus is used to update the user's status. | ||||||
|  | // If idle>0 then set status to idle. | ||||||
|  | // If game!="" then set game. | ||||||
|  | // if otherwise, set status to active, and no game. | ||||||
|  | func (s *Session) UpdateStatus(idle int, game string) (err error) { | ||||||
|  | 	return s.UpdateStreamingStatus(idle, game, "") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type requestGuildMembersData struct { | ||||||
|  | 	GuildID string `json:"guild_id"` | ||||||
|  | 	Query   string `json:"query"` | ||||||
|  | 	Limit   int    `json:"limit"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type requestGuildMembersOp struct { | ||||||
|  | 	Op   int                     `json:"op"` | ||||||
|  | 	Data requestGuildMembersData `json:"d"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RequestGuildMembers requests guild members from the gateway | ||||||
|  | // The gateway responds with GuildMembersChunk events | ||||||
|  | // guildID  : The ID of the guild to request members of | ||||||
|  | // query    : String that username starts with, leave empty to return all members | ||||||
|  | // limit    : Max number of items to return, or 0 to request all members matched | ||||||
|  | func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err error) { | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	defer s.RUnlock() | ||||||
|  | 	if s.wsConn == nil { | ||||||
|  | 		return ErrWSNotFound | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data := requestGuildMembersData{ | ||||||
|  | 		GuildID: guildID, | ||||||
|  | 		Query:   query, | ||||||
|  | 		Limit:   limit, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.wsMutex.Lock() | ||||||
|  | 	err = s.wsConn.WriteJSON(requestGuildMembersOp{8, data}) | ||||||
|  | 	s.wsMutex.Unlock() | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // onEvent is the "event handler" for all messages received on the | ||||||
|  | // Discord Gateway API websocket connection. | ||||||
|  | // | ||||||
|  | // If you use the AddHandler() function to register a handler for a | ||||||
|  | // specific event this function will pass the event along to that handler. | ||||||
|  | // | ||||||
|  | // If you use the AddHandler() function to register a handler for the | ||||||
|  | // "OnEvent" event then all events will be passed to that handler. | ||||||
|  | func (s *Session) onEvent(messageType int, message []byte) (*Event, error) { | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	var reader io.Reader | ||||||
|  | 	reader = bytes.NewBuffer(message) | ||||||
|  |  | ||||||
|  | 	// If this is a compressed message, uncompress it. | ||||||
|  | 	if messageType == websocket.BinaryMessage { | ||||||
|  |  | ||||||
|  | 		z, err2 := zlib.NewReader(reader) | ||||||
|  | 		if err2 != nil { | ||||||
|  | 			s.log(LogError, "error uncompressing websocket message, %s", err) | ||||||
|  | 			return nil, err2 | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		defer func() { | ||||||
|  | 			err3 := z.Close() | ||||||
|  | 			if err3 != nil { | ||||||
|  | 				s.log(LogWarning, "error closing zlib, %s", err) | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  |  | ||||||
|  | 		reader = z | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Decode the event into an Event struct. | ||||||
|  | 	var e *Event | ||||||
|  | 	decoder := json.NewDecoder(reader) | ||||||
|  | 	if err = decoder.Decode(&e); err != nil { | ||||||
|  | 		s.log(LogError, "error decoding websocket message, %s", err) | ||||||
|  | 		return e, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s\n\n", e.Operation, e.Sequence, e.Type, string(e.RawData)) | ||||||
|  |  | ||||||
|  | 	// Ping request. | ||||||
|  | 	// Must respond with a heartbeat packet within 5 seconds | ||||||
|  | 	if e.Operation == 1 { | ||||||
|  | 		s.log(LogInformational, "sending heartbeat in response to Op1") | ||||||
|  | 		s.wsMutex.Lock() | ||||||
|  | 		err = s.wsConn.WriteJSON(heartbeatOp{1, atomic.LoadInt64(s.sequence)}) | ||||||
|  | 		s.wsMutex.Unlock() | ||||||
|  | 		if err != nil { | ||||||
|  | 			s.log(LogError, "error sending heartbeat in response to Op1") | ||||||
|  | 			return e, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return e, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Reconnect | ||||||
|  | 	// Must immediately disconnect from gateway and reconnect to new gateway. | ||||||
|  | 	if e.Operation == 7 { | ||||||
|  | 		s.log(LogInformational, "Closing and reconnecting in response to Op7") | ||||||
|  | 		s.Close() | ||||||
|  | 		s.reconnect() | ||||||
|  | 		return e, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Invalid Session | ||||||
|  | 	// Must respond with a Identify packet. | ||||||
|  | 	if e.Operation == 9 { | ||||||
|  |  | ||||||
|  | 		s.log(LogInformational, "sending identify packet to gateway in response to Op9") | ||||||
|  |  | ||||||
|  | 		err = s.identify() | ||||||
|  | 		if err != nil { | ||||||
|  | 			s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err) | ||||||
|  | 			return e, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return e, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if e.Operation == 10 { | ||||||
|  | 		// Op10 is handled by Open() | ||||||
|  | 		return e, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if e.Operation == 11 { | ||||||
|  | 		s.Lock() | ||||||
|  | 		s.LastHeartbeatAck = time.Now().UTC() | ||||||
|  | 		s.Unlock() | ||||||
|  | 		s.log(LogInformational, "got heartbeat ACK") | ||||||
|  | 		return e, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Do not try to Dispatch a non-Dispatch Message | ||||||
|  | 	if e.Operation != 0 { | ||||||
|  | 		// But we probably should be doing something with them. | ||||||
|  | 		// TEMP | ||||||
|  | 		s.log(LogWarning, "unknown Op: %d, Seq: %d, Type: %s, Data: %s, message: %s", e.Operation, e.Sequence, e.Type, string(e.RawData), string(message)) | ||||||
|  | 		return e, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Store the message sequence | ||||||
|  | 	atomic.StoreInt64(s.sequence, e.Sequence) | ||||||
|  |  | ||||||
|  | 	// Map event to registered event handlers and pass it along to any registered handlers. | ||||||
|  | 	if eh, ok := registeredInterfaceProviders[e.Type]; ok { | ||||||
|  | 		e.Struct = eh.New() | ||||||
|  |  | ||||||
|  | 		// Attempt to unmarshal our event. | ||||||
|  | 		if err = json.Unmarshal(e.RawData, e.Struct); err != nil { | ||||||
|  | 			s.log(LogError, "error unmarshalling %s event, %s", e.Type, err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Send event to any registered event handlers for it's type. | ||||||
|  | 		// Because the above doesn't cancel this, in case of an error | ||||||
|  | 		// the struct could be partially populated or at default values. | ||||||
|  | 		// However, most errors are due to a single field and I feel | ||||||
|  | 		// it's better to pass along what we received than nothing at all. | ||||||
|  | 		// TODO: Think about that decision :) | ||||||
|  | 		// Either way, READY events must fire, even with errors. | ||||||
|  | 		s.handleEvent(e.Type, e.Struct) | ||||||
|  | 	} else { | ||||||
|  | 		s.log(LogWarning, "unknown event: Op: %d, Seq: %d, Type: %s, Data: %s", e.Operation, e.Sequence, e.Type, string(e.RawData)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// For legacy reasons, we send the raw event also, this could be useful for handling unknown events. | ||||||
|  | 	s.handleEvent(eventEventType, e) | ||||||
|  |  | ||||||
|  | 	return e, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  | // Code related to voice connections that initiate over the data websocket | ||||||
|  | // ------------------------------------------------------------------------------------------------ | ||||||
|  |  | ||||||
|  | type voiceChannelJoinData struct { | ||||||
|  | 	GuildID   *string `json:"guild_id"` | ||||||
|  | 	ChannelID *string `json:"channel_id"` | ||||||
|  | 	SelfMute  bool    `json:"self_mute"` | ||||||
|  | 	SelfDeaf  bool    `json:"self_deaf"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type voiceChannelJoinOp struct { | ||||||
|  | 	Op   int                  `json:"op"` | ||||||
|  | 	Data voiceChannelJoinData `json:"d"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ChannelVoiceJoin joins the session user to a voice channel. | ||||||
|  | // | ||||||
|  | //    gID     : Guild ID of the channel to join. | ||||||
|  | //    cID     : Channel ID of the channel to join. | ||||||
|  | //    mute    : If true, you will be set to muted upon joining. | ||||||
|  | //    deaf    : If true, you will be set to deafened upon joining. | ||||||
|  | func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *VoiceConnection, err error) { | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	voice, _ = s.VoiceConnections[gID] | ||||||
|  | 	s.RUnlock() | ||||||
|  |  | ||||||
|  | 	if voice == nil { | ||||||
|  | 		voice = &VoiceConnection{} | ||||||
|  | 		s.Lock() | ||||||
|  | 		s.VoiceConnections[gID] = voice | ||||||
|  | 		s.Unlock() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	voice.Lock() | ||||||
|  | 	voice.GuildID = gID | ||||||
|  | 	voice.ChannelID = cID | ||||||
|  | 	voice.deaf = deaf | ||||||
|  | 	voice.mute = mute | ||||||
|  | 	voice.session = s | ||||||
|  | 	voice.Unlock() | ||||||
|  |  | ||||||
|  | 	// Send the request to Discord that we want to join the voice channel | ||||||
|  | 	data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, &cID, mute, deaf}} | ||||||
|  | 	s.wsMutex.Lock() | ||||||
|  | 	err = s.wsConn.WriteJSON(data) | ||||||
|  | 	s.wsMutex.Unlock() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// doesn't exactly work perfect yet.. TODO | ||||||
|  | 	err = voice.waitUntilConnected() | ||||||
|  | 	if err != nil { | ||||||
|  | 		s.log(LogWarning, "error waiting for voice to connect, %s", err) | ||||||
|  | 		voice.Close() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // onVoiceStateUpdate handles Voice State Update events on the data websocket. | ||||||
|  | func (s *Session) onVoiceStateUpdate(st *VoiceStateUpdate) { | ||||||
|  |  | ||||||
|  | 	// If we don't have a connection for the channel, don't bother | ||||||
|  | 	if st.ChannelID == "" { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Check if we have a voice connection to update | ||||||
|  | 	s.RLock() | ||||||
|  | 	voice, exists := s.VoiceConnections[st.GuildID] | ||||||
|  | 	s.RUnlock() | ||||||
|  | 	if !exists { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// We only care about events that are about us. | ||||||
|  | 	if s.State.User.ID != st.UserID { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Store the SessionID for later use. | ||||||
|  | 	voice.Lock() | ||||||
|  | 	voice.UserID = st.UserID | ||||||
|  | 	voice.sessionID = st.SessionID | ||||||
|  | 	voice.ChannelID = st.ChannelID | ||||||
|  | 	voice.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // onVoiceServerUpdate handles the Voice Server Update data websocket event. | ||||||
|  | // | ||||||
|  | // This is also fired if the Guild's voice region changes while connected | ||||||
|  | // to a voice channel.  In that case, need to re-establish connection to | ||||||
|  | // the new region endpoint. | ||||||
|  | func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) { | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	s.RLock() | ||||||
|  | 	voice, exists := s.VoiceConnections[st.GuildID] | ||||||
|  | 	s.RUnlock() | ||||||
|  |  | ||||||
|  | 	// If no VoiceConnection exists, just skip this | ||||||
|  | 	if !exists { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// If currently connected to voice ws/udp, then disconnect. | ||||||
|  | 	// Has no effect if not connected. | ||||||
|  | 	voice.Close() | ||||||
|  |  | ||||||
|  | 	// Store values for later use | ||||||
|  | 	voice.Lock() | ||||||
|  | 	voice.token = st.Token | ||||||
|  | 	voice.endpoint = st.Endpoint | ||||||
|  | 	voice.GuildID = st.GuildID | ||||||
|  | 	voice.Unlock() | ||||||
|  |  | ||||||
|  | 	// Open a connection to the voice server | ||||||
|  | 	err := voice.open() | ||||||
|  | 	if err != nil { | ||||||
|  | 		s.log(LogError, "onVoiceServerUpdate voice.open, %s", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type identifyProperties struct { | ||||||
|  | 	OS              string `json:"$os"` | ||||||
|  | 	Browser         string `json:"$browser"` | ||||||
|  | 	Device          string `json:"$device"` | ||||||
|  | 	Referer         string `json:"$referer"` | ||||||
|  | 	ReferringDomain string `json:"$referring_domain"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type identifyData struct { | ||||||
|  | 	Token          string             `json:"token"` | ||||||
|  | 	Properties     identifyProperties `json:"properties"` | ||||||
|  | 	LargeThreshold int                `json:"large_threshold"` | ||||||
|  | 	Compress       bool               `json:"compress"` | ||||||
|  | 	Shard          *[2]int            `json:"shard,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type identifyOp struct { | ||||||
|  | 	Op   int          `json:"op"` | ||||||
|  | 	Data identifyData `json:"d"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // identify sends the identify packet to the gateway | ||||||
|  | func (s *Session) identify() error { | ||||||
|  |  | ||||||
|  | 	properties := identifyProperties{runtime.GOOS, | ||||||
|  | 		"Discordgo v" + VERSION, | ||||||
|  | 		"", | ||||||
|  | 		"", | ||||||
|  | 		"", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	data := identifyData{s.Token, | ||||||
|  | 		properties, | ||||||
|  | 		250, | ||||||
|  | 		s.Compress, | ||||||
|  | 		nil, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if s.ShardCount > 1 { | ||||||
|  |  | ||||||
|  | 		if s.ShardID >= s.ShardCount { | ||||||
|  | 			return ErrWSShardBounds | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		data.Shard = &[2]int{s.ShardID, s.ShardCount} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	op := identifyOp{2, data} | ||||||
|  |  | ||||||
|  | 	s.wsMutex.Lock() | ||||||
|  | 	err := s.wsConn.WriteJSON(op) | ||||||
|  | 	s.wsMutex.Unlock() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *Session) reconnect() { | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	if s.ShouldReconnectOnError { | ||||||
|  |  | ||||||
|  | 		wait := time.Duration(1) | ||||||
|  |  | ||||||
|  | 		for { | ||||||
|  | 			s.log(LogInformational, "trying to reconnect to gateway") | ||||||
|  |  | ||||||
|  | 			err = s.Open() | ||||||
|  | 			if err == nil { | ||||||
|  | 				s.log(LogInformational, "successfully reconnected to gateway") | ||||||
|  |  | ||||||
|  | 				// I'm not sure if this is actually needed. | ||||||
|  | 				// if the gw reconnect works properly, voice should stay alive | ||||||
|  | 				// However, there seems to be cases where something "weird" | ||||||
|  | 				// happens.  So we're doing this for now just to improve | ||||||
|  | 				// stability in those edge cases. | ||||||
|  | 				s.RLock() | ||||||
|  | 				defer s.RUnlock() | ||||||
|  | 				for _, v := range s.VoiceConnections { | ||||||
|  |  | ||||||
|  | 					s.log(LogInformational, "reconnecting voice connection to guild %s", v.GuildID) | ||||||
|  | 					go v.reconnect() | ||||||
|  |  | ||||||
|  | 					// This is here just to prevent violently spamming the | ||||||
|  | 					// voice reconnects | ||||||
|  | 					time.Sleep(1 * time.Second) | ||||||
|  |  | ||||||
|  | 				} | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Certain race conditions can call reconnect() twice. If this happens, we | ||||||
|  | 			// just break out of the reconnect loop | ||||||
|  | 			if err == ErrWSAlreadyOpen { | ||||||
|  | 				s.log(LogInformational, "Websocket already exists, no need to reconnect") | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			s.log(LogError, "error reconnecting to gateway, %s", err) | ||||||
|  |  | ||||||
|  | 			<-time.After(wait * time.Second) | ||||||
|  | 			wait *= 2 | ||||||
|  | 			if wait > 600 { | ||||||
|  | 				wait = 600 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close closes a websocket and stops all listening/heartbeat goroutines. | ||||||
|  | // TODO: Add support for Voice WS/UDP connections | ||||||
|  | func (s *Session) Close() (err error) { | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "called") | ||||||
|  | 	s.Lock() | ||||||
|  |  | ||||||
|  | 	s.DataReady = false | ||||||
|  |  | ||||||
|  | 	if s.listening != nil { | ||||||
|  | 		s.log(LogInformational, "closing listening channel") | ||||||
|  | 		close(s.listening) | ||||||
|  | 		s.listening = nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// TODO: Close all active Voice Connections too | ||||||
|  | 	// this should force stop any reconnecting voice channels too | ||||||
|  |  | ||||||
|  | 	if s.wsConn != nil { | ||||||
|  |  | ||||||
|  | 		s.log(LogInformational, "sending close frame") | ||||||
|  | 		// To cleanly close a connection, a client should send a close | ||||||
|  | 		// frame and wait for the server to close the connection. | ||||||
|  | 		s.wsMutex.Lock() | ||||||
|  | 		err := s.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) | ||||||
|  | 		s.wsMutex.Unlock() | ||||||
|  | 		if err != nil { | ||||||
|  | 			s.log(LogInformational, "error closing websocket, %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// TODO: Wait for Discord to actually close the connection. | ||||||
|  | 		time.Sleep(1 * time.Second) | ||||||
|  |  | ||||||
|  | 		s.log(LogInformational, "closing gateway websocket") | ||||||
|  | 		err = s.wsConn.Close() | ||||||
|  | 		if err != nil { | ||||||
|  | 			s.log(LogInformational, "error closing websocket, %s", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		s.wsConn = nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Unlock() | ||||||
|  |  | ||||||
|  | 	s.log(LogInformational, "emit disconnect event") | ||||||
|  | 	s.handleEvent(disconnectEventType, &Disconnect{}) | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/matterbridge/logrus-prefixed-formatter/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/matterbridge/logrus-prefixed-formatter/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | The MIT License (MIT) | ||||||
|  |  | ||||||
|  | Copyright (c) 2017 Denis Parchenko | ||||||
|  |  | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  |  | ||||||
|  | The above copyright notice and this permission notice shall be included in all | ||||||
|  | copies or substantial portions of the Software. | ||||||
|  |  | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | SOFTWARE. | ||||||
							
								
								
									
										59
									
								
								vendor/github.com/matterbridge/logrus-prefixed-formatter/examples/basic.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								vendor/github.com/matterbridge/logrus-prefixed-formatter/examples/basic.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	prefixed "github.com/x-cray/logrus-prefixed-formatter" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var log = logrus.New() | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	formatter := new(prefixed.TextFormatter) | ||||||
|  | 	log.Formatter = formatter | ||||||
|  | 	log.Level = logrus.DebugLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	defer func() { | ||||||
|  | 		err := recover() | ||||||
|  | 		if err != nil { | ||||||
|  | 			// Fatal message | ||||||
|  | 			log.WithFields(logrus.Fields{ | ||||||
|  | 				"omg":    true, | ||||||
|  | 				"number": 100, | ||||||
|  | 			}).Fatal("[main] The ice breaks!") | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	// You could either provide a map key called `prefix` to add prefix | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"prefix": "main", | ||||||
|  | 		"animal": "walrus", | ||||||
|  | 		"number": 8, | ||||||
|  | 	}).Debug("Started observing beach") | ||||||
|  |  | ||||||
|  | 	// Or you can simply add prefix in square brackets within message itself | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"animal": "walrus", | ||||||
|  | 		"size":   10, | ||||||
|  | 	}).Debug("[main] A group of walrus emerges from the ocean") | ||||||
|  |  | ||||||
|  | 	// Warning message | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"omg":    true, | ||||||
|  | 		"number": 122, | ||||||
|  | 	}).Warn("[main] The group's number increased tremendously!") | ||||||
|  |  | ||||||
|  | 	// Information message | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"prefix":      "sensor", | ||||||
|  | 		"temperature": -4, | ||||||
|  | 	}).Info("Temperature changes") | ||||||
|  |  | ||||||
|  | 	// Panic message | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"prefix": "sensor", | ||||||
|  | 		"animal": "orca", | ||||||
|  | 		"size":   9009, | ||||||
|  | 	}).Panic("It's over 9000!") | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								vendor/github.com/matterbridge/logrus-prefixed-formatter/examples/themes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								vendor/github.com/matterbridge/logrus-prefixed-formatter/examples/themes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | package main | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	prefixed "github.com/x-cray/logrus-prefixed-formatter" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var log = logrus.New() | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	formatter := new(prefixed.TextFormatter) | ||||||
|  | 	formatter.FullTimestamp = true | ||||||
|  |  | ||||||
|  | 	// Set specific colors for prefix and timestamp | ||||||
|  | 	formatter.SetColorScheme(&prefixed.ColorScheme{ | ||||||
|  | 		PrefixStyle:    "blue+b", | ||||||
|  | 		TimestampStyle: "white+h", | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	log.Formatter = formatter | ||||||
|  | 	log.Level = logrus.DebugLevel | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func main() { | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"prefix": "main", | ||||||
|  | 		"animal": "walrus", | ||||||
|  | 		"number": 8, | ||||||
|  | 	}).Debug("Started observing beach") | ||||||
|  |  | ||||||
|  | 	// Or you can simply add prefix in square brackets within message itself | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"animal": "walrus", | ||||||
|  | 		"size":   10, | ||||||
|  | 	}).Debug("[main] A group of walrus emerges from the ocean") | ||||||
|  |  | ||||||
|  | 	// Warning message | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"omg":    true, | ||||||
|  | 		"number": 122, | ||||||
|  | 	}).Warn("[main] The group's number increased tremendously!") | ||||||
|  |  | ||||||
|  | 	// Information message | ||||||
|  | 	log.WithFields(logrus.Fields{ | ||||||
|  | 		"prefix":      "sensor", | ||||||
|  | 		"temperature": -4, | ||||||
|  | 	}).Info("Temperature changes") | ||||||
|  | } | ||||||
							
								
								
									
										382
									
								
								vendor/github.com/matterbridge/logrus-prefixed-formatter/formatter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								vendor/github.com/matterbridge/logrus-prefixed-formatter/formatter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,382 @@ | |||||||
|  | package prefixed | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"regexp" | ||||||
|  | 	"sort" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"github.com/mgutz/ansi" | ||||||
|  | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"golang.org/x/crypto/ssh/terminal" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const defaultTimestampFormat = time.RFC3339 | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	baseTimestamp      time.Time    = time.Now() | ||||||
|  | 	defaultColorScheme *ColorScheme = &ColorScheme{ | ||||||
|  | 		InfoLevelStyle:  "green", | ||||||
|  | 		WarnLevelStyle:  "yellow", | ||||||
|  | 		ErrorLevelStyle: "red", | ||||||
|  | 		FatalLevelStyle: "red", | ||||||
|  | 		PanicLevelStyle: "red", | ||||||
|  | 		DebugLevelStyle: "blue", | ||||||
|  | 		PrefixStyle:     "cyan", | ||||||
|  | 		TimestampStyle:  "black+h", | ||||||
|  | 	} | ||||||
|  | 	noColorsColorScheme *compiledColorScheme = &compiledColorScheme{ | ||||||
|  | 		InfoLevelColor:  ansi.ColorFunc(""), | ||||||
|  | 		WarnLevelColor:  ansi.ColorFunc(""), | ||||||
|  | 		ErrorLevelColor: ansi.ColorFunc(""), | ||||||
|  | 		FatalLevelColor: ansi.ColorFunc(""), | ||||||
|  | 		PanicLevelColor: ansi.ColorFunc(""), | ||||||
|  | 		DebugLevelColor: ansi.ColorFunc(""), | ||||||
|  | 		PrefixColor:     ansi.ColorFunc(""), | ||||||
|  | 		TimestampColor:  ansi.ColorFunc(""), | ||||||
|  | 	} | ||||||
|  | 	defaultCompiledColorScheme *compiledColorScheme = compileColorScheme(defaultColorScheme) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func miniTS() int { | ||||||
|  | 	return int(time.Since(baseTimestamp) / time.Second) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type ColorScheme struct { | ||||||
|  | 	InfoLevelStyle  string | ||||||
|  | 	WarnLevelStyle  string | ||||||
|  | 	ErrorLevelStyle string | ||||||
|  | 	FatalLevelStyle string | ||||||
|  | 	PanicLevelStyle string | ||||||
|  | 	DebugLevelStyle string | ||||||
|  | 	PrefixStyle     string | ||||||
|  | 	TimestampStyle  string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type compiledColorScheme struct { | ||||||
|  | 	InfoLevelColor  func(string) string | ||||||
|  | 	WarnLevelColor  func(string) string | ||||||
|  | 	ErrorLevelColor func(string) string | ||||||
|  | 	FatalLevelColor func(string) string | ||||||
|  | 	PanicLevelColor func(string) string | ||||||
|  | 	DebugLevelColor func(string) string | ||||||
|  | 	PrefixColor     func(string) string | ||||||
|  | 	TimestampColor  func(string) string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TextFormatter struct { | ||||||
|  | 	// Set to true to bypass checking for a TTY before outputting colors. | ||||||
|  | 	ForceColors bool | ||||||
|  |  | ||||||
|  | 	// Force disabling colors. For a TTY colors are enabled by default. | ||||||
|  | 	DisableColors bool | ||||||
|  |  | ||||||
|  | 	// Force formatted layout, even for non-TTY output. | ||||||
|  | 	ForceFormatting bool | ||||||
|  |  | ||||||
|  | 	// Disable timestamp logging. useful when output is redirected to logging | ||||||
|  | 	// system that already adds timestamps. | ||||||
|  | 	DisableTimestamp bool | ||||||
|  |  | ||||||
|  | 	// Disable the conversion of the log levels to uppercase | ||||||
|  | 	DisableUppercase bool | ||||||
|  |  | ||||||
|  | 	// Enable logging the full timestamp when a TTY is attached instead of just | ||||||
|  | 	// the time passed since beginning of execution. | ||||||
|  | 	FullTimestamp bool | ||||||
|  |  | ||||||
|  | 	// Timestamp format to use for display when a full timestamp is printed. | ||||||
|  | 	TimestampFormat string | ||||||
|  |  | ||||||
|  | 	// The fields are sorted by default for a consistent output. For applications | ||||||
|  | 	// that log extremely frequently and don't use the JSON formatter this may not | ||||||
|  | 	// be desired. | ||||||
|  | 	DisableSorting bool | ||||||
|  |  | ||||||
|  | 	// Wrap empty fields in quotes if true. | ||||||
|  | 	QuoteEmptyFields bool | ||||||
|  |  | ||||||
|  | 	// Can be set to the override the default quoting character " | ||||||
|  | 	// with something else. For example: ', or `. | ||||||
|  | 	QuoteCharacter string | ||||||
|  |  | ||||||
|  | 	// Pad msg field with spaces on the right for display. | ||||||
|  | 	// The value for this parameter will be the size of padding. | ||||||
|  | 	// Its default value is zero, which means no padding will be applied for msg. | ||||||
|  | 	SpacePadding int | ||||||
|  |  | ||||||
|  | 	// Pad prefix field with spaces on the right for display. | ||||||
|  | 	// The value for this parameter will be the size of padding. | ||||||
|  | 	// Its default value is zero, which means no padding will be applied for prefix. | ||||||
|  | 	PrefixPadding int | ||||||
|  |  | ||||||
|  | 	// Color scheme to use. | ||||||
|  | 	colorScheme *compiledColorScheme | ||||||
|  |  | ||||||
|  | 	// Whether the logger's out is to a terminal. | ||||||
|  | 	isTerminal bool | ||||||
|  |  | ||||||
|  | 	sync.Once | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func getCompiledColor(main string, fallback string) func(string) string { | ||||||
|  | 	var style string | ||||||
|  | 	if main != "" { | ||||||
|  | 		style = main | ||||||
|  | 	} else { | ||||||
|  | 		style = fallback | ||||||
|  | 	} | ||||||
|  | 	return ansi.ColorFunc(style) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func compileColorScheme(s *ColorScheme) *compiledColorScheme { | ||||||
|  | 	return &compiledColorScheme{ | ||||||
|  | 		InfoLevelColor:  getCompiledColor(s.InfoLevelStyle, defaultColorScheme.InfoLevelStyle), | ||||||
|  | 		WarnLevelColor:  getCompiledColor(s.WarnLevelStyle, defaultColorScheme.WarnLevelStyle), | ||||||
|  | 		ErrorLevelColor: getCompiledColor(s.ErrorLevelStyle, defaultColorScheme.ErrorLevelStyle), | ||||||
|  | 		FatalLevelColor: getCompiledColor(s.FatalLevelStyle, defaultColorScheme.FatalLevelStyle), | ||||||
|  | 		PanicLevelColor: getCompiledColor(s.PanicLevelStyle, defaultColorScheme.PanicLevelStyle), | ||||||
|  | 		DebugLevelColor: getCompiledColor(s.DebugLevelStyle, defaultColorScheme.DebugLevelStyle), | ||||||
|  | 		PrefixColor:     getCompiledColor(s.PrefixStyle, defaultColorScheme.PrefixStyle), | ||||||
|  | 		TimestampColor:  getCompiledColor(s.TimestampStyle, defaultColorScheme.TimestampStyle), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *TextFormatter) init(entry *logrus.Entry) { | ||||||
|  | 	if len(f.QuoteCharacter) == 0 { | ||||||
|  | 		f.QuoteCharacter = "\"" | ||||||
|  | 	} | ||||||
|  | 	if entry.Logger != nil { | ||||||
|  | 		f.isTerminal = f.checkIfTerminal(entry.Logger.Out) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *TextFormatter) checkIfTerminal(w io.Writer) bool { | ||||||
|  | 	switch v := w.(type) { | ||||||
|  | 	case *os.File: | ||||||
|  | 		return terminal.IsTerminal(int(v.Fd())) | ||||||
|  | 	default: | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *TextFormatter) SetColorScheme(colorScheme *ColorScheme) { | ||||||
|  | 	f.colorScheme = compileColorScheme(colorScheme) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) { | ||||||
|  | 	var b *bytes.Buffer | ||||||
|  | 	var keys []string = make([]string, 0, len(entry.Data)) | ||||||
|  | 	for k := range entry.Data { | ||||||
|  | 		keys = append(keys, k) | ||||||
|  | 	} | ||||||
|  | 	lastKeyIdx := len(keys) - 1 | ||||||
|  |  | ||||||
|  | 	if !f.DisableSorting { | ||||||
|  | 		sort.Strings(keys) | ||||||
|  | 	} | ||||||
|  | 	if entry.Buffer != nil { | ||||||
|  | 		b = entry.Buffer | ||||||
|  | 	} else { | ||||||
|  | 		b = &bytes.Buffer{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	prefixFieldClashes(entry.Data) | ||||||
|  |  | ||||||
|  | 	f.Do(func() { f.init(entry) }) | ||||||
|  |  | ||||||
|  | 	isFormatted := f.ForceFormatting || f.isTerminal | ||||||
|  |  | ||||||
|  | 	timestampFormat := f.TimestampFormat | ||||||
|  | 	if timestampFormat == "" { | ||||||
|  | 		timestampFormat = defaultTimestampFormat | ||||||
|  | 	} | ||||||
|  | 	if isFormatted { | ||||||
|  | 		isColored := (f.ForceColors || f.isTerminal) && !f.DisableColors | ||||||
|  | 		var colorScheme *compiledColorScheme | ||||||
|  | 		if isColored { | ||||||
|  | 			if f.colorScheme == nil { | ||||||
|  | 				colorScheme = defaultCompiledColorScheme | ||||||
|  | 			} else { | ||||||
|  | 				colorScheme = f.colorScheme | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			colorScheme = noColorsColorScheme | ||||||
|  | 		} | ||||||
|  | 		f.printColored(b, entry, keys, timestampFormat, colorScheme) | ||||||
|  | 	} else { | ||||||
|  | 		if !f.DisableTimestamp { | ||||||
|  | 			f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat), true) | ||||||
|  | 		} | ||||||
|  | 		f.appendKeyValue(b, "level", entry.Level.String(), true) | ||||||
|  | 		if entry.Message != "" { | ||||||
|  | 			f.appendKeyValue(b, "msg", entry.Message, lastKeyIdx >= 0) | ||||||
|  | 		} | ||||||
|  | 		for i, key := range keys { | ||||||
|  | 			f.appendKeyValue(b, key, entry.Data[key], lastKeyIdx != i) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b.WriteByte('\n') | ||||||
|  | 	return b.Bytes(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string, colorScheme *compiledColorScheme) { | ||||||
|  | 	var levelColor func(string) string | ||||||
|  | 	var levelText string | ||||||
|  | 	switch entry.Level { | ||||||
|  | 	case logrus.InfoLevel: | ||||||
|  | 		levelColor = colorScheme.InfoLevelColor | ||||||
|  | 	case logrus.WarnLevel: | ||||||
|  | 		levelColor = colorScheme.WarnLevelColor | ||||||
|  | 	case logrus.ErrorLevel: | ||||||
|  | 		levelColor = colorScheme.ErrorLevelColor | ||||||
|  | 	case logrus.FatalLevel: | ||||||
|  | 		levelColor = colorScheme.FatalLevelColor | ||||||
|  | 	case logrus.PanicLevel: | ||||||
|  | 		levelColor = colorScheme.PanicLevelColor | ||||||
|  | 	default: | ||||||
|  | 		levelColor = colorScheme.DebugLevelColor | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if entry.Level != logrus.WarnLevel { | ||||||
|  | 		levelText = entry.Level.String() | ||||||
|  | 	} else { | ||||||
|  | 		levelText = "warn" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !f.DisableUppercase { | ||||||
|  | 		levelText = strings.ToUpper(levelText) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	level := levelColor(fmt.Sprintf("%5s", levelText)) | ||||||
|  | 	prefix := "" | ||||||
|  | 	message := entry.Message | ||||||
|  |  | ||||||
|  | 	adjustedPrefixPadding := f.PrefixPadding //compensate for ANSI color sequences | ||||||
|  |  | ||||||
|  | 	if prefixValue, ok := entry.Data["prefix"]; ok { | ||||||
|  | 		rawPrefixLength := len(prefixValue.(string)) | ||||||
|  | 		prefix = colorScheme.PrefixColor(" " + prefixValue.(string) + ":") | ||||||
|  | 		adjustedPrefixPadding = f.PrefixPadding + (len(prefix) - rawPrefixLength - 1) | ||||||
|  | 	} else { | ||||||
|  | 		prefixValue, trimmedMsg := extractPrefix(entry.Message) | ||||||
|  | 		rawPrefixLength := len(prefixValue) | ||||||
|  | 		if len(prefixValue) > 0 { | ||||||
|  | 			prefix = colorScheme.PrefixColor(" " + prefixValue + ":") | ||||||
|  | 			message = trimmedMsg | ||||||
|  | 		} | ||||||
|  | 		adjustedPrefixPadding = f.PrefixPadding + (len(prefix) - rawPrefixLength - 1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	prefixFormat := "%s" | ||||||
|  | 	if f.PrefixPadding != 0 { | ||||||
|  | 		prefixFormat = fmt.Sprintf("%%-%ds", adjustedPrefixPadding) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	messageFormat := "%s" | ||||||
|  | 	if f.SpacePadding != 0 { | ||||||
|  | 		messageFormat = fmt.Sprintf("%%-%ds", f.SpacePadding) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if f.DisableTimestamp { | ||||||
|  | 		fmt.Fprintf(b, "%s"+prefixFormat+" "+messageFormat, level, prefix, message) | ||||||
|  | 	} else { | ||||||
|  | 		var timestamp string | ||||||
|  | 		if !f.FullTimestamp { | ||||||
|  | 			timestamp = fmt.Sprintf("[%04d]", miniTS()) | ||||||
|  | 		} else { | ||||||
|  | 			timestamp = fmt.Sprintf("[%s]", entry.Time.Format(timestampFormat)) | ||||||
|  | 		} | ||||||
|  | 		fmt.Fprintf(b, "%s %s"+prefixFormat+" "+messageFormat, colorScheme.TimestampColor(timestamp), level, prefix, message) | ||||||
|  | 	} | ||||||
|  | 	for _, k := range keys { | ||||||
|  | 		if k != "prefix" { | ||||||
|  | 			v := entry.Data[k] | ||||||
|  | 			fmt.Fprintf(b, " %s=%+v", levelColor(k), v) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *TextFormatter) needsQuoting(text string) bool { | ||||||
|  | 	if f.QuoteEmptyFields && len(text) == 0 { | ||||||
|  | 		return true | ||||||
|  | 	} | ||||||
|  | 	for _, ch := range text { | ||||||
|  | 		if !((ch >= 'a' && ch <= 'z') || | ||||||
|  | 			(ch >= 'A' && ch <= 'Z') || | ||||||
|  | 			(ch >= '0' && ch <= '9') || | ||||||
|  | 			ch == '-' || ch == '.') { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func extractPrefix(msg string) (string, string) { | ||||||
|  | 	prefix := "" | ||||||
|  | 	regex := regexp.MustCompile("^\\[(.*?)\\]") | ||||||
|  | 	if regex.MatchString(msg) { | ||||||
|  | 		match := regex.FindString(msg) | ||||||
|  | 		prefix, msg = match[1:len(match)-1], strings.TrimSpace(msg[len(match):]) | ||||||
|  | 	} | ||||||
|  | 	return prefix, msg | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}, appendSpace bool) { | ||||||
|  | 	b.WriteString(key) | ||||||
|  | 	b.WriteByte('=') | ||||||
|  | 	f.appendValue(b, value) | ||||||
|  |  | ||||||
|  | 	if appendSpace { | ||||||
|  | 		b.WriteByte(' ') | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) { | ||||||
|  | 	switch value := value.(type) { | ||||||
|  | 	case string: | ||||||
|  | 		if !f.needsQuoting(value) { | ||||||
|  | 			b.WriteString(value) | ||||||
|  | 		} else { | ||||||
|  | 			fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, value, f.QuoteCharacter) | ||||||
|  | 		} | ||||||
|  | 	case error: | ||||||
|  | 		errmsg := value.Error() | ||||||
|  | 		if !f.needsQuoting(errmsg) { | ||||||
|  | 			b.WriteString(errmsg) | ||||||
|  | 		} else { | ||||||
|  | 			fmt.Fprintf(b, "%s%v%s", f.QuoteCharacter, errmsg, f.QuoteCharacter) | ||||||
|  | 		} | ||||||
|  | 	default: | ||||||
|  | 		fmt.Fprint(b, value) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This is to not silently overwrite `time`, `msg` and `level` fields when | ||||||
|  | // dumping it. If this code wasn't there doing: | ||||||
|  | // | ||||||
|  | //  logrus.WithField("level", 1).Info("hello") | ||||||
|  | // | ||||||
|  | // would just silently drop the user provided level. Instead with this code | ||||||
|  | // it'll be logged as: | ||||||
|  | // | ||||||
|  | //  {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} | ||||||
|  | func prefixFieldClashes(data logrus.Fields) { | ||||||
|  | 	if t, ok := data["time"]; ok { | ||||||
|  | 		data["fields.time"] = t | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if m, ok := data["msg"]; ok { | ||||||
|  | 		data["fields.msg"] = m | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if l, ok := data["level"]; ok { | ||||||
|  | 		data["fields.level"] = l | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								vendor/manifest
									
									
									
									
										vendored
									
									
								
							| @@ -398,6 +398,14 @@ | |||||||
| 			"branch": "master", | 			"branch": "master", | ||||||
| 			"notests": true | 			"notests": true | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"importpath": "github.com/matterbridge/discordgo", | ||||||
|  | 			"repository": "https://github.com/matterbridge/discordgo", | ||||||
|  | 			"vcs": "git", | ||||||
|  | 			"revision": "ef40ff5ba64fced4133098c05527c090f1f4f611", | ||||||
|  | 			"branch": "work", | ||||||
|  | 			"notests": true | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"importpath": "github.com/matterbridge/go-xmpp", | 			"importpath": "github.com/matterbridge/go-xmpp", | ||||||
| 			"repository": "https://github.com/matterbridge/go-xmpp", | 			"repository": "https://github.com/matterbridge/go-xmpp", | ||||||
| @@ -422,6 +430,14 @@ | |||||||
| 			"branch": "work", | 			"branch": "work", | ||||||
| 			"notests": true | 			"notests": true | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			"importpath": "github.com/matterbridge/logrus-prefixed-formatter", | ||||||
|  | 			"repository": "https://github.com/matterbridge/logrus-prefixed-formatter", | ||||||
|  | 			"vcs": "git", | ||||||
|  | 			"revision": "01618749af61a7f764d7cc7f6baea6f940c188a1", | ||||||
|  | 			"branch": "work", | ||||||
|  | 			"notests": true | ||||||
|  | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"importpath": "github.com/mattermost/mattermost-server/einterfaces", | 			"importpath": "github.com/mattermost/mattermost-server/einterfaces", | ||||||
| 			"repository": "https://github.com/mattermost/mattermost-server", | 			"repository": "https://github.com/mattermost/mattermost-server", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Wim
					Wim