Merge remote-tracking branch 'upstream/develop' into 1

This commit is contained in:
LEGOlord208 2017-05-04 20:22:05 +02:00
commit 853853d59f
4 changed files with 245 additions and 44 deletions

View file

@ -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.

View file

@ -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

View file

@ -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
View file

@ -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