Browse Source

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

LEGOlord208 8 năm trước cách đây
mục cha
commit
853853d59f
4 tập tin đã thay đổi với 245 bổ sung44 xóa
  1. 6 0
      discord.go
  2. 8 0
      message.go
  3. 82 37
      restapi.go
  4. 149 7
      state.go

+ 6 - 0
discord.go

@@ -39,6 +39,12 @@ var ErrMFA = errors.New("account has 2FA enabled")
 //     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.

+ 8 - 0
message.go

@@ -11,6 +11,7 @@ package discordgo
 
 import (
 	"fmt"
+	"io"
 	"regexp"
 )
 
@@ -31,11 +32,18 @@ type Message struct {
 	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.
 type MessageSend struct {
 	Content string        `json:"content,omitempty"`
 	Embed   *MessageEmbed `json:"embed,omitempty"`
 	Tts     bool          `json:"tts"`
+	File    *File         `json:"file"`
 }
 
 // MessageEdit is used to chain parameters via ChannelMessageEditComplex, which

+ 82 - 37
restapi.go

@@ -173,6 +173,12 @@ func unmarshal(data []byte, v interface{}) error {
 // ------------------------------------------------------------------------------------------------
 
 // 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) {
 
 	data := struct {
@@ -663,11 +669,28 @@ func (s *Session) GuildBans(guildID string) (st []*GuildBan, err error) {
 // userID    : The ID of a User
 // days      : The number of days of previous comments to delete.
 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)
 
+	queryParams := url.Values{}
 	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, ""))
@@ -1294,7 +1317,59 @@ func (s *Session) ChannelMessageSendComplex(channelID string, data *MessageSend)
 		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 {
 		return
 	}
@@ -1427,48 +1502,18 @@ func (s *Session) ChannelMessagesPinned(channelID string) (st []*Message, err er
 // channelID : The ID of a Channel.
 // name: The name of the file.
 // io.Reader : A reader for the file contents.
-func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (st *Message, err error) {
-	return s.ChannelFileSendWithMessage(channelID, "", name, r)
+func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (*Message, error) {
+	return s.ChannelMessageSendComplex(channelID, &MessageSend{File: &File{Name: name, Reader: r}})
 }
 
 // ChannelFileSendWithMessage sends a file to the given channel with an message.
+// DEPRECATED. Use ChannelMessageSendComplex instead.
 // channelID : The ID of a Channel.
 // content: Optional Message content.
 // name: The name of the file.
 // io.Reader : A reader for the file contents.
-func (s *Session) ChannelFileSendWithMessage(channelID, content string, name string, r io.Reader) (st *Message, err error) {
-
-	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
+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})
 }
 
 // ChannelInvites returns an array of Invite structures for the given channel

+ 149 - 7
state.go

@@ -34,6 +34,7 @@ type State struct {
 	TrackMembers    bool
 	TrackRoles      bool
 	TrackVoice      bool
+	TrackPresences  bool
 
 	guildMap   map[string]*Guild
 	channelMap map[string]*Channel
@@ -46,13 +47,14 @@ func NewState() *State {
 			PrivateChannels: []*Channel{},
 			Guilds:          []*Guild{},
 		},
-		TrackChannels: true,
-		TrackEmojis:   true,
-		TrackMembers:  true,
-		TrackRoles:    true,
-		TrackVoice:    true,
-		guildMap:      make(map[string]*Guild),
-		channelMap:    make(map[string]*Channel),
+		TrackChannels:  true,
+		TrackEmojis:    true,
+		TrackMembers:   true,
+		TrackRoles:     true,
+		TrackVoice:     true,
+		TrackPresences: true,
+		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")
 }
 
+// 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.
 
 // 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 {
 			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