Release version v0.21.0
This commit is contained in:
commit
1294b313b9
21 changed files with 350 additions and 104 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# IDE-specific metadata
|
||||
.idea/
|
|
@ -1,8 +1,9 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
install:
|
||||
- go get github.com/bwmarrin/discordgo
|
||||
- go get -v .
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
# DiscordGo
|
||||
|
||||
[](https://godoc.org/github.com/bwmarrin/discordgo) [](http://goreportcard.com/report/bwmarrin/discordgo) [](https://travis-ci.org/bwmarrin/discordgo) [](https://discord.gg/0f1SbxBZjYoCtNPP) [](https://discordapp.com/invite/discord-api)
|
||||
[](https://godoc.org/github.com/bwmarrin/discordgo) [](http://goreportcard.com/report/bwmarrin/discordgo) [](https://travis-ci.org/bwmarrin/discordgo) [](https://discord.gg/0f1SbxBZjYoCtNPP) [](https://discord.com/invite/discord-api)
|
||||
|
||||
<img align="right" src="http://bwmarrin.github.io/discordgo/img/discordgo.png">
|
||||
|
||||
DiscordGo is a [Go](https://golang.org/) package that provides low level
|
||||
bindings to the [Discord](https://discordapp.com/) chat client API. DiscordGo
|
||||
bindings to the [Discord](https://discord.com/) chat client API. DiscordGo
|
||||
has nearly complete support for all of the Discord API endpoints, websocket
|
||||
interface, and voice interface.
|
||||
|
||||
If you would like to help the DiscordGo package please use
|
||||
[this link](https://discordapp.com/oauth2/authorize?client_id=173113690092994561&scope=bot)
|
||||
[this link](https://discord.com/oauth2/authorize?client_id=173113690092994561&scope=bot)
|
||||
to add the official DiscordGo test bot **dgo** to your server. This provides
|
||||
indispensable help to this project.
|
||||
|
||||
|
|
32
discord.go
32
discord.go
|
@ -17,11 +17,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
|
||||
const VERSION = "0.20.3"
|
||||
const VERSION = "0.21.0"
|
||||
|
||||
// ErrMFA will be risen by New when the user has 2FA.
|
||||
var ErrMFA = errors.New("account has 2FA enabled")
|
||||
|
@ -30,10 +31,13 @@ var ErrMFA = errors.New("account has 2FA enabled")
|
|||
// 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,
|
||||
// With a single auth token - All requests will use the token blindly
|
||||
// (just tossing it into the HTTP Authorization header);
|
||||
// no verification of the token will be done and requests may fail.
|
||||
// IF THE TOKEN IS FOR A BOT, IT MUST BE PREFIXED WITH `BOT `
|
||||
// eg: `"Bot <token>"`
|
||||
// IF IT IS AN OAUTH2 ACCESS TOKEN, IT MUST BE PREFIXED WITH `Bearer `
|
||||
// eg: `"Bearer <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
|
||||
|
@ -63,6 +67,15 @@ func New(args ...interface{}) (s *Session, err error) {
|
|||
LastHeartbeatAck: time.Now().UTC(),
|
||||
}
|
||||
|
||||
// Initilize the Identify Package with defaults
|
||||
// These can be modified prior to calling Open()
|
||||
s.Identify.Compress = true
|
||||
s.Identify.LargeThreshold = 250
|
||||
s.Identify.GuildSubscriptions = true
|
||||
s.Identify.Properties.OS = runtime.GOOS
|
||||
s.Identify.Properties.Browser = "DiscordGo v" + VERSION
|
||||
s.Identify.Intents = MakeIntent(IntentsAllWithoutPrivileged)
|
||||
|
||||
// If no arguments are passed return the empty Session interface.
|
||||
if args == nil {
|
||||
return
|
||||
|
@ -94,7 +107,8 @@ func New(args ...interface{}) (s *Session, err error) {
|
|||
|
||||
// If third string exists, it must be an auth token.
|
||||
if len(v) > 2 {
|
||||
s.Token = v[2]
|
||||
s.Identify.Token = v[2]
|
||||
s.Token = v[2] // TODO: Remove, Deprecated - Kept for backwards compatibility.
|
||||
}
|
||||
|
||||
case string:
|
||||
|
@ -107,7 +121,8 @@ func New(args ...interface{}) (s *Session, err error) {
|
|||
} else if pass == "" {
|
||||
pass = v
|
||||
} else if s.Token == "" {
|
||||
s.Token = v
|
||||
s.Identify.Token = v
|
||||
s.Token = v // TODO: Remove, Deprecated - Kept for backwards compatibility.
|
||||
} else {
|
||||
err = fmt.Errorf("too many string parameters provided")
|
||||
return
|
||||
|
@ -127,10 +142,12 @@ func New(args ...interface{}) (s *Session, err error) {
|
|||
// Discord will verify it for free, or log the user in if it is
|
||||
// invalid.
|
||||
if pass == "" {
|
||||
s.Token = auth
|
||||
s.Identify.Token = auth
|
||||
s.Token = auth // TODO: Remove, Deprecated - Kept for backwards compatibility.
|
||||
} else {
|
||||
err = s.Login(auth, pass)
|
||||
if err != nil || s.Token == "" {
|
||||
// TODO: Remove last s.Token part, Deprecated - Kept for backwards compatibility.
|
||||
if err != nil || s.Identify.Token == "" || s.Token == "" {
|
||||
if s.MFA {
|
||||
err = ErrMFA
|
||||
} else {
|
||||
|
@ -140,8 +157,5 @@ func New(args ...interface{}) (s *Session, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ support multi-server voice connections and some other features that are
|
|||
exclusive to Bot accounts only.
|
||||
|
||||
To create a new user account (if you have not done so already) visit the
|
||||
[Discord](https://discordapp.com/) website and click on the
|
||||
[Discord](https://discord.com/) website and click on the
|
||||
**Try Discord Now, It's Free** button then follow the steps to setup your
|
||||
new account.
|
||||
|
||||
|
@ -77,12 +77,12 @@ have access to some user client specific features however they gain access to
|
|||
many Bot specific features.
|
||||
|
||||
To create a new bot account first create yourself a normal user account on
|
||||
Discord then visit the [My Applications](https://discordapp.com/developers/applications/me)
|
||||
Discord then visit the [My Applications](https://discord.com/developers/applications/me)
|
||||
page and click on the **New Application** box. Follow the prompts from there
|
||||
to finish creating your account.
|
||||
|
||||
|
||||
**More information about Bots vs Client accounts can be found [here](https://discordapp.com/developers/docs/topics/oauth2#bot-vs-user-accounts)**
|
||||
**More information about Bots vs Client accounts can be found [here](https://discord.com/developers/docs/topics/oauth2#bot-vs-user-accounts)**
|
||||
|
||||
# Requirements
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
<hr>
|
||||
<img align="right" src="http://bwmarrin.github.io/discordgo/img/discordgo.png">
|
||||
|
||||
[Go](https://golang.org/) (golang) interface for the [Discord](https://discordapp.com/)
|
||||
[Go](https://golang.org/) (golang) interface for the [Discord](https://discord.com/)
|
||||
chat service. Provides both low-level direct bindings to the
|
||||
Discord API and helper functions that allow you to make custom clients and chat
|
||||
bot applications easily.
|
||||
|
||||
[Discord](https://discordapp.com/) is an all-in-one voice and text chat for
|
||||
[Discord](https://discord.com/) is an all-in-one voice and text chat for
|
||||
gamers that's free, secure, and works on both your desktop and phone.
|
||||
|
||||
### Why DiscordGo?
|
||||
|
@ -30,4 +30,4 @@ information and support for DiscordGo. There's also a chance to make some
|
|||
friends :)
|
||||
|
||||
* Join the [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP) chat server dedicated to Go programming.
|
||||
* Join the [Discord API](https://discordapp.com/invite/discord-API) chat server dedicated to the Discord API.
|
||||
* Join the [Discord API](https://discord.com/invite/discord-API) chat server dedicated to the Discord API.
|
||||
|
|
|
@ -18,12 +18,12 @@ var APIVersion = "6"
|
|||
|
||||
// Known Discord API Endpoints.
|
||||
var (
|
||||
EndpointStatus = "https://status.discordapp.com/api/v2/"
|
||||
EndpointStatus = "https://status.discord.com/api/v2/"
|
||||
EndpointSm = EndpointStatus + "scheduled-maintenances/"
|
||||
EndpointSmActive = EndpointSm + "active.json"
|
||||
EndpointSmUpcoming = EndpointSm + "upcoming.json"
|
||||
|
||||
EndpointDiscord = "https://discordapp.com/"
|
||||
EndpointDiscord = "https://discord.com/"
|
||||
EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/"
|
||||
EndpointGuilds = EndpointAPI + "guilds/"
|
||||
EndpointChannels = EndpointAPI + "channels/"
|
||||
|
|
2
event.go
2
event.go
|
@ -110,7 +110,7 @@ func (s *Session) addEventHandlerOnce(eventHandler EventHandler) func() {
|
|||
// })
|
||||
//
|
||||
// List of events can be found at this page, with corresponding names in the
|
||||
// library for each event: https://discordapp.com/developers/docs/topics/gateway#event-names
|
||||
// library for each event: https://discord.com/developers/docs/topics/gateway#event-names
|
||||
// There are also synthetic events fired by the library internally which are
|
||||
// available for handling, like Connect, Disconnect, and RateLimit.
|
||||
// events.go contains all of the Discord WSAPI and synthetic events that can be handled.
|
||||
|
|
|
@ -139,8 +139,11 @@ type GuildEmojisUpdate struct {
|
|||
|
||||
// A GuildMembersChunk is the data for a GuildMembersChunk event.
|
||||
type GuildMembersChunk struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
Members []*Member `json:"members"`
|
||||
GuildID string `json:"guild_id"`
|
||||
Members []*Member `json:"members"`
|
||||
ChunkIndex int `json:"chunk_index"`
|
||||
ChunkCount int `json:"chunk_count"`
|
||||
Presences []*Presence `json:"presences,omitempty"`
|
||||
}
|
||||
|
||||
// GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event.
|
||||
|
@ -169,6 +172,7 @@ type MessageUpdate struct {
|
|||
// MessageDelete is the data for a MessageDelete event.
|
||||
type MessageDelete struct {
|
||||
*Message
|
||||
BeforeDelete *Message `json:"-"`
|
||||
}
|
||||
|
||||
// MessageReactionAdd is the data for a MessageReactionAdd event.
|
||||
|
|
|
@ -6,7 +6,7 @@ This example demonstrates how to utilize DiscordGo to create, view, and delete
|
|||
Bot Applications on your account.
|
||||
|
||||
These tasks are normally accomplished from the
|
||||
[Discord Developers](https://discordapp.com/developers/applications/me) site.
|
||||
[Discord Developers](https://discord.com/developers/applications/me) site.
|
||||
|
||||
**Join [Discord Gophers](https://discord.gg/0f1SbxBZjYoCtNPP)
|
||||
Discord chat channel for support.**
|
||||
|
|
|
@ -51,7 +51,7 @@ func main() {
|
|||
}
|
||||
|
||||
// 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.
|
||||
// message is created on any channel that the authenticated bot has access to.
|
||||
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
|
||||
// Ignore all messages created by the bot itself
|
||||
|
|
2
go.mod
2
go.mod
|
@ -4,3 +4,5 @@ require (
|
|||
github.com/gorilla/websocket v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16
|
||||
)
|
||||
|
||||
go 1.10
|
||||
|
|
|
@ -37,7 +37,7 @@ const (
|
|||
// 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
|
||||
// msglog provides package wide logging consistency for discordgo
|
||||
// the format, a... portion this command follows that of fmt.Printf
|
||||
// msgL : LogLevel of the message
|
||||
// caller : 1 + the number of callers away from the message source
|
||||
|
|
52
message.go
52
message.go
|
@ -63,7 +63,7 @@ type Message struct {
|
|||
MentionRoles []string `json:"mention_roles"`
|
||||
|
||||
// Whether the message is text-to-speech.
|
||||
Tts bool `json:"tts"`
|
||||
TTS bool `json:"tts"`
|
||||
|
||||
// Whether the message mentions everyone.
|
||||
MentionEveryone bool `json:"mention_everyone"`
|
||||
|
@ -129,10 +129,11 @@ type File struct {
|
|||
|
||||
// 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:"-"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Embed *MessageEmbed `json:"embed,omitempty"`
|
||||
TTS bool `json:"tts"`
|
||||
Files []*File `json:"-"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
|
||||
// TODO: Remove this when compatibility is not required.
|
||||
File *File `json:"-"`
|
||||
|
@ -141,8 +142,9 @@ type MessageSend struct {
|
|||
// 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"`
|
||||
Content *string `json:"content,omitempty"`
|
||||
Embed *MessageEmbed `json:"embed,omitempty"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
|
||||
ID string
|
||||
Channel string
|
||||
|
@ -171,6 +173,42 @@ func (m *MessageEdit) SetEmbed(embed *MessageEmbed) *MessageEdit {
|
|||
return m
|
||||
}
|
||||
|
||||
// AllowedMentionType describes the types of mentions used
|
||||
// in the MessageAllowedMentions type.
|
||||
type AllowedMentionType string
|
||||
|
||||
// The types of mentions used in MessageAllowedMentions.
|
||||
const (
|
||||
AllowedMentionTypeRoles AllowedMentionType = "roles"
|
||||
AllowedMentionTypeUsers AllowedMentionType = "users"
|
||||
AllowedMentionTypeEveryone AllowedMentionType = "everyone"
|
||||
)
|
||||
|
||||
// MessageAllowedMentions allows the user to specify which mentions
|
||||
// Discord is allowed to parse in this message. This is useful when
|
||||
// sending user input as a message, as it prevents unwanted mentions.
|
||||
// If this type is used, all mentions must be explicitly whitelisted,
|
||||
// either by putting an AllowedMentionType in the Parse slice
|
||||
// (allowing all mentions of that type) or, in the case of roles and
|
||||
// users, explicitly allowing those mentions on an ID-by-ID basis.
|
||||
// For more information on this functionality, see:
|
||||
// https://discordapp.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-reference
|
||||
type MessageAllowedMentions struct {
|
||||
// The mention types that are allowed to be parsed in this message.
|
||||
// Please note that this is purposely **not** marked as omitempty,
|
||||
// so if a zero-value MessageAllowedMentions object is provided no
|
||||
// mentions will be allowed.
|
||||
Parse []AllowedMentionType `json:"parse"`
|
||||
|
||||
// A list of role IDs to allow. This cannot be used when specifying
|
||||
// AllowedMentionTypeRoles in the Parse slice.
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
|
||||
// A list of user IDs to allow. This cannot be used when specifying
|
||||
// AllowedMentionTypeUsers in the Parse slice.
|
||||
Users []string `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
// A MessageAttachment stores data for message attachments.
|
||||
type MessageAttachment struct {
|
||||
ID string `json:"id"`
|
||||
|
|
27
restapi.go
27
restapi.go
|
@ -38,7 +38,7 @@ var (
|
|||
ErrPruneDaysBounds = errors.New("the number of days should be more than or equal to 1")
|
||||
ErrGuildNoIcon = errors.New("guild does not have an icon set")
|
||||
ErrGuildNoSplash = errors.New("guild does not have a splash set")
|
||||
ErrUnauthorized = errors.New("HTTP request was unauthorized. This could be because the provided token was not a bot token. Please add \"Bot \" to the start of your token. https://discordapp.com/developers/docs/reference#authentication-example-bot-token-authorization-header")
|
||||
ErrUnauthorized = errors.New("HTTP request was unauthorized. This could be because the provided token was not a bot token. Please add \"Bot \" to the start of your token. https://discord.com/developers/docs/reference#authentication-example-bot-token-authorization-header")
|
||||
)
|
||||
|
||||
// Request is the same as RequestWithBucketID but the bucket id is the same as the urlStr
|
||||
|
@ -506,7 +506,7 @@ func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions
|
|||
}
|
||||
|
||||
// Calculates the permissions for a member.
|
||||
// https://support.discordapp.com/hc/en-us/articles/206141927-How-is-the-permission-hierarchy-structured-
|
||||
// https://support.discord.com/hc/en-us/articles/206141927-How-is-the-permission-hierarchy-structured-
|
||||
func memberPermissions(guild *Guild, channel *Channel, member *Member) (apermissions int) {
|
||||
userID := member.User.ID
|
||||
|
||||
|
@ -583,14 +583,6 @@ func memberPermissions(guild *Guild, channel *Channel, member *Member) (apermiss
|
|||
// Guild returns a Guild structure of a specific Guild.
|
||||
// guildID : The ID of a Guild
|
||||
func (s *Session) Guild(guildID string) (st *Guild, err error) {
|
||||
if s.StateEnabled {
|
||||
// Attempt to grab the guild from State first.
|
||||
st, err = s.State.Guild(guildID)
|
||||
if err == nil && !st.Unavailable {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
body, err := s.RequestWithBucketID("GET", EndpointGuild(guildID), nil, EndpointGuild(guildID))
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -931,6 +923,8 @@ type GuildChannelCreateData struct {
|
|||
Topic string `json:"topic,omitempty"`
|
||||
Bitrate int `json:"bitrate,omitempty"`
|
||||
UserLimit int `json:"user_limit,omitempty"`
|
||||
RateLimitPerUser int `json:"rate_limit_per_user,omitempty"`
|
||||
Position int `json:"position,omitempty"`
|
||||
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"`
|
||||
ParentID string `json:"parent_id,omitempty"`
|
||||
NSFW bool `json:"nsfw,omitempty"`
|
||||
|
@ -1593,7 +1587,7 @@ func (s *Session) ChannelMessageSendComplex(channelID string, data *MessageSend)
|
|||
func (s *Session) ChannelMessageSendTTS(channelID string, content string) (*Message, error) {
|
||||
return s.ChannelMessageSendComplex(channelID, &MessageSend{
|
||||
Content: content,
|
||||
Tts: true,
|
||||
TTS: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2132,7 +2126,9 @@ func (s *Session) MessageReactionsRemoveAll(channelID, messageID string) error {
|
|||
// messageID : The message ID.
|
||||
// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier.
|
||||
// limit : max number of users to return (max 100)
|
||||
func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit int) (st []*User, err error) {
|
||||
// beforeID : If provided all reactions returned will be before given ID.
|
||||
// afterID : If provided all reactions returned will be after given ID.
|
||||
func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit int, beforeID, afterID string) (st []*User, err error) {
|
||||
// emoji such as #⃣ need to have # escaped
|
||||
emojiID = strings.Replace(emojiID, "#", "%23", -1)
|
||||
uri := EndpointMessageReactions(channelID, messageID, emojiID)
|
||||
|
@ -2143,6 +2139,13 @@ func (s *Session) MessageReactions(channelID, messageID, emojiID string, limit i
|
|||
v.Set("limit", strconv.Itoa(limit))
|
||||
}
|
||||
|
||||
if afterID != "" {
|
||||
v.Set("after", afterID)
|
||||
}
|
||||
if beforeID != "" {
|
||||
v.Set("before", beforeID)
|
||||
}
|
||||
|
||||
if len(v) > 0 {
|
||||
uri += "?" + v.Encode()
|
||||
}
|
||||
|
|
13
state.go
13
state.go
|
@ -848,6 +848,12 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
|
|||
err = s.MemberAdd(t.Members[i])
|
||||
}
|
||||
}
|
||||
|
||||
if s.TrackPresences {
|
||||
for _, p := range t.Presences {
|
||||
err = s.PresenceAdd(t.GuildID, p)
|
||||
}
|
||||
}
|
||||
case *GuildRoleCreate:
|
||||
if s.TrackRoles {
|
||||
err = s.RoleAdd(t.GuildID, t.Role)
|
||||
|
@ -893,6 +899,13 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
|
|||
}
|
||||
case *MessageDelete:
|
||||
if s.MaxMessageCount != 0 {
|
||||
var old *Message
|
||||
old, err = s.Message(t.ChannelID, t.ID)
|
||||
if err == nil {
|
||||
oldCopy := *old
|
||||
t.BeforeDelete = &oldCopy
|
||||
}
|
||||
|
||||
err = s.MessageRemove(t.Message)
|
||||
}
|
||||
case *MessageDeleteBulk:
|
||||
|
|
132
structs.go
132
structs.go
|
@ -29,8 +29,10 @@ type Session struct {
|
|||
// General configurable settings.
|
||||
|
||||
// Authentication token for this session
|
||||
// TODO: Remove Below, Deprecated, Use Identify struct
|
||||
Token string
|
||||
MFA bool
|
||||
|
||||
MFA bool
|
||||
|
||||
// Debug for printing JSON request/responses
|
||||
Debug bool // Deprecated, will be removed.
|
||||
|
@ -39,6 +41,11 @@ type Session struct {
|
|||
// Should the session reconnect the websocket on errors.
|
||||
ShouldReconnectOnError bool
|
||||
|
||||
// Identify is sent during initial handshake with the discord gateway.
|
||||
// https://discord.com/developers/docs/topics/gateway#identify
|
||||
Identify Identify
|
||||
|
||||
// TODO: Remove Below, Deprecated, Use Identify struct
|
||||
// Should the session request compressed websocket data.
|
||||
Compress bool
|
||||
|
||||
|
@ -587,12 +594,13 @@ type VoiceState struct {
|
|||
|
||||
// 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"`
|
||||
User *User `json:"user"`
|
||||
Status Status `json:"status"`
|
||||
Game *Game `json:"game"`
|
||||
Activities []*Game `json:"activities"`
|
||||
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
|
||||
|
@ -604,6 +612,7 @@ const (
|
|||
GameTypeStreaming
|
||||
GameTypeListening
|
||||
GameTypeWatching
|
||||
GameTypeCustom
|
||||
)
|
||||
|
||||
// A Game struct holds the name of the "playing .." game for a user
|
||||
|
@ -687,7 +696,7 @@ 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"`
|
||||
EnableTTSCommand bool `json:"enable_tts_command"`
|
||||
MessageDisplayCompact bool `json:"message_display_compact"`
|
||||
ShowCurrentGame bool `json:"show_current_game"`
|
||||
ConvertEmoticons bool `json:"convert_emoticons"`
|
||||
|
@ -909,8 +918,63 @@ type GatewayBotResponse struct {
|
|||
Shards int `json:"shards"`
|
||||
}
|
||||
|
||||
// GatewayStatusUpdate is sent by the client to indicate a presence or status update
|
||||
// https://discord.com/developers/docs/topics/gateway#update-status-gateway-status-update-structure
|
||||
type GatewayStatusUpdate struct {
|
||||
Since int `json:"since"`
|
||||
Game Activity `json:"game"`
|
||||
Status string `json:"status"`
|
||||
AFK bool `json:"afk"`
|
||||
}
|
||||
|
||||
// Activity defines the Activity sent with GatewayStatusUpdate
|
||||
// https://discord.com/developers/docs/topics/gateway#activity-object
|
||||
type Activity struct {
|
||||
Name string
|
||||
Type ActivityType
|
||||
URL string
|
||||
}
|
||||
|
||||
// ActivityType is the type of Activity (see ActivityType* consts) in the Activity struct
|
||||
// https://discord.com/developers/docs/topics/gateway#activity-object-activity-types
|
||||
type ActivityType int
|
||||
|
||||
// Valid ActivityType values
|
||||
// https://discord.com/developers/docs/topics/gateway#activity-object-activity-types
|
||||
const (
|
||||
ActivityTypeGame GameType = iota
|
||||
ActivityTypeStreaming
|
||||
ActivityTypeListening
|
||||
// ActivityTypeWatching // not valid in this use case?
|
||||
ActivityTypeCustom = 4
|
||||
)
|
||||
|
||||
// Identify is sent during initial handshake with the discord gateway.
|
||||
// https://discord.com/developers/docs/topics/gateway#identify
|
||||
type Identify struct {
|
||||
Token string `json:"token"`
|
||||
Properties IdentifyProperties `json:"properties"`
|
||||
Compress bool `json:"compress"`
|
||||
LargeThreshold int `json:"large_threshold"`
|
||||
Shard *[2]int `json:"shard,omitempty"`
|
||||
Presence GatewayStatusUpdate `json:"presence,omitempty"`
|
||||
GuildSubscriptions bool `json:"guild_subscriptions"`
|
||||
Intents *Intent `json:"intents,omitempty"`
|
||||
}
|
||||
|
||||
// IdentifyProperties contains the "properties" portion of an Identify packet
|
||||
// https://discord.com/developers/docs/topics/gateway#identify-identify-connection-properties
|
||||
type IdentifyProperties struct {
|
||||
OS string `json:"$os"`
|
||||
Browser string `json:"$browser"`
|
||||
Device string `json:"$device"`
|
||||
Referer string `json:"$referer"`
|
||||
ReferringDomain string `json:"$referring_domain"`
|
||||
}
|
||||
|
||||
// Constants for the different bit offsets of text channel permissions
|
||||
const (
|
||||
// Deprecated: PermissionReadMessages has been replaced with PermissionViewChannel for text and voice channels
|
||||
PermissionReadMessages = 1 << (iota + 10)
|
||||
PermissionSendMessages
|
||||
PermissionSendTTSMessages
|
||||
|
@ -952,8 +1016,9 @@ const (
|
|||
PermissionManageServer
|
||||
PermissionAddReactions
|
||||
PermissionViewAuditLogs
|
||||
PermissionViewChannel = 1 << (iota + 2)
|
||||
|
||||
PermissionAllText = PermissionReadMessages |
|
||||
PermissionAllText = PermissionViewChannel |
|
||||
PermissionSendMessages |
|
||||
PermissionSendTTSMessages |
|
||||
PermissionManageMessages |
|
||||
|
@ -961,7 +1026,8 @@ const (
|
|||
PermissionAttachFiles |
|
||||
PermissionReadMessageHistory |
|
||||
PermissionMentionEveryone
|
||||
PermissionAllVoice = PermissionVoiceConnect |
|
||||
PermissionAllVoice = PermissionViewChannel |
|
||||
PermissionVoiceConnect |
|
||||
PermissionVoiceSpeak |
|
||||
PermissionVoiceMuteMembers |
|
||||
PermissionVoiceDeafenMembers |
|
||||
|
@ -1037,3 +1103,49 @@ const (
|
|||
|
||||
ErrCodeReactionBlocked = 90001
|
||||
)
|
||||
|
||||
// Intent is the type of a Gateway Intent
|
||||
// https://discord.com/developers/docs/topics/gateway#gateway-intents
|
||||
type Intent int
|
||||
|
||||
// Constants for the different bit offsets of intents
|
||||
const (
|
||||
IntentsGuilds Intent = 1 << iota
|
||||
IntentsGuildMembers
|
||||
IntentsGuildBans
|
||||
IntentsGuildEmojis
|
||||
IntentsGuildIntegrations
|
||||
IntentsGuildWebhooks
|
||||
IntentsGuildInvites
|
||||
IntentsGuildVoiceStates
|
||||
IntentsGuildPresences
|
||||
IntentsGuildMessages
|
||||
IntentsGuildMessageReactions
|
||||
IntentsGuildMessageTyping
|
||||
IntentsDirectMessages
|
||||
IntentsDirectMessageReactions
|
||||
IntentsDirectMessageTyping
|
||||
|
||||
IntentsAllWithoutPrivileged = IntentsGuilds |
|
||||
IntentsGuildBans |
|
||||
IntentsGuildEmojis |
|
||||
IntentsGuildIntegrations |
|
||||
IntentsGuildWebhooks |
|
||||
IntentsGuildInvites |
|
||||
IntentsGuildVoiceStates |
|
||||
IntentsGuildMessages |
|
||||
IntentsGuildMessageReactions |
|
||||
IntentsGuildMessageTyping |
|
||||
IntentsDirectMessages |
|
||||
IntentsDirectMessageReactions |
|
||||
IntentsDirectMessageTyping
|
||||
IntentsAll = IntentsAllWithoutPrivileged |
|
||||
IntentsGuildMembers |
|
||||
IntentsGuildPresences
|
||||
IntentsNone Intent = 0
|
||||
)
|
||||
|
||||
// MakeIntent helps convert a gateway intent value for use in the Identify structure.
|
||||
func MakeIntent(intents Intent) *Intent {
|
||||
return &intents
|
||||
}
|
||||
|
|
2
util.go
2
util.go
|
@ -12,6 +12,6 @@ func SnowflakeTimestamp(ID string) (t time.Time, err error) {
|
|||
return
|
||||
}
|
||||
timestamp := (i >> 22) + 1420070400000
|
||||
t = time.Unix(timestamp/1000, 0)
|
||||
t = time.Unix(0, timestamp*1000000)
|
||||
return
|
||||
}
|
||||
|
|
21
util_test.go
Normal file
21
util_test.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package discordgo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSnowflakeTimestamp(t *testing.T) {
|
||||
// #discordgo channel ID :)
|
||||
id := "155361364909621248"
|
||||
parsedTimestamp, err := SnowflakeTimestamp(id)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("returned error incorrect: got %v, want nil", err)
|
||||
}
|
||||
|
||||
correctTimestamp := time.Date(2016, time.March, 4, 17, 10, 35, 869*1000000, time.UTC)
|
||||
if !parsedTimestamp.Equal(correctTimestamp) {
|
||||
t.Errorf("parsed time incorrect: got %v, want %v", parsedTimestamp, correctTimestamp)
|
||||
}
|
||||
}
|
19
voice.go
19
voice.go
|
@ -346,6 +346,25 @@ func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}
|
|||
for {
|
||||
_, message, err := v.wsConn.ReadMessage()
|
||||
if err != nil {
|
||||
// 4014 indicates a manual disconnection by someone in the guild;
|
||||
// we shouldn't reconnect.
|
||||
if websocket.IsCloseError(err, 4014) {
|
||||
v.log(LogInformational, "received 4014 manual disconnection")
|
||||
|
||||
// Abandon the voice WS connection
|
||||
v.Lock()
|
||||
v.wsConn = nil
|
||||
v.Unlock()
|
||||
|
||||
v.session.Lock()
|
||||
delete(v.session.VoiceConnections, v.GuildID)
|
||||
v.session.Unlock()
|
||||
|
||||
v.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Detect if we have been closed manually. If a Close() has already
|
||||
// happened, the websocket we are listening on will be different to the
|
||||
// current session.
|
||||
|
|
111
wsapi.go
111
wsapi.go
|
@ -18,7 +18,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
|
@ -47,7 +46,7 @@ type resumePacket struct {
|
|||
}
|
||||
|
||||
// Open creates a websocket connection to Discord.
|
||||
// See: https://discordapp.com/developers/docs/topics/gateway#connecting
|
||||
// See: https://discord.com/developers/docs/topics/gateway#connecting
|
||||
func (s *Session) Open() error {
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
|
@ -80,7 +79,7 @@ func (s *Session) Open() error {
|
|||
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.log(LogError, "error connecting to gateway %s, %s", s.gateway, err)
|
||||
s.gateway = "" // clear cached gateway
|
||||
s.wsConn = nil // Just to be safe.
|
||||
return err
|
||||
|
@ -399,9 +398,10 @@ func (s *Session) UpdateStatusComplex(usd UpdateStatusData) (err error) {
|
|||
}
|
||||
|
||||
type requestGuildMembersData struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
Query string `json:"query"`
|
||||
Limit int `json:"limit"`
|
||||
GuildIDs []string `json:"guild_id"`
|
||||
Query string `json:"query"`
|
||||
Limit int `json:"limit"`
|
||||
Presences bool `json:"presences"`
|
||||
}
|
||||
|
||||
type requestGuildMembersOp struct {
|
||||
|
@ -411,10 +411,39 @@ type requestGuildMembersOp struct {
|
|||
|
||||
// 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) {
|
||||
// guildID : Single Guild ID to request members of
|
||||
// query : String that username starts with, leave empty to return all members
|
||||
// limit : Max number of items to return, or 0 to request all members matched
|
||||
// presences : Whether to request presences of guild members
|
||||
func (s *Session) RequestGuildMembers(guildID string, query string, limit int, presences bool) (err error) {
|
||||
data := requestGuildMembersData{
|
||||
GuildIDs: []string{guildID},
|
||||
Query: query,
|
||||
Limit: limit,
|
||||
Presences: presences,
|
||||
}
|
||||
err = s.requestGuildMembers(data)
|
||||
return
|
||||
}
|
||||
|
||||
// RequestGuildMembersBatch requests guild members from the gateway
|
||||
// The gateway responds with GuildMembersChunk events
|
||||
// guildID : Slice of guild IDs to request members of
|
||||
// query : String that username starts with, leave empty to return all members
|
||||
// limit : Max number of items to return, or 0 to request all members matched
|
||||
// presences : Whether to request presences of guild members
|
||||
func (s *Session) RequestGuildMembersBatch(guildIDs []string, query string, limit int, presences bool) (err error) {
|
||||
data := requestGuildMembersData{
|
||||
GuildIDs: guildIDs,
|
||||
Query: query,
|
||||
Limit: limit,
|
||||
Presences: presences,
|
||||
}
|
||||
err = s.requestGuildMembers(data)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Session) requestGuildMembers(data requestGuildMembersData) (err error) {
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
s.RLock()
|
||||
|
@ -423,12 +452,6 @@ func (s *Session) RequestGuildMembers(guildID, query string, limit int) (err err
|
|||
return ErrWSNotFound
|
||||
}
|
||||
|
||||
data := requestGuildMembersData{
|
||||
GuildID: guildID,
|
||||
Query: query,
|
||||
Limit: limit,
|
||||
}
|
||||
|
||||
s.wsMutex.Lock()
|
||||
err = s.wsConn.WriteJSON(requestGuildMembersOp{8, data})
|
||||
s.wsMutex.Unlock()
|
||||
|
@ -722,55 +745,42 @@ func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) {
|
|||
}
|
||||
}
|
||||
|
||||
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"`
|
||||
Op int `json:"op"`
|
||||
Data Identify `json:"d"`
|
||||
}
|
||||
|
||||
// identify sends the identify packet to the gateway
|
||||
func (s *Session) identify() error {
|
||||
s.log(LogDebug, "called")
|
||||
|
||||
properties := identifyProperties{runtime.GOOS,
|
||||
"Discordgo v" + VERSION,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
// TODO: This is a temporary block of code to help
|
||||
// maintain backwards compatability
|
||||
if s.Compress == false {
|
||||
s.Identify.Compress = false
|
||||
}
|
||||
|
||||
data := identifyData{s.Token,
|
||||
properties,
|
||||
250,
|
||||
s.Compress,
|
||||
nil,
|
||||
// TODO: This is a temporary block of code to help
|
||||
// maintain backwards compatability
|
||||
if s.Token != "" && s.Identify.Token == "" {
|
||||
s.Identify.Token = s.Token
|
||||
}
|
||||
|
||||
// TODO: Below block should be refactored so ShardID and ShardCount
|
||||
// can be deprecated and their usage moved to the Session.Identify
|
||||
// struct
|
||||
if s.ShardCount > 1 {
|
||||
|
||||
if s.ShardID >= s.ShardCount {
|
||||
return ErrWSShardBounds
|
||||
}
|
||||
|
||||
data.Shard = &[2]int{s.ShardID, s.ShardCount}
|
||||
s.Identify.Shard = &[2]int{s.ShardID, s.ShardCount}
|
||||
}
|
||||
|
||||
op := identifyOp{2, data}
|
||||
|
||||
// Send Identify packet to Discord
|
||||
op := identifyOp{2, s.Identify}
|
||||
s.log(LogDebug, "Identify Packet: \n%#v", op)
|
||||
s.wsMutex.Lock()
|
||||
err := s.wsConn.WriteJSON(op)
|
||||
s.wsMutex.Unlock()
|
||||
|
@ -838,6 +848,13 @@ func (s *Session) Close() error {
|
|||
}
|
||||
|
||||
// Close closes a websocket and stops all listening/heartbeat goroutines.
|
||||
// TODO: Add support for Voice WS/UDP
|
||||
func (s *Session) Close() error {
|
||||
return s.CloseWithCode(websocket.CloseNormalClosure)
|
||||
}
|
||||
|
||||
// CloseWithCode closes a websocket using the provided closeCode and stops all
|
||||
// listening/heartbeat goroutines.
|
||||
// TODO: Add support for Voice WS/UDP connections
|
||||
func (s *Session) CloseWithCode(closeCode int) (err error) {
|
||||
|
||||
|
|
Loading…
Reference in a new issue