Merge remote-tracking branch 'upstream/develop' into 1
This commit is contained in:
commit
853853d59f
4 changed files with 245 additions and 44 deletions
|
@ -39,6 +39,12 @@ var ErrMFA = errors.New("account has 2FA enabled")
|
||||||
// With an email, password and auth token - Discord will verify the auth
|
// With an email, password and auth token - Discord will verify the auth
|
||||||
// token, if it is invalid it will sign in with the provided
|
// token, if it is invalid it will sign in with the provided
|
||||||
// credentials. This is the Discord recommended way to sign in.
|
// 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) {
|
func New(args ...interface{}) (s *Session, err error) {
|
||||||
|
|
||||||
// Create an empty Session interface.
|
// Create an empty Session interface.
|
||||||
|
|
|
@ -11,6 +11,7 @@ package discordgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,11 +32,18 @@ type Message struct {
|
||||||
Reactions []*MessageReactions `json:"reactions"`
|
Reactions []*MessageReactions `json:"reactions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File stores info about files you e.g. send in messages.
|
||||||
|
type File struct {
|
||||||
|
Name string
|
||||||
|
Reader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
// MessageSend stores all parameters you can send with ChannelMessageSendComplex.
|
// MessageSend stores all parameters you can send with ChannelMessageSendComplex.
|
||||||
type MessageSend struct {
|
type MessageSend struct {
|
||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
Embed *MessageEmbed `json:"embed,omitempty"`
|
Embed *MessageEmbed `json:"embed,omitempty"`
|
||||||
Tts bool `json:"tts"`
|
Tts bool `json:"tts"`
|
||||||
|
File *File `json:"file"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessageEdit is used to chain parameters via ChannelMessageEditComplex, which
|
// MessageEdit is used to chain parameters via ChannelMessageEditComplex, which
|
||||||
|
|
119
restapi.go
119
restapi.go
|
@ -173,6 +173,12 @@ func unmarshal(data []byte, v interface{}) error {
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Login asks the Discord server for an authentication token.
|
// Login asks the Discord server for an authentication token.
|
||||||
|
//
|
||||||
|
// NOTE: While email/pass authentication is supported by DiscordGo it is
|
||||||
|
// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token
|
||||||
|
// and then use that authentication token for all future connections.
|
||||||
|
// Also, doing any form of automation with a user (non Bot) account may result
|
||||||
|
// in that account being permanently banned from Discord.
|
||||||
func (s *Session) Login(email, password string) (err error) {
|
func (s *Session) Login(email, password string) (err error) {
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
|
@ -663,11 +669,28 @@ func (s *Session) GuildBans(guildID string) (st []*GuildBan, err error) {
|
||||||
// userID : The ID of a User
|
// userID : The ID of a User
|
||||||
// days : The number of days of previous comments to delete.
|
// days : The number of days of previous comments to delete.
|
||||||
func (s *Session) GuildBanCreate(guildID, userID string, days int) (err error) {
|
func (s *Session) GuildBanCreate(guildID, userID string, days int) (err error) {
|
||||||
|
return s.GuildBanCreateWithReason(guildID, userID, "", days)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GuildBanCreateWithReason bans the given user from the given guild also providing a reaso.
|
||||||
|
// guildID : The ID of a Guild.
|
||||||
|
// userID : The ID of a User
|
||||||
|
// reason : The reason for this ban
|
||||||
|
// days : The number of days of previous comments to delete.
|
||||||
|
func (s *Session) GuildBanCreateWithReason(guildID, userID, reason string, days int) (err error) {
|
||||||
|
|
||||||
uri := EndpointGuildBan(guildID, userID)
|
uri := EndpointGuildBan(guildID, userID)
|
||||||
|
|
||||||
|
queryParams := url.Values{}
|
||||||
if days > 0 {
|
if days > 0 {
|
||||||
uri = fmt.Sprintf("%s?delete-message-days=%d", uri, days)
|
queryParams.Set("delete-message-days", strconv.Itoa(days))
|
||||||
|
}
|
||||||
|
if reason != "" {
|
||||||
|
queryParams.Set("reason", reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(queryParams) > 0 {
|
||||||
|
uri += "?" + queryParams.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.RequestWithBucketID("PUT", uri, nil, EndpointGuildBan(guildID, ""))
|
_, err = s.RequestWithBucketID("PUT", uri, nil, EndpointGuildBan(guildID, ""))
|
||||||
|
@ -1294,7 +1317,59 @@ func (s *Session) ChannelMessageSendComplex(channelID string, data *MessageSend)
|
||||||
data.Embed.Type = "rich"
|
data.Embed.Type = "rich"
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := s.RequestWithBucketID("POST", EndpointChannelMessages(channelID), data, EndpointChannelMessages(channelID))
|
endpoint := EndpointChannelMessages(channelID)
|
||||||
|
|
||||||
|
var response []byte
|
||||||
|
if data.File != nil {
|
||||||
|
body := &bytes.Buffer{}
|
||||||
|
bodywriter := multipart.NewWriter(body)
|
||||||
|
|
||||||
|
// What's a better way of doing this? Reflect? Generator? I'm open to suggestions
|
||||||
|
|
||||||
|
if data.Content != "" {
|
||||||
|
if err = bodywriter.WriteField("content", data.Content); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Embed != nil {
|
||||||
|
var embed []byte
|
||||||
|
embed, err = json.Marshal(data.Embed)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = bodywriter.WriteField("embed", string(embed))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Tts {
|
||||||
|
if err = bodywriter.WriteField("tts", "true"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer io.Writer
|
||||||
|
writer, err = bodywriter.CreateFormFile("file", data.File.Name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(writer, data.File.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bodywriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err = s.request("POST", endpoint, bodywriter.FormDataContentType(), body.Bytes(), endpoint, 0)
|
||||||
|
} else {
|
||||||
|
response, err = s.RequestWithBucketID("POST", endpoint, data, endpoint)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1427,48 +1502,18 @@ func (s *Session) ChannelMessagesPinned(channelID string) (st []*Message, err er
|
||||||
// channelID : The ID of a Channel.
|
// channelID : The ID of a Channel.
|
||||||
// name: The name of the file.
|
// name: The name of the file.
|
||||||
// io.Reader : A reader for the file contents.
|
// io.Reader : A reader for the file contents.
|
||||||
func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (st *Message, err error) {
|
func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (*Message, error) {
|
||||||
return s.ChannelFileSendWithMessage(channelID, "", name, r)
|
return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelFileSendWithMessage sends a file to the given channel with an message.
|
// ChannelFileSendWithMessage sends a file to the given channel with an message.
|
||||||
|
// DEPRECATED. Use ChannelMessageSendComplex instead.
|
||||||
// channelID : The ID of a Channel.
|
// channelID : The ID of a Channel.
|
||||||
// content: Optional Message content.
|
// content: Optional Message content.
|
||||||
// name: The name of the file.
|
// name: The name of the file.
|
||||||
// io.Reader : A reader for the file contents.
|
// io.Reader : A reader for the file contents.
|
||||||
func (s *Session) ChannelFileSendWithMessage(channelID, content string, name string, r io.Reader) (st *Message, err error) {
|
func (s *Session) ChannelFileSendWithMessage(channelID, content string, name string, r io.Reader) (*Message, error) {
|
||||||
|
return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}, Content: content})
|
||||||
body := &bytes.Buffer{}
|
|
||||||
bodywriter := multipart.NewWriter(body)
|
|
||||||
|
|
||||||
if len(content) != 0 {
|
|
||||||
if err := bodywriter.WriteField("content", content); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writer, err := bodywriter.CreateFormFile("file", name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = io.Copy(writer, r)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bodywriter.Close()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := s.request("POST", EndpointChannelMessages(channelID), bodywriter.FormDataContentType(), body.Bytes(), EndpointChannelMessages(channelID), 0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = unmarshal(response, &st)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelInvites returns an array of Invite structures for the given channel
|
// ChannelInvites returns an array of Invite structures for the given channel
|
||||||
|
|
156
state.go
156
state.go
|
@ -34,6 +34,7 @@ type State struct {
|
||||||
TrackMembers bool
|
TrackMembers bool
|
||||||
TrackRoles bool
|
TrackRoles bool
|
||||||
TrackVoice bool
|
TrackVoice bool
|
||||||
|
TrackPresences bool
|
||||||
|
|
||||||
guildMap map[string]*Guild
|
guildMap map[string]*Guild
|
||||||
channelMap map[string]*Channel
|
channelMap map[string]*Channel
|
||||||
|
@ -46,13 +47,14 @@ func NewState() *State {
|
||||||
PrivateChannels: []*Channel{},
|
PrivateChannels: []*Channel{},
|
||||||
Guilds: []*Guild{},
|
Guilds: []*Guild{},
|
||||||
},
|
},
|
||||||
TrackChannels: true,
|
TrackChannels: true,
|
||||||
TrackEmojis: true,
|
TrackEmojis: true,
|
||||||
TrackMembers: true,
|
TrackMembers: true,
|
||||||
TrackRoles: true,
|
TrackRoles: true,
|
||||||
TrackVoice: true,
|
TrackVoice: true,
|
||||||
guildMap: make(map[string]*Guild),
|
TrackPresences: true,
|
||||||
channelMap: make(map[string]*Channel),
|
guildMap: make(map[string]*Guild),
|
||||||
|
channelMap: make(map[string]*Channel),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,6 +149,107 @@ func (s *State) Guild(guildID string) (*Guild, error) {
|
||||||
return nil, errors.New("guild not found")
|
return nil, errors.New("guild not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 errors.New("presence not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, errors.New("presence not found")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Consider moving Guild state update methods onto *Guild.
|
// TODO: Consider moving Guild state update methods onto *Guild.
|
||||||
|
|
||||||
// MemberAdd adds a member to the current world state, or
|
// MemberAdd adds a member to the current world state, or
|
||||||
|
@ -725,6 +828,45 @@ func (s *State) onInterface(se *Session, i interface{}) (err error) {
|
||||||
if s.TrackVoice {
|
if s.TrackVoice {
|
||||||
err = s.voiceStateUpdate(t)
|
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
|
return
|
||||||
|
|
Loading…
Reference in a new issue