From 79e6c9fa6cfca4bfe8913fd891fd9b12bd5bb505 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 28 Jan 2017 22:45:32 +0100 Subject: Update vendor --- bridge/gitter/gitter.go | 2 +- vendor/github.com/42wim/go-gitter/LICENSE | 201 ----- vendor/github.com/42wim/go-gitter/faye.go | 70 -- vendor/github.com/42wim/go-gitter/gitter.go | 430 --------- vendor/github.com/42wim/go-gitter/model.go | 142 --- vendor/github.com/42wim/go-gitter/stream.go | 221 ----- vendor/github.com/42wim/go-gitter/test_utils.go | 30 - .../42wim/matterbridge-plus/bridge/LICENSE | 202 ----- .../42wim/matterbridge-plus/bridge/bridge.go | 434 --------- .../42wim/matterbridge-plus/bridge/config.go | 68 -- .../42wim/matterbridge-plus/bridge/helper.go | 59 -- .../42wim/matterbridge-plus/matterclient/LICENSE | 202 ----- .../matterbridge-plus/matterclient/matterclient.go | 441 ---------- vendor/github.com/bwmarrin/discordgo/discord.go | 144 +-- vendor/github.com/bwmarrin/discordgo/endpoints.go | 18 + vendor/github.com/bwmarrin/discordgo/event.go | 238 +++++ .../github.com/bwmarrin/discordgo/eventhandlers.go | 977 +++++++++++++++++++++ vendor/github.com/bwmarrin/discordgo/events.go | 239 +++-- .../bwmarrin/discordgo/examples/airhorn/main.go | 12 +- .../bwmarrin/discordgo/examples/new_basic/main.go | 13 +- .../bwmarrin/discordgo/examples/pingpong/main.go | 14 +- vendor/github.com/bwmarrin/discordgo/message.go | 100 ++- vendor/github.com/bwmarrin/discordgo/oauth2.go | 13 +- vendor/github.com/bwmarrin/discordgo/ratelimit.go | 157 ++++ vendor/github.com/bwmarrin/discordgo/restapi.go | 710 ++++++++++++--- vendor/github.com/bwmarrin/discordgo/state.go | 124 ++- vendor/github.com/bwmarrin/discordgo/structs.go | 274 +++--- .../discordgo/tools/cmd/eventhandlers/main.go | 123 +++ vendor/github.com/bwmarrin/discordgo/types.go | 58 ++ vendor/github.com/bwmarrin/discordgo/voice.go | 4 +- vendor/github.com/bwmarrin/discordgo/wsapi.go | 108 ++- vendor/github.com/mattn/go-xmpp/xmpp.go | 11 +- vendor/github.com/sromku/go-gitter/LICENSE | 201 +++++ vendor/github.com/sromku/go-gitter/faye.go | 70 ++ vendor/github.com/sromku/go-gitter/gitter.go | 468 ++++++++++ vendor/github.com/sromku/go-gitter/model.go | 142 +++ vendor/github.com/sromku/go-gitter/stream.go | 221 +++++ vendor/github.com/sromku/go-gitter/test_utils.go | 30 + vendor/manifest | 38 +- 39 files changed, 3891 insertions(+), 3118 deletions(-) delete mode 100644 vendor/github.com/42wim/go-gitter/LICENSE delete mode 100644 vendor/github.com/42wim/go-gitter/faye.go delete mode 100644 vendor/github.com/42wim/go-gitter/gitter.go delete mode 100644 vendor/github.com/42wim/go-gitter/model.go delete mode 100644 vendor/github.com/42wim/go-gitter/stream.go delete mode 100644 vendor/github.com/42wim/go-gitter/test_utils.go delete mode 100644 vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE delete mode 100644 vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go delete mode 100644 vendor/github.com/42wim/matterbridge-plus/bridge/config.go delete mode 100644 vendor/github.com/42wim/matterbridge-plus/bridge/helper.go delete mode 100644 vendor/github.com/42wim/matterbridge-plus/matterclient/LICENSE delete mode 100644 vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go create mode 100644 vendor/github.com/bwmarrin/discordgo/event.go create mode 100644 vendor/github.com/bwmarrin/discordgo/eventhandlers.go create mode 100644 vendor/github.com/bwmarrin/discordgo/ratelimit.go create mode 100644 vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go create mode 100644 vendor/github.com/bwmarrin/discordgo/types.go create mode 100644 vendor/github.com/sromku/go-gitter/LICENSE create mode 100644 vendor/github.com/sromku/go-gitter/faye.go create mode 100644 vendor/github.com/sromku/go-gitter/gitter.go create mode 100644 vendor/github.com/sromku/go-gitter/model.go create mode 100644 vendor/github.com/sromku/go-gitter/stream.go create mode 100644 vendor/github.com/sromku/go-gitter/test_utils.go diff --git a/bridge/gitter/gitter.go b/bridge/gitter/gitter.go index ffc5e93f..0400b4ba 100644 --- a/bridge/gitter/gitter.go +++ b/bridge/gitter/gitter.go @@ -1,9 +1,9 @@ package bgitter import ( - "github.com/42wim/go-gitter" "github.com/42wim/matterbridge/bridge/config" log "github.com/Sirupsen/logrus" + "github.com/sromku/go-gitter" "strings" ) diff --git a/vendor/github.com/42wim/go-gitter/LICENSE b/vendor/github.com/42wim/go-gitter/LICENSE deleted file mode 100644 index 8dada3ed..00000000 --- a/vendor/github.com/42wim/go-gitter/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/42wim/go-gitter/faye.go b/vendor/github.com/42wim/go-gitter/faye.go deleted file mode 100644 index dcd3e210..00000000 --- a/vendor/github.com/42wim/go-gitter/faye.go +++ /dev/null @@ -1,70 +0,0 @@ -package gitter - -import ( - "encoding/json" - "fmt" - - "github.com/mrexodia/wray" -) - -type Faye struct { - endpoint string - Event chan Event - client *wray.FayeClient - gitter *Gitter -} - -func (gitter *Gitter) Faye(roomID string) *Faye { - wray.RegisterTransports([]wray.Transport{ - &wray.HttpTransport{ - SendHook: func(data map[string]interface{}) { - if channel, ok := data["channel"]; ok && channel == "/meta/handshake" { - data["ext"] = map[string]interface{}{"token": gitter.config.token} - } - }, - }, - }) - return &Faye{ - endpoint: "/api/v1/rooms/" + roomID + "/chatMessages", - Event: make(chan Event), - client: wray.NewFayeClient(fayeBaseURL), - gitter: gitter, - } -} - -func (faye *Faye) Listen() { - defer faye.destroy() - - faye.client.Subscribe(faye.endpoint, false, func(message wray.Message) { - dataBytes, err := json.Marshal(message.Data["model"]) - if err != nil { - fmt.Printf("JSON Marshal error: %v\n", err) - return - } - var gitterMessage Message - err = json.Unmarshal(dataBytes, &gitterMessage) - if err != nil { - fmt.Printf("JSON Unmarshal error: %v\n", err) - return - } - faye.Event <- Event{ - Data: &MessageReceived{ - Message: gitterMessage, - }, - } - }) - - //TODO: this might be needed in the future - /*go func() { - for { - faye.client.Publish("/api/v1/ping2", map[string]interface{}{"reason": "ping"}) - time.Sleep(60 * time.Second) - } - }()*/ - - faye.client.Listen() -} - -func (faye *Faye) destroy() { - close(faye.Event) -} diff --git a/vendor/github.com/42wim/go-gitter/gitter.go b/vendor/github.com/42wim/go-gitter/gitter.go deleted file mode 100644 index 2f9e9a51..00000000 --- a/vendor/github.com/42wim/go-gitter/gitter.go +++ /dev/null @@ -1,430 +0,0 @@ -// Package gitter is a Go client library for the Gitter API. -// -// Author: sromku -package gitter - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "log" - "net/http" - "net/url" - "strconv" - "time" - - "github.com/mreiferson/go-httpclient" -) - -var ( - apiBaseURL = "https://api.gitter.im/v1/" - streamBaseURL = "https://stream.gitter.im/v1/" - fayeBaseURL = "https://ws.gitter.im/faye" -) - -type Gitter struct { - config struct { - apiBaseURL string - streamBaseURL string - token string - client *http.Client - } - debug bool - logWriter io.Writer -} - -// New initializes the Gitter API client -// -// For example: -// api := gitter.New("YOUR_ACCESS_TOKEN") -func New(token string) *Gitter { - - transport := &httpclient.Transport{ - ConnectTimeout: 5 * time.Second, - ReadWriteTimeout: 40 * time.Second, - } - defer transport.Close() - - s := &Gitter{} - s.config.apiBaseURL = apiBaseURL - s.config.streamBaseURL = streamBaseURL - s.config.token = token - s.config.client = &http.Client{ - Transport: transport, - } - return s -} - -// SetClient sets a custom http client. Can be useful in App Engine case. -func (gitter *Gitter) SetClient(client *http.Client) { - gitter.config.client = client -} - -// GetUser returns the current user -func (gitter *Gitter) GetUser() (*User, error) { - - var users []User - response, err := gitter.get(gitter.config.apiBaseURL + "user") - if err != nil { - gitter.log(err) - return nil, err - } - - err = json.Unmarshal(response, &users) - if err != nil { - gitter.log(err) - return nil, err - } - - if len(users) > 0 { - return &users[0], nil - } - - err = APIError{What: "Failed to retrieve current user"} - gitter.log(err) - return nil, err -} - -// GetUserRooms returns a list of Rooms the user is part of -func (gitter *Gitter) GetUserRooms(userID string) ([]Room, error) { - - var rooms []Room - response, err := gitter.get(gitter.config.apiBaseURL + "user/" + userID + "/rooms") - if err != nil { - gitter.log(err) - return nil, err - } - - err = json.Unmarshal(response, &rooms) - if err != nil { - gitter.log(err) - return nil, err - } - - return rooms, nil -} - -// GetRooms returns a list of rooms the current user is in -func (gitter *Gitter) GetRooms() ([]Room, error) { - - var rooms []Room - response, err := gitter.get(gitter.config.apiBaseURL + "rooms") - if err != nil { - gitter.log(err) - return nil, err - } - - err = json.Unmarshal(response, &rooms) - if err != nil { - gitter.log(err) - return nil, err - } - - return rooms, nil -} - -// GetUsersInRoom returns the users in the room with the passed id -func (gitter *Gitter) GetUsersInRoom(roomID string) ([]User, error) { - - var users []User - response, err := gitter.get(gitter.config.apiBaseURL + "rooms/" + roomID + "/users") - if err != nil { - gitter.log(err) - return nil, err - } - - err = json.Unmarshal(response, &users) - if err != nil { - gitter.log(err) - return nil, err - } - return users, nil -} - -// GetRoom returns a room with the passed id -func (gitter *Gitter) GetRoom(roomID string) (*Room, error) { - - var room Room - response, err := gitter.get(gitter.config.apiBaseURL + "rooms/" + roomID) - if err != nil { - gitter.log(err) - return nil, err - } - - err = json.Unmarshal(response, &room) - if err != nil { - gitter.log(err) - return nil, err - } - - return &room, nil -} - -// GetMessages returns a list of messages in a room. -// Pagination is optional. You can pass nil or specific pagination params. -func (gitter *Gitter) GetMessages(roomID string, params *Pagination) ([]Message, error) { - - var messages []Message - url := gitter.config.apiBaseURL + "rooms/" + roomID + "/chatMessages" - if params != nil { - url += "?" + params.encode() - } - response, err := gitter.get(url) - if err != nil { - gitter.log(err) - return nil, err - } - - err = json.Unmarshal(response, &messages) - if err != nil { - gitter.log(err) - return nil, err - } - - return messages, nil -} - -// GetMessage returns a message in a room. -func (gitter *Gitter) GetMessage(roomID, messageID string) (*Message, error) { - - var message Message - response, err := gitter.get(gitter.config.apiBaseURL + "rooms/" + roomID + "/chatMessages/" + messageID) - if err != nil { - gitter.log(err) - return nil, err - } - - err = json.Unmarshal(response, &message) - if err != nil { - gitter.log(err) - return nil, err - } - - return &message, nil -} - -// SendMessage sends a message to a room -func (gitter *Gitter) SendMessage(roomID, text string) error { - - message := Message{Text: text} - body, _ := json.Marshal(message) - _, err := gitter.post(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages", body) - if err != nil { - gitter.log(err) - return err - } - - return nil -} - -// JoinRoom joins a room -func (gitter *Gitter) JoinRoom(roomID, userID string) (*Room, error) { - - message := Room{ID: roomID} - body, _ := json.Marshal(message) - response, err := gitter.post(gitter.config.apiBaseURL+"user/"+userID+"/rooms", body) - - if err != nil { - gitter.log(err) - return nil, err - } - - var room Room - err = json.Unmarshal(response, &room) - if err != nil { - gitter.log(err) - return nil, err - } - - return &room, nil -} - -// LeaveRoom removes a user from the room -func (gitter *Gitter) LeaveRoom(roomID, userID string) error { - - _, err := gitter.delete(gitter.config.apiBaseURL + "rooms/" + roomID + "/users/" + userID) - if err != nil { - gitter.log(err) - return err - } - - return nil -} - -// SetDebug traces errors if it's set to true. -func (gitter *Gitter) SetDebug(debug bool, logWriter io.Writer) { - gitter.debug = debug - gitter.logWriter = logWriter -} - -// Pagination params -type Pagination struct { - - // Skip n messages - Skip int - - // Get messages before beforeId - BeforeID string - - // Get messages after afterId - AfterID string - - // Maximum number of messages to return - Limit int - - // Search query - Query string -} - -func (messageParams *Pagination) encode() string { - values := url.Values{} - - if messageParams.AfterID != "" { - values.Add("afterId", messageParams.AfterID) - } - - if messageParams.BeforeID != "" { - values.Add("beforeId", messageParams.BeforeID) - } - - if messageParams.Skip > 0 { - values.Add("skip", strconv.Itoa(messageParams.Skip)) - } - - if messageParams.Limit > 0 { - values.Add("limit", strconv.Itoa(messageParams.Limit)) - } - - return values.Encode() -} - -func (gitter *Gitter) getResponse(url string, stream *Stream) (*http.Response, error) { - r, err := http.NewRequest("GET", url, nil) - if err != nil { - gitter.log(err) - return nil, err - } - r.Header.Set("Content-Type", "application/json") - r.Header.Set("Accept", "application/json") - r.Header.Set("Authorization", "Bearer "+gitter.config.token) - if stream != nil { - stream.streamConnection.request = r - } - response, err := gitter.config.client.Do(r) - if err != nil { - gitter.log(err) - return nil, err - } - return response, nil -} - -func (gitter *Gitter) get(url string) ([]byte, error) { - resp, err := gitter.getResponse(url, nil) - if err != nil { - gitter.log(err) - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)} - gitter.log(err) - return nil, err - } - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - gitter.log(err) - return nil, err - } - - return body, nil -} - -func (gitter *Gitter) post(url string, body []byte) ([]byte, error) { - r, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) - if err != nil { - gitter.log(err) - return nil, err - } - - r.Header.Set("Content-Type", "application/json") - r.Header.Set("Accept", "application/json") - r.Header.Set("Authorization", "Bearer "+gitter.config.token) - - resp, err := gitter.config.client.Do(r) - if err != nil { - gitter.log(err) - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)} - gitter.log(err) - return nil, err - } - - result, err := ioutil.ReadAll(resp.Body) - if err != nil { - gitter.log(err) - return nil, err - } - - return result, nil -} - -func (gitter *Gitter) delete(url string) ([]byte, error) { - r, err := http.NewRequest("delete", url, nil) - if err != nil { - gitter.log(err) - return nil, err - } - - r.Header.Set("Content-Type", "application/json") - r.Header.Set("Accept", "application/json") - r.Header.Set("Authorization", "Bearer "+gitter.config.token) - - resp, err := gitter.config.client.Do(r) - if err != nil { - gitter.log(err) - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)} - gitter.log(err) - return nil, err - } - - result, err := ioutil.ReadAll(resp.Body) - if err != nil { - gitter.log(err) - return nil, err - } - - return result, nil -} - -func (gitter *Gitter) log(a interface{}) { - if gitter.debug { - log.Println(a) - if gitter.logWriter != nil { - timestamp := time.Now().Format(time.RFC3339) - msg := fmt.Sprintf("%v: %v", timestamp, a) - fmt.Fprintln(gitter.logWriter, msg) - } - } -} - -// APIError holds data of errors returned from the API. -type APIError struct { - What string -} - -func (e APIError) Error() string { - return fmt.Sprintf("%v", e.What) -} diff --git a/vendor/github.com/42wim/go-gitter/model.go b/vendor/github.com/42wim/go-gitter/model.go deleted file mode 100644 index 7a3d0729..00000000 --- a/vendor/github.com/42wim/go-gitter/model.go +++ /dev/null @@ -1,142 +0,0 @@ -package gitter - -import "time" - -// A Room in Gitter can represent a GitHub Organization, a GitHub Repository, a Gitter Channel or a One-to-one conversation. -// In the case of the Organizations and Repositories, the access control policies are inherited from GitHub. -type Room struct { - - // Room ID - ID string `json:"id"` - - // Room name - Name string `json:"name"` - - // Room topic. (default: GitHub repo description) - Topic string `json:"topic"` - - // Room URI on Gitter - URI string `json:"uri"` - - // Indicates if the room is a one-to-one chat - OneToOne bool `json:"oneToOne"` - - // Count of users in the room - UserCount int `json:"userCount"` - - // Number of unread messages for the current user - UnreadItems int `json:"unreadItems"` - - // Number of unread mentions for the current user - Mentions int `json:"mentions"` - - // Last time the current user accessed the room in ISO format - LastAccessTime time.Time `json:"lastAccessTime"` - - // Indicates if the current user has disabled notifications - Lurk bool `json:"lurk"` - - // Path to the room on gitter - URL string `json:"url"` - - // Type of the room - // - ORG: A room that represents a GitHub Organization. - // - REPO: A room that represents a GitHub Repository. - // - ONETOONE: A one-to-one chat. - // - ORG_CHANNEL: A Gitter channel nested under a GitHub Organization. - // - REPO_CHANNEL A Gitter channel nested under a GitHub Repository. - // - USER_CHANNEL A Gitter channel nested under a GitHub User. - GithubType string `json:"githubType"` - - // Tags that define the room - Tags []string `json:"tags"` - - RoomMember bool `json:"roomMember"` - - // Room version. - Version int `json:"v"` -} - -type User struct { - - // Gitter User ID - ID string `json:"id"` - - // Gitter/GitHub username - Username string `json:"username"` - - // Gitter/GitHub user real name - DisplayName string `json:"displayName"` - - // Path to the user on Gitter - URL string `json:"url"` - - // User avatar URI (small) - AvatarURLSmall string `json:"avatarUrlSmall"` - - // User avatar URI (medium) - AvatarURLMedium string `json:"avatarUrlMedium"` -} - -type Message struct { - - // ID of the message - ID string `json:"id"` - - // Original message in plain-text/markdown - Text string `json:"text"` - - // HTML formatted message - HTML string `json:"html"` - - // ISO formatted date of the message - Sent time.Time `json:"sent"` - - // ISO formatted date of the message if edited - EditedAt time.Time `json:"editedAt"` - - // User that sent the message - From User `json:"fromUser"` - - // Boolean that indicates if the current user has read the message. - Unread bool `json:"unread"` - - // Number of users that have read the message - ReadBy int `json:"readBy"` - - // List of URLs present in the message - Urls []URL `json:"urls"` - - // List of @Mentions in the message - Mentions []Mention `json:"mentions"` - - // List of #Issues referenced in the message - Issues []Issue `json:"issues"` - - // Version - Version int `json:"v"` -} - -// Mention holds data about mentioned user in the message -type Mention struct { - - // User's username - ScreenName string `json:"screenName"` - - // Gitter User ID - UserID string `json:"userID"` -} - -// Issue references issue in the message -type Issue struct { - - // Issue number - Number string `json:"number"` -} - -// URL presented in the message -type URL struct { - - // URL - URL string `json:"url"` -} diff --git a/vendor/github.com/42wim/go-gitter/stream.go b/vendor/github.com/42wim/go-gitter/stream.go deleted file mode 100644 index 4a5a3c68..00000000 --- a/vendor/github.com/42wim/go-gitter/stream.go +++ /dev/null @@ -1,221 +0,0 @@ -package gitter - -import ( - "bufio" - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/mreiferson/go-httpclient" -) - -var defaultConnectionWaitTime time.Duration = 3000 // millis -var defaultConnectionMaxRetries = 5 - -// Stream initialize stream -func (gitter *Gitter) Stream(roomID string) *Stream { - return &Stream{ - url: streamBaseURL + "rooms/" + roomID + "/chatMessages", - Event: make(chan Event), - gitter: gitter, - streamConnection: gitter.newStreamConnection( - defaultConnectionWaitTime, - defaultConnectionMaxRetries), - } -} - -// Implemented to conform with https://developer.gitter.im/docs/streaming-api -func (gitter *Gitter) Listen(stream *Stream) { - - defer stream.destroy() - - var reader *bufio.Reader - var gitterMessage Message - lastKeepalive := time.Now().Unix() - - // connect - stream.connect() - -Loop: - for { - - // if closed then stop trying - if stream.isClosed() { - stream.Event <- Event{ - Data: &GitterConnectionClosed{}, - } - break Loop - } - - resp := stream.getResponse() - if resp.StatusCode != 200 { - gitter.log(fmt.Sprintf("Unexpected response code %v", resp.StatusCode)) - continue - } - - //"The JSON stream returns messages as JSON objects that are delimited by carriage return (\r)" <- Not true crap it's (\n) only - reader = bufio.NewReader(resp.Body) - line, err := reader.ReadBytes('\n') - if err != nil { - gitter.log("ReadBytes error: " + err.Error()) - stream.connect() - continue - } - - //Check if the line only consists of whitespace - onlyWhitespace := true - for _, b := range line { - if b != ' ' && b != '\t' && b != '\r' && b != '\n' { - onlyWhitespace = false - } - } - - if onlyWhitespace { - //"Parsers must be tolerant of occasional extra newline characters placed between messages." - currentKeepalive := time.Now().Unix() //interesting behavior of 100+ keepalives per seconds was observed - if currentKeepalive-lastKeepalive > 10 { - lastKeepalive = currentKeepalive - gitter.log("Keepalive was received") - } - continue - } else if stream.isClosed() { - gitter.log("Stream closed") - continue - } - - // unmarshal the streamed data - err = json.Unmarshal(line, &gitterMessage) - if err != nil { - gitter.log("JSON Unmarshal error: " + err.Error()) - continue - } - - // we are here, then we got the good message. pipe it forward. - stream.Event <- Event{ - Data: &MessageReceived{ - Message: gitterMessage, - }, - } - } - - gitter.log("Listening was completed") -} - -// Stream holds stream data. -type Stream struct { - url string - Event chan Event - streamConnection *streamConnection - gitter *Gitter -} - -func (stream *Stream) destroy() { - close(stream.Event) -} - -type Event struct { - Data interface{} -} - -type GitterConnectionClosed struct { -} - -type MessageReceived struct { - Message Message -} - -// connect and try to reconnect with -func (stream *Stream) connect() { - - if stream.streamConnection.retries == stream.streamConnection.currentRetries { - stream.Close() - stream.gitter.log("Number of retries exceeded the max retries number, we are done here") - return - } - - res, err := stream.gitter.getResponse(stream.url, stream) - if stream.streamConnection.canceled { - // do nothing - } else if err != nil || res.StatusCode != 200 { - stream.gitter.log("Failed to get response, trying reconnect ") - stream.gitter.log(err) - - // sleep and wait - stream.streamConnection.currentRetries++ - time.Sleep(time.Millisecond * stream.streamConnection.wait * time.Duration(stream.streamConnection.currentRetries)) - - // connect again - stream.Close() - stream.connect() - } else { - stream.gitter.log("Response was received") - stream.streamConnection.currentRetries = 0 - stream.streamConnection.closed = false - stream.streamConnection.response = res - } -} - -type streamConnection struct { - - // connection was closed - closed bool - - // canceled - canceled bool - - // wait time till next try - wait time.Duration - - // max tries to recover - retries int - - // current streamed response - response *http.Response - - // current request - request *http.Request - - // current status - currentRetries int -} - -// Close the stream connection and stop receiving streamed data -func (stream *Stream) Close() { - conn := stream.streamConnection - conn.closed = true - if conn.response != nil { - stream.gitter.log("Stream connection close response") - defer conn.response.Body.Close() - } - if conn.request != nil { - stream.gitter.log("Stream connection close request") - switch transport := stream.gitter.config.client.Transport.(type) { - case *httpclient.Transport: - stream.streamConnection.canceled = true - transport.CancelRequest(conn.request) - default: - } - - } - conn.currentRetries = 0 -} - -func (stream *Stream) isClosed() bool { - return stream.streamConnection.closed -} - -func (stream *Stream) getResponse() *http.Response { - return stream.streamConnection.response -} - -// Optional, set stream connection properties -// wait - time in milliseconds of waiting between reconnections. Will grow exponentially. -// retries - number of reconnections retries before dropping the stream. -func (gitter *Gitter) newStreamConnection(wait time.Duration, retries int) *streamConnection { - return &streamConnection{ - closed: true, - wait: wait, - retries: retries, - } -} diff --git a/vendor/github.com/42wim/go-gitter/test_utils.go b/vendor/github.com/42wim/go-gitter/test_utils.go deleted file mode 100644 index 6703da2e..00000000 --- a/vendor/github.com/42wim/go-gitter/test_utils.go +++ /dev/null @@ -1,30 +0,0 @@ -package gitter - -import ( - "net/http" - "net/http/httptest" - "net/url" -) - -var ( - mux *http.ServeMux - gitter *Gitter - server *httptest.Server -) - -func setup() { - mux = http.NewServeMux() - server = httptest.NewServer(mux) - - gitter = New("abc") - - // Fake the API and Stream base URLs by using the test - // server URL instead. - url, _ := url.Parse(server.URL) - gitter.config.apiBaseURL = url.String() + "/" - gitter.config.streamBaseURL = url.String() + "/" -} - -func teardown() { - server.Close() -} diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE b/vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE deleted file mode 100644 index 8f71f43f..00000000 --- a/vendor/github.com/42wim/matterbridge-plus/bridge/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go b/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go deleted file mode 100644 index ee27a65d..00000000 --- a/vendor/github.com/42wim/matterbridge-plus/bridge/bridge.go +++ /dev/null @@ -1,434 +0,0 @@ -package bridge - -import ( - "crypto/tls" - "github.com/42wim/matterbridge-plus/matterclient" - "github.com/42wim/matterbridge/matterhook" - log "github.com/Sirupsen/logrus" - "github.com/peterhellberg/giphy" - ircm "github.com/sorcix/irc" - "github.com/thoj/go-ircevent" - "regexp" - "sort" - "strconv" - "strings" - "time" -) - -//type Bridge struct { -type MMhook struct { - mh *matterhook.Client -} - -type MMapi struct { - mc *matterclient.MMClient - mmMap map[string]string - mmIgnoreNicks []string -} - -type MMirc struct { - i *irc.Connection - ircNick string - ircMap map[string]string - names map[string][]string - ircIgnoreNicks []string -} - -type MMMessage struct { - Text string - Channel string - Username string -} - -type Bridge struct { - MMhook - MMapi - MMirc - *Config - kind string -} - -type FancyLog struct { - irc *log.Entry - mm *log.Entry -} - -var flog FancyLog - -const Legacy = "legacy" - -func initFLog() { - flog.irc = log.WithFields(log.Fields{"module": "irc"}) - flog.mm = log.WithFields(log.Fields{"module": "mattermost"}) -} - -func NewBridge(name string, config *Config, kind string) *Bridge { - initFLog() - b := &Bridge{} - b.Config = config - b.kind = kind - b.ircNick = b.Config.IRC.Nick - b.ircMap = make(map[string]string) - b.MMirc.names = make(map[string][]string) - b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks) - b.mmIgnoreNicks = strings.Fields(b.Config.Mattermost.IgnoreNicks) - if kind == Legacy { - if len(b.Config.Token) > 0 { - for _, val := range b.Config.Token { - b.ircMap[val.IRCChannel] = val.MMChannel - } - } - - b.mh = matterhook.New(b.Config.Mattermost.URL, - matterhook.Config{Port: b.Config.Mattermost.Port, Token: b.Config.Mattermost.Token, - InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify, - BindAddress: b.Config.Mattermost.BindAddress}) - } else { - b.mmMap = make(map[string]string) - if len(b.Config.Channel) > 0 { - for _, val := range b.Config.Channel { - b.ircMap[val.IRC] = val.Mattermost - b.mmMap[val.Mattermost] = val.IRC - } - } - b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password, - b.Config.Mattermost.Team, b.Config.Mattermost.Server) - b.mc.SkipTLSVerify = b.Config.Mattermost.SkipTLSVerify - b.mc.NoTLS = b.Config.Mattermost.NoTLS - flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Mattermost.Login, b.Config.Mattermost.Team, b.Config.Mattermost.Server) - err := b.mc.Login() - if err != nil { - flog.mm.Fatal("Can not connect", err) - } - flog.mm.Info("Login ok") - b.mc.JoinChannel(b.Config.Mattermost.Channel) - if len(b.Config.Channel) > 0 { - for _, val := range b.Config.Channel { - b.mc.JoinChannel(val.Mattermost) - } - } - go b.mc.WsReceiver() - } - flog.irc.Info("Trying IRC connection") - b.i = b.createIRC(name) - flog.irc.Info("Connection succeeded") - go b.handleMatter() - return b -} - -func (b *Bridge) createIRC(name string) *irc.Connection { - i := irc.IRC(b.Config.IRC.Nick, b.Config.IRC.Nick) - i.UseTLS = b.Config.IRC.UseTLS - i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.IRC.SkipTLSVerify} - if b.Config.IRC.Password != "" { - i.Password = b.Config.IRC.Password - } - i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) - i.Connect(b.Config.IRC.Server + ":" + strconv.Itoa(b.Config.IRC.Port)) - return i -} - -func (b *Bridge) handleNewConnection(event *irc.Event) { - flog.irc.Info("Registering callbacks") - i := b.i - b.ircNick = event.Arguments[0] - i.AddCallback("PRIVMSG", b.handlePrivMsg) - i.AddCallback("CTCP_ACTION", b.handlePrivMsg) - i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames) - i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames) - i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime) - i.AddCallback(ircm.NOTICE, b.handleNotice) - i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.irc.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) - i.AddCallback("PING", func(e *irc.Event) { - i.SendRaw("PONG :" + e.Message()) - flog.irc.Debugf("PING/PONG") - }) - if b.Config.Mattermost.ShowJoinPart { - i.AddCallback("JOIN", b.handleJoinPart) - i.AddCallback("PART", b.handleJoinPart) - } - i.AddCallback("*", b.handleOther) - b.setupChannels() -} - -func (b *Bridge) setupChannels() { - i := b.i - if b.Config.IRC.Channel != "" { - flog.irc.Infof("Joining %s as %s", b.Config.IRC.Channel, b.ircNick) - i.Join(b.Config.IRC.Channel) - } - if b.kind == Legacy { - for _, val := range b.Config.Token { - flog.irc.Infof("Joining %s as %s", val.IRCChannel, b.ircNick) - i.Join(val.IRCChannel) - } - } else { - for _, val := range b.Config.Channel { - flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick) - i.Join(val.IRC) - } - } -} - -func (b *Bridge) handleIrcBotCommand(event *irc.Event) bool { - parts := strings.Fields(event.Message()) - exp, _ := regexp.Compile("[:,]+$") - channel := event.Arguments[0] - command := "" - if len(parts) == 2 { - command = parts[1] - } - if exp.ReplaceAllString(parts[0], "") == b.ircNick { - switch command { - case "users": - usernames := b.mc.UsernamesInChannel(b.getMMChannel(channel)) - sort.Strings(usernames) - b.i.Privmsg(channel, "Users on Mattermost: "+strings.Join(usernames, ", ")) - default: - b.i.Privmsg(channel, "Valid commands are: [users, help]") - } - return true - } - return false -} - -func (b *Bridge) ircNickFormat(nick string) string { - if nick == b.ircNick { - return nick - } - if b.Config.Mattermost.RemoteNickFormat == nil { - return "irc-" + nick - } - return strings.Replace(*b.Config.Mattermost.RemoteNickFormat, "{NICK}", nick, -1) -} - -func (b *Bridge) handlePrivMsg(event *irc.Event) { - if b.ignoreMessage(event.Nick, event.Message(), "irc") { - return - } - if b.handleIrcBotCommand(event) { - return - } - msg := "" - if event.Code == "CTCP_ACTION" { - msg = event.Nick + " " - } - msg += event.Message() - b.Send(b.ircNickFormat(event.Nick), msg, b.getMMChannel(event.Arguments[0])) -} - -func (b *Bridge) handleJoinPart(event *irc.Event) { - b.Send(b.ircNick, b.ircNickFormat(event.Nick)+" "+strings.ToLower(event.Code)+"s "+event.Message(), b.getMMChannel(event.Arguments[0])) -} - -func (b *Bridge) handleNotice(event *irc.Event) { - if strings.Contains(event.Message(), "This nickname is registered") { - b.i.Privmsg(b.Config.IRC.NickServNick, "IDENTIFY "+b.Config.IRC.NickServPassword) - } -} - -func (b *Bridge) nicksPerRow() int { - if b.Config.Mattermost.NicksPerRow < 1 { - return 4 - } - return b.Config.Mattermost.NicksPerRow -} - -func (b *Bridge) formatnicks(nicks []string, continued bool) string { - switch b.Config.Mattermost.NickFormatter { - case "table": - return tableformatter(nicks, b.nicksPerRow(), continued) - default: - return plainformatter(nicks, b.nicksPerRow()) - } -} - -func (b *Bridge) storeNames(event *irc.Event) { - channel := event.Arguments[2] - b.MMirc.names[channel] = append( - b.MMirc.names[channel], - strings.Split(strings.TrimSpace(event.Message()), " ")...) -} - -func (b *Bridge) endNames(event *irc.Event) { - channel := event.Arguments[1] - sort.Strings(b.MMirc.names[channel]) - maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow() - continued := false - for len(b.MMirc.names[channel]) > maxNamesPerPost { - b.Send( - b.ircNick, - b.formatnicks(b.MMirc.names[channel][0:maxNamesPerPost], continued), - b.getMMChannel(channel)) - b.MMirc.names[channel] = b.MMirc.names[channel][maxNamesPerPost:] - continued = true - } - b.Send(b.ircNick, b.formatnicks(b.MMirc.names[channel], continued), b.getMMChannel(channel)) - b.MMirc.names[channel] = nil -} - -func (b *Bridge) handleTopicWhoTime(event *irc.Event) { - parts := strings.Split(event.Arguments[2], "!") - t, err := strconv.ParseInt(event.Arguments[3], 10, 64) - if err != nil { - flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3]) - } - user := parts[0] - if len(parts) > 1 { - user += " [" + parts[1] + "]" - } - flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0)) -} - -func (b *Bridge) handleOther(event *irc.Event) { - flog.irc.Debugf("%#v", event) -} - -func (b *Bridge) Send(nick string, message string, channel string) error { - return b.SendType(nick, message, channel, "") -} - -func (b *Bridge) SendType(nick string, message string, channel string, mtype string) error { - if b.Config.Mattermost.PrefixMessagesWithNick { - if IsMarkup(message) { - message = nick + "\n\n" + message - } else { - message = nick + " " + message - } - } - if b.kind == Legacy { - matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL} - matterMessage.Channel = channel - matterMessage.UserName = nick - matterMessage.Type = mtype - matterMessage.Text = message - err := b.mh.Send(matterMessage) - if err != nil { - flog.mm.Info(err) - return err - } - return nil - } - flog.mm.Debug("->mattermost channel: ", channel, " ", message) - b.mc.PostMessage(channel, message) - return nil -} - -func (b *Bridge) handleMatterHook(mchan chan *MMMessage) { - for { - message := b.mh.Receive() - m := &MMMessage{} - m.Username = message.UserName - m.Text = message.Text - m.Channel = message.Token - mchan <- m - } -} - -func (b *Bridge) handleMatterClient(mchan chan *MMMessage) { - for message := range b.mc.MessageChan { - // do not post our own messages back to irc - if message.Raw.Action == "posted" && b.mc.User.Username != message.Username { - m := &MMMessage{} - m.Username = message.Username - m.Channel = message.Channel - m.Text = message.Text - flog.mm.Debugf("<-mattermost channel: %s %#v %#v", message.Channel, message.Post, message.Raw) - mchan <- m - } - } -} - -func (b *Bridge) handleMatter() { - flog.mm.Infof("Choosing Mattermost connection type %s", b.kind) - mchan := make(chan *MMMessage) - if b.kind == Legacy { - go b.handleMatterHook(mchan) - } else { - go b.handleMatterClient(mchan) - } - flog.mm.Info("Start listening for Mattermost messages") - for message := range mchan { - var username string - if b.ignoreMessage(message.Username, message.Text, "mattermost") { - continue - } - username = message.Username + ": " - if b.Config.IRC.RemoteNickFormat != "" { - username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1) - } else if b.Config.IRC.UseSlackCircumfix { - username = "<" + message.Username + "> " - } - cmds := strings.Fields(message.Text) - // empty message - if len(cmds) == 0 { - continue - } - cmd := cmds[0] - switch cmd { - case "!users": - flog.mm.Info("Received !users from ", message.Username) - b.i.SendRaw("NAMES " + b.getIRCChannel(message.Channel)) - continue - case "!gif": - message.Text = b.giphyRandom(strings.Fields(strings.Replace(message.Text, "!gif ", "", 1))) - b.Send(b.ircNick, message.Text, b.getIRCChannel(message.Channel)) - continue - } - texts := strings.Split(message.Text, "\n") - for _, text := range texts { - flog.mm.Debug("Sending message from " + message.Username + " to " + message.Channel) - b.i.Privmsg(b.getIRCChannel(message.Channel), username+text) - } - } -} - -func (b *Bridge) giphyRandom(query []string) string { - g := giphy.DefaultClient - if b.Config.General.GiphyAPIKey != "" { - g.APIKey = b.Config.General.GiphyAPIKey - } - res, err := g.Random(query) - if err != nil { - return "error" - } - return res.Data.FixedHeightDownsampledURL -} - -func (b *Bridge) getMMChannel(ircChannel string) string { - mmchannel, ok := b.ircMap[ircChannel] - if !ok { - mmchannel = b.Config.Mattermost.Channel - } - return mmchannel -} - -func (b *Bridge) getIRCChannel(channel string) string { - if b.kind == Legacy { - ircchannel := b.Config.IRC.Channel - _, ok := b.Config.Token[channel] - if ok { - ircchannel = b.Config.Token[channel].IRCChannel - } - return ircchannel - } - ircchannel, ok := b.mmMap[channel] - if !ok { - ircchannel = b.Config.IRC.Channel - } - return ircchannel -} - -func (b *Bridge) ignoreMessage(nick string, message string, protocol string) bool { - var ignoreNicks = b.mmIgnoreNicks - if protocol == "irc" { - ignoreNicks = b.ircIgnoreNicks - } - // should we discard messages ? - for _, entry := range ignoreNicks { - if nick == entry { - return true - } - } - return false -} diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/config.go b/vendor/github.com/42wim/matterbridge-plus/bridge/config.go deleted file mode 100644 index f31db98d..00000000 --- a/vendor/github.com/42wim/matterbridge-plus/bridge/config.go +++ /dev/null @@ -1,68 +0,0 @@ -package bridge - -import ( - "gopkg.in/gcfg.v1" - "io/ioutil" - "log" -) - -type Config struct { - IRC struct { - UseTLS bool - SkipTLSVerify bool - Server string - Port int - Nick string - Password string - Channel string - UseSlackCircumfix bool - NickServNick string - NickServPassword string - RemoteNickFormat string - IgnoreNicks string - } - Mattermost struct { - URL string - Port int - ShowJoinPart bool - Token string - IconURL string - SkipTLSVerify bool - BindAddress string - Channel string - PrefixMessagesWithNick bool - NicksPerRow int - NickFormatter string - Server string - Team string - Login string - Password string - RemoteNickFormat *string - IgnoreNicks string - NoTLS bool - } - Token map[string]*struct { - IRCChannel string - MMChannel string - } - Channel map[string]*struct { - IRC string - Mattermost string - } - General struct { - GiphyAPIKey string - } -} - -func NewConfig(cfgfile string) *Config { - var cfg Config - content, err := ioutil.ReadFile(cfgfile) - if err != nil { - log.Fatal(err) - } - err = gcfg.ReadStringInto(&cfg, string(content)) - if err != nil { - log.Fatal("Failed to parse "+cfgfile+":", err) - } - return &cfg -} diff --git a/vendor/github.com/42wim/matterbridge-plus/bridge/helper.go b/vendor/github.com/42wim/matterbridge-plus/bridge/helper.go deleted file mode 100644 index 7669ad57..00000000 --- a/vendor/github.com/42wim/matterbridge-plus/bridge/helper.go +++ /dev/null @@ -1,59 +0,0 @@ -package bridge - -import ( - "strings" -) - -func tableformatter(nicks []string, nicksPerRow int, continued bool) string { - result := "|IRC users" - if continued { - result = "|(continued)" - } - for i := 0; i < 2; i++ { - for j := 1; j <= nicksPerRow && j <= len(nicks); j++ { - if i == 0 { - result += "|" - } else { - result += ":-|" - } - } - result += "\r\n|" - } - result += nicks[0] + "|" - for i := 1; i < len(nicks); i++ { - if i%nicksPerRow == 0 { - result += "\r\n|" + nicks[i] + "|" - } else { - result += nicks[i] + "|" - } - } - return result -} - -func plainformatter(nicks []string, nicksPerRow int) string { - return strings.Join(nicks, ", ") + " currently on IRC" -} - -func IsMarkup(message string) bool { - switch message[0] { - case '|': - fallthrough - case '#': - fallthrough - case '_': - fallthrough - case '*': - fallthrough - case '~': - fallthrough - case '-': - fallthrough - case ':': - fallthrough - case '>': - fallthrough - case '=': - return true - } - return false -} diff --git a/vendor/github.com/42wim/matterbridge-plus/matterclient/LICENSE b/vendor/github.com/42wim/matterbridge-plus/matterclient/LICENSE deleted file mode 100644 index 8f71f43f..00000000 --- a/vendor/github.com/42wim/matterbridge-plus/matterclient/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - diff --git a/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go b/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go deleted file mode 100644 index 93128220..00000000 --- a/vendor/github.com/42wim/matterbridge-plus/matterclient/matterclient.go +++ /dev/null @@ -1,441 +0,0 @@ -package matterclient - -import ( - "crypto/tls" - "errors" - log "github.com/Sirupsen/logrus" - "net/http" - "net/http/cookiejar" - "net/url" - "strings" - "time" - - "github.com/gorilla/websocket" - "github.com/jpillora/backoff" - "github.com/mattermost/platform/model" -) - -type Credentials struct { - Login string - Team string - Pass string - Server string - NoTLS bool - SkipTLSVerify bool -} - -type Message struct { - Raw *model.Message - Post *model.Post - Team string - Channel string - Username string - Text string -} - -type MMClient struct { - *Credentials - Client *model.Client - WsClient *websocket.Conn - WsQuit bool - WsAway bool - Channels *model.ChannelList - MoreChannels *model.ChannelList - User *model.User - Users map[string]*model.User - MessageChan chan *Message - Team *model.Team - log *log.Entry -} - -func New(login, pass, team, server string) *MMClient { - cred := &Credentials{Login: login, Pass: pass, Team: team, Server: server} - mmclient := &MMClient{Credentials: cred, MessageChan: make(chan *Message, 100)} - mmclient.log = log.WithFields(log.Fields{"module": "matterclient"}) - log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) - return mmclient -} - -func (m *MMClient) SetLogLevel(level string) { - l, err := log.ParseLevel(level) - if err != nil { - log.SetLevel(log.InfoLevel) - return - } - log.SetLevel(l) -} - -func (m *MMClient) Login() error { - if m.WsQuit { - return nil - } - b := &backoff.Backoff{ - Min: time.Second, - Max: 5 * time.Minute, - Jitter: true, - } - uriScheme := "https://" - wsScheme := "wss://" - if m.NoTLS { - uriScheme = "http://" - wsScheme = "ws://" - } - // login to mattermost - m.Client = model.NewClient(uriScheme + m.Credentials.Server) - m.Client.HttpClient.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} - var myinfo *model.Result - var appErr *model.AppError - var logmsg = "trying login" - for { - m.log.Debugf("%s %s %s %s", logmsg, m.Credentials.Team, m.Credentials.Login, m.Credentials.Server) - if strings.Contains(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN) { - m.log.Debugf(logmsg+" with ", model.SESSION_COOKIE_TOKEN) - token := strings.Split(m.Credentials.Pass, model.SESSION_COOKIE_TOKEN+"=") - m.Client.HttpClient.Jar = m.createCookieJar(token[1]) - m.Client.MockSession(token[1]) - myinfo, appErr = m.Client.GetMe("") - if myinfo.Data.(*model.User) == nil { - m.log.Errorf("LOGIN TOKEN: %s is invalid", m.Credentials.Pass) - return errors.New("invalid " + model.SESSION_COOKIE_TOKEN) - } - } else { - myinfo, appErr = m.Client.Login(m.Credentials.Login, m.Credentials.Pass) - } - if appErr != nil { - d := b.Duration() - m.log.Debug(appErr.DetailedError) - if !strings.Contains(appErr.DetailedError, "connection refused") && - !strings.Contains(appErr.DetailedError, "invalid character") { - if appErr.Message == "" { - return errors.New(appErr.DetailedError) - } - return errors.New(appErr.Message) - } - m.log.Debugf("LOGIN: %s, reconnecting in %s", appErr, d) - time.Sleep(d) - logmsg = "retrying login" - continue - } - break - } - // reset timer - b.Reset() - - initLoad, _ := m.Client.GetInitialLoad() - initData := initLoad.Data.(*model.InitialLoad) - m.User = initData.User - for _, v := range initData.Teams { - m.log.Debugf("trying %s (id: %s)", v.Name, v.Id) - if v.Name == m.Credentials.Team { - m.Client.SetTeamId(v.Id) - m.Team = v - m.log.Debugf("GetallTeamListings: found id %s for team %s", v.Id, v.Name) - break - } - } - if m.Team == nil { - return errors.New("team not found") - } - - // setup websocket connection - wsurl := wsScheme + m.Credentials.Server + "/api/v3/users/websocket" - header := http.Header{} - header.Set(model.HEADER_AUTH, "BEARER "+m.Client.AuthToken) - - m.log.Debug("WsClient: making connection") - var err error - for { - wsDialer := &websocket.Dialer{Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: m.SkipTLSVerify}} - m.WsClient, _, err = wsDialer.Dial(wsurl, header) - if err != nil { - d := b.Duration() - m.log.Debugf("WSS: %s, reconnecting in %s", err, d) - time.Sleep(d) - continue - } - break - } - b.Reset() - - // populating users - m.UpdateUsers() - - // populating channels - m.UpdateChannels() - - return nil -} - -func (m *MMClient) WsReceiver() { - var rmsg model.Message - for { - if m.WsQuit { - m.log.Debug("exiting WsReceiver") - return - } - if err := m.WsClient.ReadJSON(&rmsg); err != nil { - m.log.Error("error:", err) - // reconnect - m.Login() - } - if rmsg.Action == "ping" { - m.handleWsPing() - continue - } - msg := &Message{Raw: &rmsg, Team: m.Credentials.Team} - m.parseMessage(msg) - m.MessageChan <- msg - } - -} - -func (m *MMClient) handleWsPing() { - m.log.Debug("Ws PING") - if !m.WsQuit && !m.WsAway { - m.log.Debug("Ws PONG") - m.WsClient.WriteMessage(websocket.PongMessage, []byte{}) - } -} - -func (m *MMClient) parseMessage(rmsg *Message) { - switch rmsg.Raw.Action { - case model.ACTION_POSTED: - m.parseActionPost(rmsg) - /* - case model.ACTION_USER_REMOVED: - m.handleWsActionUserRemoved(&rmsg) - case model.ACTION_USER_ADDED: - m.handleWsActionUserAdded(&rmsg) - */ - } -} - -func (m *MMClient) parseActionPost(rmsg *Message) { - data := model.PostFromJson(strings.NewReader(rmsg.Raw.Props["post"])) - // log.Println("receiving userid", data.UserId) - // we don't have the user, refresh the userlist - if m.Users[data.UserId] == nil { - m.UpdateUsers() - } - rmsg.Username = m.Users[data.UserId].Username - rmsg.Channel = m.GetChannelName(data.ChannelId) - // direct message - if strings.Contains(rmsg.Channel, "__") { - //log.Println("direct message") - rcvusers := strings.Split(rmsg.Channel, "__") - if rcvusers[0] != m.User.Id { - rmsg.Channel = m.Users[rcvusers[0]].Username - } else { - rmsg.Channel = m.Users[rcvusers[1]].Username - } - } - rmsg.Text = data.Message - rmsg.Post = data - return -} - -func (m *MMClient) UpdateUsers() error { - mmusers, _ := m.Client.GetProfilesForDirectMessageList(m.Team.Id) - m.Users = mmusers.Data.(map[string]*model.User) - return nil -} - -func (m *MMClient) UpdateChannels() error { - mmchannels, _ := m.Client.GetChannels("") - m.Channels = mmchannels.Data.(*model.ChannelList) - mmchannels, _ = m.Client.GetMoreChannels("") - m.MoreChannels = mmchannels.Data.(*model.ChannelList) - return nil -} - -func (m *MMClient) GetChannelName(id string) string { - for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) { - if channel.Id == id { - return channel.Name - } - } - // not found? could be a new direct message from mattermost. Try to update and check again - m.UpdateChannels() - for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) { - if channel.Id == id { - return channel.Name - } - } - return "" -} - -func (m *MMClient) GetChannelId(name string) string { - for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) { - if channel.Name == name { - return channel.Id - } - } - return "" -} - -func (m *MMClient) GetChannelHeader(id string) string { - for _, channel := range append(m.Channels.Channels, m.MoreChannels.Channels...) { - if channel.Id == id { - return channel.Header - } - } - return "" -} - -func (m *MMClient) PostMessage(channel string, text string) { - post := &model.Post{ChannelId: m.GetChannelId(channel), Message: text} - m.Client.CreatePost(post) -} - -func (m *MMClient) JoinChannel(channel string) error { - cleanChan := strings.Replace(channel, "#", "", 1) - if m.GetChannelId(cleanChan) == "" { - return errors.New("failed to join") - } - for _, c := range m.Channels.Channels { - if c.Name == cleanChan { - m.log.Debug("Not joining ", cleanChan, " already joined.") - return nil - } - } - m.log.Debug("Joining ", cleanChan) - _, err := m.Client.JoinChannel(m.GetChannelId(cleanChan)) - if err != nil { - return errors.New("failed to join") - } - // m.SyncChannel(m.getMMChannelId(strings.Replace(channel, "#", "", 1)), strings.Replace(channel, "#", "", 1)) - return nil -} - -func (m *MMClient) GetPostsSince(channelId string, time int64) *model.PostList { - res, err := m.Client.GetPostsSince(channelId, time) - if err != nil { - return nil - } - return res.Data.(*model.PostList) -} - -func (m *MMClient) SearchPosts(query string) *model.PostList { - res, err := m.Client.SearchPosts(query, false) - if err != nil { - return nil - } - return res.Data.(*model.PostList) -} - -func (m *MMClient) GetPosts(channelId string, limit int) *model.PostList { - res, err := m.Client.GetPosts(channelId, 0, limit, "") - if err != nil { - return nil - } - return res.Data.(*model.PostList) -} - -func (m *MMClient) GetPublicLink(filename string) string { - res, err := m.Client.GetPublicLink(filename) - if err != nil { - return "" - } - return res.Data.(string) -} - -func (m *MMClient) GetPublicLinks(filenames []string) []string { - var output []string - for _, f := range filenames { - res, err := m.Client.GetPublicLink(f) - if err != nil { - continue - } - output = append(output, res.Data.(string)) - } - return output -} - -func (m *MMClient) UpdateChannelHeader(channelId string, header string) { - data := make(map[string]string) - data["channel_id"] = channelId - data["channel_header"] = header - m.log.Debugf("updating channelheader %#v, %#v", channelId, header) - _, err := m.Client.UpdateChannelHeader(data) - if err != nil { - log.Error(err) - } -} - -func (m *MMClient) UpdateLastViewed(channelId string) { - m.log.Debugf("posting lastview %#v", channelId) - _, err := m.Client.UpdateLastViewedAt(channelId) - if err != nil { - m.log.Error(err) - } -} - -func (m *MMClient) UsernamesInChannel(channelName string) []string { - ceiRes, err := m.Client.GetChannelExtraInfo(m.GetChannelId(channelName), 5000, "") - if err != nil { - m.log.Errorf("UsernamesInChannel(%s) failed: %s", channelName, err) - return []string{} - } - extra := ceiRes.Data.(*model.ChannelExtra) - result := []string{} - for _, member := range extra.Members { - result = append(result, member.Username) - } - return result -} - -func (m *MMClient) createCookieJar(token string) *cookiejar.Jar { - var cookies []*http.Cookie - jar, _ := cookiejar.New(nil) - firstCookie := &http.Cookie{ - Name: "MMAUTHTOKEN", - Value: token, - Path: "/", - Domain: m.Credentials.Server, - } - cookies = append(cookies, firstCookie) - cookieURL, _ := url.Parse("https://" + m.Credentials.Server) - jar.SetCookies(cookieURL, cookies) - return jar -} - -func (m *MMClient) GetOtherUserDM(channel string) *model.User { - m.UpdateUsers() - var rcvuser *model.User - if strings.Contains(channel, "__") { - rcvusers := strings.Split(channel, "__") - if rcvusers[0] != m.User.Id { - rcvuser = m.Users[rcvusers[0]] - } else { - rcvuser = m.Users[rcvusers[1]] - } - } - return rcvuser -} - -func (m *MMClient) SendDirectMessage(toUserId string, msg string) { - m.log.Debugf("SendDirectMessage to %s, msg %s", toUserId, msg) - var channel string - // We don't have a DM with this user yet. - if m.GetChannelId(toUserId+"__"+m.User.Id) == "" && m.GetChannelId(m.User.Id+"__"+toUserId) == "" { - // create DM channel - _, err := m.Client.CreateDirectChannel(toUserId) - if err != nil { - m.log.Debugf("SendDirectMessage to %#v failed: %s", toUserId, err) - } - // update our channels - mmchannels, _ := m.Client.GetChannels("") - m.Channels = mmchannels.Data.(*model.ChannelList) - } - - // build the channel name - if toUserId > m.User.Id { - channel = m.User.Id + "__" + toUserId - } else { - channel = toUserId + "__" + m.User.Id - } - // build & send the message - msg = strings.Replace(msg, "\r", "", -1) - post := &model.Post{ChannelId: m.GetChannelId(channel), Message: msg} - m.Client.CreatePost(post) -} diff --git a/vendor/github.com/bwmarrin/discordgo/discord.go b/vendor/github.com/bwmarrin/discordgo/discord.go index d1cfddf5..4081c65a 100644 --- a/vendor/github.com/bwmarrin/discordgo/discord.go +++ b/vendor/github.com/bwmarrin/discordgo/discord.go @@ -13,13 +13,10 @@ // Package discordgo provides Discord binding for Go package discordgo -import ( - "fmt" - "reflect" -) +import "fmt" // VERSION of Discordgo, follows Symantic Versioning. (http://semver.org/) -const VERSION = "0.13.0" +const VERSION = "0.15.0" // New creates a new Discord session and will automate some startup // tasks if given enough information to do so. Currently you can pass zero @@ -27,6 +24,8 @@ const VERSION = "0.13.0" // 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 "` // 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 @@ -37,11 +36,13 @@ 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, } // If no arguments are passed return the empty Session interface. @@ -122,136 +123,3 @@ func New(args ...interface{}) (s *Session, err error) { return } - -// validateHandler takes an event handler func, and returns the type of event. -// eg. -// Session.validateHandler(func (s *discordgo.Session, m *discordgo.MessageCreate)) -// will return the reflect.Type of *discordgo.MessageCreate -func (s *Session) validateHandler(handler interface{}) reflect.Type { - - handlerType := reflect.TypeOf(handler) - - if handlerType.NumIn() != 2 { - panic("Unable to add event handler, handler must be of the type func(*discordgo.Session, *discordgo.EventType).") - } - - if handlerType.In(0) != reflect.TypeOf(s) { - panic("Unable to add event handler, first argument must be of type *discordgo.Session.") - } - - eventType := handlerType.In(1) - - // Support handlers of type interface{}, this is a special handler, which is triggered on every event. - if eventType.Kind() == reflect.Interface { - eventType = nil - } - - return eventType -} - -// AddHandler allows you to add an event handler that will be fired anytime -// the Discord WSAPI event that matches the interface fires. -// eventToInterface in events.go has a list of all the Discord WSAPI events -// and their respective interface. -// 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() { - - s.initialize() - - eventType := s.validateHandler(handler) - - s.handlersMu.Lock() - defer s.handlersMu.Unlock() - - h := reflect.ValueOf(handler) - - s.handlers[eventType] = append(s.handlers[eventType], h) - - // This must be done as we need a consistent reference to the - // reflected value, otherwise a RemoveHandler method would have - // been nice. - return func() { - s.handlersMu.Lock() - defer s.handlersMu.Unlock() - - handlers := s.handlers[eventType] - for i, v := range handlers { - if h == v { - s.handlers[eventType] = append(handlers[:i], handlers[i+1:]...) - return - } - } - } -} - -// handle calls any handlers that match the event type and any handlers of -// interface{}. -func (s *Session) handle(event interface{}) { - - s.handlersMu.RLock() - defer s.handlersMu.RUnlock() - - if s.handlers == nil { - return - } - - handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)} - - if handlers, ok := s.handlers[nil]; ok { - for _, handler := range handlers { - go handler.Call(handlerParameters) - } - } - - if handlers, ok := s.handlers[reflect.TypeOf(event)]; ok { - for _, handler := range handlers { - go handler.Call(handlerParameters) - } - } -} - -// initialize adds all internal handlers and state tracking handlers. -func (s *Session) initialize() { - - s.log(LogInformational, "called") - - s.handlersMu.Lock() - if s.handlers != nil { - s.handlersMu.Unlock() - return - } - - s.handlers = map[interface{}][]reflect.Value{} - s.handlersMu.Unlock() - - s.AddHandler(s.onReady) - s.AddHandler(s.onResumed) - s.AddHandler(s.onVoiceServerUpdate) - s.AddHandler(s.onVoiceStateUpdate) - s.AddHandler(s.State.onInterface) -} - -// onReady handles the ready event. -func (s *Session) onReady(se *Session, r *Ready) { - - // Store the SessionID within the Session struct. - s.sessionID = r.SessionID - - // Start the heartbeat to keep the connection alive. - go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval) -} - -// onResumed handles the resumed event. -func (s *Session) onResumed(se *Session, r *Resumed) { - - // Start the heartbeat to keep the connection alive. - go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval) -} diff --git a/vendor/github.com/bwmarrin/discordgo/endpoints.go b/vendor/github.com/bwmarrin/discordgo/endpoints.go index 682433d6..f63240ff 100644 --- a/vendor/github.com/bwmarrin/discordgo/endpoints.go +++ b/vendor/github.com/bwmarrin/discordgo/endpoints.go @@ -24,6 +24,7 @@ var ( EndpointChannels = EndpointAPI + "channels/" EndpointUsers = EndpointAPI + "users/" EndpointGateway = EndpointAPI + "gateway" + EndpointWebhooks = EndpointAPI + "webhooks/" EndpointAuth = EndpointAPI + "auth/" EndpointLogin = EndpointAuth + "login" @@ -61,6 +62,7 @@ var ( 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" } @@ -73,6 +75,7 @@ var ( EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" } EndpointGuildIcon = func(gID, hash string) string { return EndpointGuilds + gID + "/icons/" + hash + ".jpg" } EndpointGuildSplash = func(gID, hash string) string { return EndpointGuilds + gID + "/splashes/" + hash + ".jpg" } + 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" } @@ -86,6 +89,21 @@ var ( EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" } EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID } + 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 } + + 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" } + EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID } EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" } diff --git a/vendor/github.com/bwmarrin/discordgo/event.go b/vendor/github.com/bwmarrin/discordgo/event.go new file mode 100644 index 00000000..245f0c1f --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/event.go @@ -0,0 +1,238 @@ +package discordgo + +import "fmt" + +// 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 recievers 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) error { + if _, ok := registeredInterfaceProviders[eh.Type()]; ok { + return fmt.Errorf("event %s already registered", eh.Type()) + } + registeredInterfaceProviders[eh.Type()] = eh + return nil +} + +// 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] { + go eh.eventHandler.Handle(s, i) + } + + if len(s.onceHandlers[t]) > 0 { + for _, eh := range s.onceHandlers[t] { + 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 *Resumed: + s.onResumed(t) + case *VoiceServerUpdate: + go s.onVoiceServerUpdate(t) + case *VoiceStateUpdate: + go s.onVoiceStateUpdate(t) + } + s.State.onInterface(s, i) +} + +// onReady handles the ready event. +func (s *Session) onReady(r *Ready) { + + // Store the SessionID within the Session struct. + s.sessionID = r.SessionID + + // Start the heartbeat to keep the connection alive. + go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval) +} + +// onResumed handles the resumed event. +func (s *Session) onResumed(r *Resumed) { + + // Start the heartbeat to keep the connection alive. + go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval) +} diff --git a/vendor/github.com/bwmarrin/discordgo/eventhandlers.go b/vendor/github.com/bwmarrin/discordgo/eventhandlers.go new file mode 100644 index 00000000..6d78bacd --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/eventhandlers.go @@ -0,0 +1,977 @@ +// 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 ( + channelCreateEventType = "CHANNEL_CREATE" + channelDeleteEventType = "CHANNEL_DELETE" + channelPinsUpdateEventType = "CHANNEL_PINS_UPDATE" + channelUpdateEventType = "CHANNEL_UPDATE" + connectEventType = "__CONNECT__" + disconnectEventType = "__DISCONNECT__" + eventEventType = "__EVENT__" + guildBanAddEventType = "GUILD_BAN_ADD" + guildBanRemoveEventType = "GUILD_BAN_REMOVE" + guildCreateEventType = "GUILD_CREATE" + guildDeleteEventType = "GUILD_DELETE" + guildEmojisUpdateEventType = "GUILD_EMOJIS_UPDATE" + guildIntegrationsUpdateEventType = "GUILD_INTEGRATIONS_UPDATE" + guildMemberAddEventType = "GUILD_MEMBER_ADD" + guildMemberRemoveEventType = "GUILD_MEMBER_REMOVE" + guildMemberUpdateEventType = "GUILD_MEMBER_UPDATE" + guildMembersChunkEventType = "GUILD_MEMBERS_CHUNK" + guildRoleCreateEventType = "GUILD_ROLE_CREATE" + guildRoleDeleteEventType = "GUILD_ROLE_DELETE" + guildRoleUpdateEventType = "GUILD_ROLE_UPDATE" + guildUpdateEventType = "GUILD_UPDATE" + messageAckEventType = "MESSAGE_ACK" + messageCreateEventType = "MESSAGE_CREATE" + messageDeleteEventType = "MESSAGE_DELETE" + messageReactionAddEventType = "MESSAGE_REACTION_ADD" + messageReactionRemoveEventType = "MESSAGE_REACTION_REMOVE" + messageUpdateEventType = "MESSAGE_UPDATE" + presenceUpdateEventType = "PRESENCE_UPDATE" + presencesReplaceEventType = "PRESENCES_REPLACE" + rateLimitEventType = "__RATE_LIMIT__" + readyEventType = "READY" + relationshipAddEventType = "RELATIONSHIP_ADD" + relationshipRemoveEventType = "RELATIONSHIP_REMOVE" + resumedEventType = "RESUMED" + typingStartEventType = "TYPING_START" + userGuildSettingsUpdateEventType = "USER_GUILD_SETTINGS_UPDATE" + userSettingsUpdateEventType = "USER_SETTINGS_UPDATE" + userUpdateEventType = "USER_UPDATE" + voiceServerUpdateEventType = "VOICE_SERVER_UPDATE" + voiceStateUpdateEventType = "VOICE_STATE_UPDATE" +) + +// channelCreateEventHandler is an event handler for ChannelCreate events. +type channelCreateEventHandler func(*Session, *ChannelCreate) + +// Type returns the event type for ChannelCreate events. +func (eh channelCreateEventHandler) Type() string { + return channelCreateEventType +} + +// New returns a new instance of ChannelCreate. +func (eh channelCreateEventHandler) New() interface{} { + return &ChannelCreate{} +} + +// Handle is the handler for ChannelCreate events. +func (eh channelCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ChannelCreate); ok { + eh(s, t) + } +} + +// channelDeleteEventHandler is an event handler for ChannelDelete events. +type channelDeleteEventHandler func(*Session, *ChannelDelete) + +// Type returns the event type for ChannelDelete events. +func (eh channelDeleteEventHandler) Type() string { + return channelDeleteEventType +} + +// New returns a new instance of ChannelDelete. +func (eh channelDeleteEventHandler) New() interface{} { + return &ChannelDelete{} +} + +// Handle is the handler for ChannelDelete events. +func (eh channelDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ChannelDelete); ok { + eh(s, t) + } +} + +// channelPinsUpdateEventHandler is an event handler for ChannelPinsUpdate events. +type channelPinsUpdateEventHandler func(*Session, *ChannelPinsUpdate) + +// Type returns the event type for ChannelPinsUpdate events. +func (eh channelPinsUpdateEventHandler) Type() string { + return channelPinsUpdateEventType +} + +// New returns a new instance of ChannelPinsUpdate. +func (eh channelPinsUpdateEventHandler) New() interface{} { + return &ChannelPinsUpdate{} +} + +// Handle is the handler for ChannelPinsUpdate events. +func (eh channelPinsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ChannelPinsUpdate); ok { + eh(s, t) + } +} + +// channelUpdateEventHandler is an event handler for ChannelUpdate events. +type channelUpdateEventHandler func(*Session, *ChannelUpdate) + +// Type returns the event type for ChannelUpdate events. +func (eh channelUpdateEventHandler) Type() string { + return channelUpdateEventType +} + +// New returns a new instance of ChannelUpdate. +func (eh channelUpdateEventHandler) New() interface{} { + return &ChannelUpdate{} +} + +// Handle is the handler for ChannelUpdate events. +func (eh channelUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*ChannelUpdate); ok { + eh(s, t) + } +} + +// connectEventHandler is an event handler for Connect events. +type connectEventHandler func(*Session, *Connect) + +// Type returns the event type for Connect events. +func (eh connectEventHandler) Type() string { + return connectEventType +} + +// New returns a new instance of Connect. +func (eh connectEventHandler) New() interface{} { + return &Connect{} +} + +// Handle is the handler for Connect events. +func (eh connectEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Connect); ok { + eh(s, t) + } +} + +// disconnectEventHandler is an event handler for Disconnect events. +type disconnectEventHandler func(*Session, *Disconnect) + +// Type returns the event type for Disconnect events. +func (eh disconnectEventHandler) Type() string { + return disconnectEventType +} + +// New returns a new instance of Disconnect. +func (eh disconnectEventHandler) New() interface{} { + return &Disconnect{} +} + +// Handle is the handler for Disconnect events. +func (eh disconnectEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Disconnect); ok { + eh(s, t) + } +} + +// eventEventHandler is an event handler for Event events. +type eventEventHandler func(*Session, *Event) + +// Type returns the event type for Event events. +func (eh eventEventHandler) Type() string { + return eventEventType +} + +// New returns a new instance of Event. +func (eh eventEventHandler) New() interface{} { + return &Event{} +} + +// Handle is the handler for Event events. +func (eh eventEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Event); ok { + eh(s, t) + } +} + +// guildBanAddEventHandler is an event handler for GuildBanAdd events. +type guildBanAddEventHandler func(*Session, *GuildBanAdd) + +// Type returns the event type for GuildBanAdd events. +func (eh guildBanAddEventHandler) Type() string { + return guildBanAddEventType +} + +// New returns a new instance of GuildBanAdd. +func (eh guildBanAddEventHandler) New() interface{} { + return &GuildBanAdd{} +} + +// Handle is the handler for GuildBanAdd events. +func (eh guildBanAddEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildBanAdd); ok { + eh(s, t) + } +} + +// guildBanRemoveEventHandler is an event handler for GuildBanRemove events. +type guildBanRemoveEventHandler func(*Session, *GuildBanRemove) + +// Type returns the event type for GuildBanRemove events. +func (eh guildBanRemoveEventHandler) Type() string { + return guildBanRemoveEventType +} + +// New returns a new instance of GuildBanRemove. +func (eh guildBanRemoveEventHandler) New() interface{} { + return &GuildBanRemove{} +} + +// Handle is the handler for GuildBanRemove events. +func (eh guildBanRemoveEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildBanRemove); ok { + eh(s, t) + } +} + +// guildCreateEventHandler is an event handler for GuildCreate events. +type guildCreateEventHandler func(*Session, *GuildCreate) + +// Type returns the event type for GuildCreate events. +func (eh guildCreateEventHandler) Type() string { + return guildCreateEventType +} + +// New returns a new instance of GuildCreate. +func (eh guildCreateEventHandler) New() interface{} { + return &GuildCreate{} +} + +// Handle is the handler for GuildCreate events. +func (eh guildCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildCreate); ok { + eh(s, t) + } +} + +// guildDeleteEventHandler is an event handler for GuildDelete events. +type guildDeleteEventHandler func(*Session, *GuildDelete) + +// Type returns the event type for GuildDelete events. +func (eh guildDeleteEventHandler) Type() string { + return guildDeleteEventType +} + +// New returns a new instance of GuildDelete. +func (eh guildDeleteEventHandler) New() interface{} { + return &GuildDelete{} +} + +// Handle is the handler for GuildDelete events. +func (eh guildDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildDelete); ok { + eh(s, t) + } +} + +// guildEmojisUpdateEventHandler is an event handler for GuildEmojisUpdate events. +type guildEmojisUpdateEventHandler func(*Session, *GuildEmojisUpdate) + +// Type returns the event type for GuildEmojisUpdate events. +func (eh guildEmojisUpdateEventHandler) Type() string { + return guildEmojisUpdateEventType +} + +// New returns a new instance of GuildEmojisUpdate. +func (eh guildEmojisUpdateEventHandler) New() interface{} { + return &GuildEmojisUpdate{} +} + +// Handle is the handler for GuildEmojisUpdate events. +func (eh guildEmojisUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildEmojisUpdate); ok { + eh(s, t) + } +} + +// guildIntegrationsUpdateEventHandler is an event handler for GuildIntegrationsUpdate events. +type guildIntegrationsUpdateEventHandler func(*Session, *GuildIntegrationsUpdate) + +// Type returns the event type for GuildIntegrationsUpdate events. +func (eh guildIntegrationsUpdateEventHandler) Type() string { + return guildIntegrationsUpdateEventType +} + +// New returns a new instance of GuildIntegrationsUpdate. +func (eh guildIntegrationsUpdateEventHandler) New() interface{} { + return &GuildIntegrationsUpdate{} +} + +// Handle is the handler for GuildIntegrationsUpdate events. +func (eh guildIntegrationsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildIntegrationsUpdate); ok { + eh(s, t) + } +} + +// guildMemberAddEventHandler is an event handler for GuildMemberAdd events. +type guildMemberAddEventHandler func(*Session, *GuildMemberAdd) + +// Type returns the event type for GuildMemberAdd events. +func (eh guildMemberAddEventHandler) Type() string { + return guildMemberAddEventType +} + +// New returns a new instance of GuildMemberAdd. +func (eh guildMemberAddEventHandler) New() interface{} { + return &GuildMemberAdd{} +} + +// Handle is the handler for GuildMemberAdd events. +func (eh guildMemberAddEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildMemberAdd); ok { + eh(s, t) + } +} + +// guildMemberRemoveEventHandler is an event handler for GuildMemberRemove events. +type guildMemberRemoveEventHandler func(*Session, *GuildMemberRemove) + +// Type returns the event type for GuildMemberRemove events. +func (eh guildMemberRemoveEventHandler) Type() string { + return guildMemberRemoveEventType +} + +// New returns a new instance of GuildMemberRemove. +func (eh guildMemberRemoveEventHandler) New() interface{} { + return &GuildMemberRemove{} +} + +// Handle is the handler for GuildMemberRemove events. +func (eh guildMemberRemoveEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildMemberRemove); ok { + eh(s, t) + } +} + +// guildMemberUpdateEventHandler is an event handler for GuildMemberUpdate events. +type guildMemberUpdateEventHandler func(*Session, *GuildMemberUpdate) + +// Type returns the event type for GuildMemberUpdate events. +func (eh guildMemberUpdateEventHandler) Type() string { + return guildMemberUpdateEventType +} + +// New returns a new instance of GuildMemberUpdate. +func (eh guildMemberUpdateEventHandler) New() interface{} { + return &GuildMemberUpdate{} +} + +// Handle is the handler for GuildMemberUpdate events. +func (eh guildMemberUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildMemberUpdate); ok { + eh(s, t) + } +} + +// guildMembersChunkEventHandler is an event handler for GuildMembersChunk events. +type guildMembersChunkEventHandler func(*Session, *GuildMembersChunk) + +// Type returns the event type for GuildMembersChunk events. +func (eh guildMembersChunkEventHandler) Type() string { + return guildMembersChunkEventType +} + +// New returns a new instance of GuildMembersChunk. +func (eh guildMembersChunkEventHandler) New() interface{} { + return &GuildMembersChunk{} +} + +// Handle is the handler for GuildMembersChunk events. +func (eh guildMembersChunkEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildMembersChunk); ok { + eh(s, t) + } +} + +// guildRoleCreateEventHandler is an event handler for GuildRoleCreate events. +type guildRoleCreateEventHandler func(*Session, *GuildRoleCreate) + +// Type returns the event type for GuildRoleCreate events. +func (eh guildRoleCreateEventHandler) Type() string { + return guildRoleCreateEventType +} + +// New returns a new instance of GuildRoleCreate. +func (eh guildRoleCreateEventHandler) New() interface{} { + return &GuildRoleCreate{} +} + +// Handle is the handler for GuildRoleCreate events. +func (eh guildRoleCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildRoleCreate); ok { + eh(s, t) + } +} + +// guildRoleDeleteEventHandler is an event handler for GuildRoleDelete events. +type guildRoleDeleteEventHandler func(*Session, *GuildRoleDelete) + +// Type returns the event type for GuildRoleDelete events. +func (eh guildRoleDeleteEventHandler) Type() string { + return guildRoleDeleteEventType +} + +// New returns a new instance of GuildRoleDelete. +func (eh guildRoleDeleteEventHandler) New() interface{} { + return &GuildRoleDelete{} +} + +// Handle is the handler for GuildRoleDelete events. +func (eh guildRoleDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildRoleDelete); ok { + eh(s, t) + } +} + +// guildRoleUpdateEventHandler is an event handler for GuildRoleUpdate events. +type guildRoleUpdateEventHandler func(*Session, *GuildRoleUpdate) + +// Type returns the event type for GuildRoleUpdate events. +func (eh guildRoleUpdateEventHandler) Type() string { + return guildRoleUpdateEventType +} + +// New returns a new instance of GuildRoleUpdate. +func (eh guildRoleUpdateEventHandler) New() interface{} { + return &GuildRoleUpdate{} +} + +// Handle is the handler for GuildRoleUpdate events. +func (eh guildRoleUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildRoleUpdate); ok { + eh(s, t) + } +} + +// guildUpdateEventHandler is an event handler for GuildUpdate events. +type guildUpdateEventHandler func(*Session, *GuildUpdate) + +// Type returns the event type for GuildUpdate events. +func (eh guildUpdateEventHandler) Type() string { + return guildUpdateEventType +} + +// New returns a new instance of GuildUpdate. +func (eh guildUpdateEventHandler) New() interface{} { + return &GuildUpdate{} +} + +// Handle is the handler for GuildUpdate events. +func (eh guildUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*GuildUpdate); ok { + eh(s, t) + } +} + +// messageAckEventHandler is an event handler for MessageAck events. +type messageAckEventHandler func(*Session, *MessageAck) + +// Type returns the event type for MessageAck events. +func (eh messageAckEventHandler) Type() string { + return messageAckEventType +} + +// New returns a new instance of MessageAck. +func (eh messageAckEventHandler) New() interface{} { + return &MessageAck{} +} + +// Handle is the handler for MessageAck events. +func (eh messageAckEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageAck); ok { + eh(s, t) + } +} + +// messageCreateEventHandler is an event handler for MessageCreate events. +type messageCreateEventHandler func(*Session, *MessageCreate) + +// Type returns the event type for MessageCreate events. +func (eh messageCreateEventHandler) Type() string { + return messageCreateEventType +} + +// New returns a new instance of MessageCreate. +func (eh messageCreateEventHandler) New() interface{} { + return &MessageCreate{} +} + +// Handle is the handler for MessageCreate events. +func (eh messageCreateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageCreate); ok { + eh(s, t) + } +} + +// messageDeleteEventHandler is an event handler for MessageDelete events. +type messageDeleteEventHandler func(*Session, *MessageDelete) + +// Type returns the event type for MessageDelete events. +func (eh messageDeleteEventHandler) Type() string { + return messageDeleteEventType +} + +// New returns a new instance of MessageDelete. +func (eh messageDeleteEventHandler) New() interface{} { + return &MessageDelete{} +} + +// Handle is the handler for MessageDelete events. +func (eh messageDeleteEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageDelete); ok { + eh(s, t) + } +} + +// messageReactionAddEventHandler is an event handler for MessageReactionAdd events. +type messageReactionAddEventHandler func(*Session, *MessageReactionAdd) + +// Type returns the event type for MessageReactionAdd events. +func (eh messageReactionAddEventHandler) Type() string { + return messageReactionAddEventType +} + +// New returns a new instance of MessageReactionAdd. +func (eh messageReactionAddEventHandler) New() interface{} { + return &MessageReactionAdd{} +} + +// Handle is the handler for MessageReactionAdd events. +func (eh messageReactionAddEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageReactionAdd); ok { + eh(s, t) + } +} + +// messageReactionRemoveEventHandler is an event handler for MessageReactionRemove events. +type messageReactionRemoveEventHandler func(*Session, *MessageReactionRemove) + +// Type returns the event type for MessageReactionRemove events. +func (eh messageReactionRemoveEventHandler) Type() string { + return messageReactionRemoveEventType +} + +// New returns a new instance of MessageReactionRemove. +func (eh messageReactionRemoveEventHandler) New() interface{} { + return &MessageReactionRemove{} +} + +// Handle is the handler for MessageReactionRemove events. +func (eh messageReactionRemoveEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageReactionRemove); ok { + eh(s, t) + } +} + +// messageUpdateEventHandler is an event handler for MessageUpdate events. +type messageUpdateEventHandler func(*Session, *MessageUpdate) + +// Type returns the event type for MessageUpdate events. +func (eh messageUpdateEventHandler) Type() string { + return messageUpdateEventType +} + +// New returns a new instance of MessageUpdate. +func (eh messageUpdateEventHandler) New() interface{} { + return &MessageUpdate{} +} + +// Handle is the handler for MessageUpdate events. +func (eh messageUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*MessageUpdate); ok { + eh(s, t) + } +} + +// presenceUpdateEventHandler is an event handler for PresenceUpdate events. +type presenceUpdateEventHandler func(*Session, *PresenceUpdate) + +// Type returns the event type for PresenceUpdate events. +func (eh presenceUpdateEventHandler) Type() string { + return presenceUpdateEventType +} + +// New returns a new instance of PresenceUpdate. +func (eh presenceUpdateEventHandler) New() interface{} { + return &PresenceUpdate{} +} + +// Handle is the handler for PresenceUpdate events. +func (eh presenceUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*PresenceUpdate); ok { + eh(s, t) + } +} + +// presencesReplaceEventHandler is an event handler for PresencesReplace events. +type presencesReplaceEventHandler func(*Session, *PresencesReplace) + +// Type returns the event type for PresencesReplace events. +func (eh presencesReplaceEventHandler) Type() string { + return presencesReplaceEventType +} + +// New returns a new instance of PresencesReplace. +func (eh presencesReplaceEventHandler) New() interface{} { + return &PresencesReplace{} +} + +// Handle is the handler for PresencesReplace events. +func (eh presencesReplaceEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*PresencesReplace); ok { + eh(s, t) + } +} + +// rateLimitEventHandler is an event handler for RateLimit events. +type rateLimitEventHandler func(*Session, *RateLimit) + +// Type returns the event type for RateLimit events. +func (eh rateLimitEventHandler) Type() string { + return rateLimitEventType +} + +// New returns a new instance of RateLimit. +func (eh rateLimitEventHandler) New() interface{} { + return &RateLimit{} +} + +// Handle is the handler for RateLimit events. +func (eh rateLimitEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*RateLimit); ok { + eh(s, t) + } +} + +// readyEventHandler is an event handler for Ready events. +type readyEventHandler func(*Session, *Ready) + +// Type returns the event type for Ready events. +func (eh readyEventHandler) Type() string { + return readyEventType +} + +// New returns a new instance of Ready. +func (eh readyEventHandler) New() interface{} { + return &Ready{} +} + +// Handle is the handler for Ready events. +func (eh readyEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Ready); ok { + eh(s, t) + } +} + +// relationshipAddEventHandler is an event handler for RelationshipAdd events. +type relationshipAddEventHandler func(*Session, *RelationshipAdd) + +// Type returns the event type for RelationshipAdd events. +func (eh relationshipAddEventHandler) Type() string { + return relationshipAddEventType +} + +// New returns a new instance of RelationshipAdd. +func (eh relationshipAddEventHandler) New() interface{} { + return &RelationshipAdd{} +} + +// Handle is the handler for RelationshipAdd events. +func (eh relationshipAddEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*RelationshipAdd); ok { + eh(s, t) + } +} + +// relationshipRemoveEventHandler is an event handler for RelationshipRemove events. +type relationshipRemoveEventHandler func(*Session, *RelationshipRemove) + +// Type returns the event type for RelationshipRemove events. +func (eh relationshipRemoveEventHandler) Type() string { + return relationshipRemoveEventType +} + +// New returns a new instance of RelationshipRemove. +func (eh relationshipRemoveEventHandler) New() interface{} { + return &RelationshipRemove{} +} + +// Handle is the handler for RelationshipRemove events. +func (eh relationshipRemoveEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*RelationshipRemove); ok { + eh(s, t) + } +} + +// resumedEventHandler is an event handler for Resumed events. +type resumedEventHandler func(*Session, *Resumed) + +// Type returns the event type for Resumed events. +func (eh resumedEventHandler) Type() string { + return resumedEventType +} + +// New returns a new instance of Resumed. +func (eh resumedEventHandler) New() interface{} { + return &Resumed{} +} + +// Handle is the handler for Resumed events. +func (eh resumedEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*Resumed); ok { + eh(s, t) + } +} + +// typingStartEventHandler is an event handler for TypingStart events. +type typingStartEventHandler func(*Session, *TypingStart) + +// Type returns the event type for TypingStart events. +func (eh typingStartEventHandler) Type() string { + return typingStartEventType +} + +// New returns a new instance of TypingStart. +func (eh typingStartEventHandler) New() interface{} { + return &TypingStart{} +} + +// Handle is the handler for TypingStart events. +func (eh typingStartEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*TypingStart); ok { + eh(s, t) + } +} + +// userGuildSettingsUpdateEventHandler is an event handler for UserGuildSettingsUpdate events. +type userGuildSettingsUpdateEventHandler func(*Session, *UserGuildSettingsUpdate) + +// Type returns the event type for UserGuildSettingsUpdate events. +func (eh userGuildSettingsUpdateEventHandler) Type() string { + return userGuildSettingsUpdateEventType +} + +// New returns a new instance of UserGuildSettingsUpdate. +func (eh userGuildSettingsUpdateEventHandler) New() interface{} { + return &UserGuildSettingsUpdate{} +} + +// Handle is the handler for UserGuildSettingsUpdate events. +func (eh userGuildSettingsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*UserGuildSettingsUpdate); ok { + eh(s, t) + } +} + +// userSettingsUpdateEventHandler is an event handler for UserSettingsUpdate events. +type userSettingsUpdateEventHandler func(*Session, *UserSettingsUpdate) + +// Type returns the event type for UserSettingsUpdate events. +func (eh userSettingsUpdateEventHandler) Type() string { + return userSettingsUpdateEventType +} + +// New returns a new instance of UserSettingsUpdate. +func (eh userSettingsUpdateEventHandler) New() interface{} { + return &UserSettingsUpdate{} +} + +// Handle is the handler for UserSettingsUpdate events. +func (eh userSettingsUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*UserSettingsUpdate); ok { + eh(s, t) + } +} + +// userUpdateEventHandler is an event handler for UserUpdate events. +type userUpdateEventHandler func(*Session, *UserUpdate) + +// Type returns the event type for UserUpdate events. +func (eh userUpdateEventHandler) Type() string { + return userUpdateEventType +} + +// New returns a new instance of UserUpdate. +func (eh userUpdateEventHandler) New() interface{} { + return &UserUpdate{} +} + +// Handle is the handler for UserUpdate events. +func (eh userUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*UserUpdate); ok { + eh(s, t) + } +} + +// voiceServerUpdateEventHandler is an event handler for VoiceServerUpdate events. +type voiceServerUpdateEventHandler func(*Session, *VoiceServerUpdate) + +// Type returns the event type for VoiceServerUpdate events. +func (eh voiceServerUpdateEventHandler) Type() string { + return voiceServerUpdateEventType +} + +// New returns a new instance of VoiceServerUpdate. +func (eh voiceServerUpdateEventHandler) New() interface{} { + return &VoiceServerUpdate{} +} + +// Handle is the handler for VoiceServerUpdate events. +func (eh voiceServerUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*VoiceServerUpdate); ok { + eh(s, t) + } +} + +// voiceStateUpdateEventHandler is an event handler for VoiceStateUpdate events. +type voiceStateUpdateEventHandler func(*Session, *VoiceStateUpdate) + +// Type returns the event type for VoiceStateUpdate events. +func (eh voiceStateUpdateEventHandler) Type() string { + return voiceStateUpdateEventType +} + +// New returns a new instance of VoiceStateUpdate. +func (eh voiceStateUpdateEventHandler) New() interface{} { + return &VoiceStateUpdate{} +} + +// Handle is the handler for VoiceStateUpdate events. +func (eh voiceStateUpdateEventHandler) Handle(s *Session, i interface{}) { + if t, ok := i.(*VoiceStateUpdate); ok { + eh(s, t) + } +} + +func handlerForInterface(handler interface{}) EventHandler { + switch v := handler.(type) { + case func(*Session, interface{}): + return interfaceEventHandler(v) + case func(*Session, *ChannelCreate): + return channelCreateEventHandler(v) + case func(*Session, *ChannelDelete): + return channelDeleteEventHandler(v) + case func(*Session, *ChannelPinsUpdate): + return channelPinsUpdateEventHandler(v) + case func(*Session, *ChannelUpdate): + return channelUpdateEventHandler(v) + case func(*Session, *Connect): + return connectEventHandler(v) + case func(*Session, *Disconnect): + return disconnectEventHandler(v) + case func(*Session, *Event): + return eventEventHandler(v) + case func(*Session, *GuildBanAdd): + return guildBanAddEventHandler(v) + case func(*Session, *GuildBanRemove): + return guildBanRemoveEventHandler(v) + case func(*Session, *GuildCreate): + return guildCreateEventHandler(v) + case func(*Session, *GuildDelete): + return guildDeleteEventHandler(v) + case func(*Session, *GuildEmojisUpdate): + return guildEmojisUpdateEventHandler(v) + case func(*Session, *GuildIntegrationsUpdate): + return guildIntegrationsUpdateEventHandler(v) + case func(*Session, *GuildMemberAdd): + return guildMemberAddEventHandler(v) + case func(*Session, *GuildMemberRemove): + return guildMemberRemoveEventHandler(v) + case func(*Session, *GuildMemberUpdate): + return guildMemberUpdateEventHandler(v) + case func(*Session, *GuildMembersChunk): + return guildMembersChunkEventHandler(v) + case func(*Session, *GuildRoleCreate): + return guildRoleCreateEventHandler(v) + case func(*Session, *GuildRoleDelete): + return guildRoleDeleteEventHandler(v) + case func(*Session, *GuildRoleUpdate): + return guildRoleUpdateEventHandler(v) + case func(*Session, *GuildUpdate): + return guildUpdateEventHandler(v) + case func(*Session, *MessageAck): + return messageAckEventHandler(v) + case func(*Session, *MessageCreate): + return messageCreateEventHandler(v) + case func(*Session, *MessageDelete): + return messageDeleteEventHandler(v) + case func(*Session, *MessageReactionAdd): + return messageReactionAddEventHandler(v) + case func(*Session, *MessageReactionRemove): + return messageReactionRemoveEventHandler(v) + case func(*Session, *MessageUpdate): + return messageUpdateEventHandler(v) + case func(*Session, *PresenceUpdate): + return presenceUpdateEventHandler(v) + case func(*Session, *PresencesReplace): + return presencesReplaceEventHandler(v) + case func(*Session, *RateLimit): + return rateLimitEventHandler(v) + case func(*Session, *Ready): + return readyEventHandler(v) + case func(*Session, *RelationshipAdd): + return relationshipAddEventHandler(v) + case func(*Session, *RelationshipRemove): + return relationshipRemoveEventHandler(v) + case func(*Session, *Resumed): + return resumedEventHandler(v) + case func(*Session, *TypingStart): + return typingStartEventHandler(v) + case func(*Session, *UserGuildSettingsUpdate): + return userGuildSettingsUpdateEventHandler(v) + case func(*Session, *UserSettingsUpdate): + return userSettingsUpdateEventHandler(v) + case func(*Session, *UserUpdate): + return userUpdateEventHandler(v) + case func(*Session, *VoiceServerUpdate): + return voiceServerUpdateEventHandler(v) + case func(*Session, *VoiceStateUpdate): + return voiceStateUpdateEventHandler(v) + } + + return nil +} +func init() { + registerInterfaceProvider(channelCreateEventHandler(nil)) + registerInterfaceProvider(channelDeleteEventHandler(nil)) + registerInterfaceProvider(channelPinsUpdateEventHandler(nil)) + registerInterfaceProvider(channelUpdateEventHandler(nil)) + registerInterfaceProvider(guildBanAddEventHandler(nil)) + registerInterfaceProvider(guildBanRemoveEventHandler(nil)) + registerInterfaceProvider(guildCreateEventHandler(nil)) + registerInterfaceProvider(guildDeleteEventHandler(nil)) + registerInterfaceProvider(guildEmojisUpdateEventHandler(nil)) + registerInterfaceProvider(guildIntegrationsUpdateEventHandler(nil)) + registerInterfaceProvider(guildMemberAddEventHandler(nil)) + registerInterfaceProvider(guildMemberRemoveEventHandler(nil)) + registerInterfaceProvider(guildMemberUpdateEventHandler(nil)) + registerInterfaceProvider(guildMembersChunkEventHandler(nil)) + registerInterfaceProvider(guildRoleCreateEventHandler(nil)) + registerInterfaceProvider(guildRoleDeleteEventHandler(nil)) + registerInterfaceProvider(guildRoleUpdateEventHandler(nil)) + registerInterfaceProvider(guildUpdateEventHandler(nil)) + registerInterfaceProvider(messageAckEventHandler(nil)) + registerInterfaceProvider(messageCreateEventHandler(nil)) + registerInterfaceProvider(messageDeleteEventHandler(nil)) + registerInterfaceProvider(messageReactionAddEventHandler(nil)) + registerInterfaceProvider(messageReactionRemoveEventHandler(nil)) + registerInterfaceProvider(messageUpdateEventHandler(nil)) + registerInterfaceProvider(presenceUpdateEventHandler(nil)) + registerInterfaceProvider(presencesReplaceEventHandler(nil)) + registerInterfaceProvider(readyEventHandler(nil)) + registerInterfaceProvider(relationshipAddEventHandler(nil)) + registerInterfaceProvider(relationshipRemoveEventHandler(nil)) + registerInterfaceProvider(resumedEventHandler(nil)) + registerInterfaceProvider(typingStartEventHandler(nil)) + registerInterfaceProvider(userGuildSettingsUpdateEventHandler(nil)) + registerInterfaceProvider(userSettingsUpdateEventHandler(nil)) + registerInterfaceProvider(userUpdateEventHandler(nil)) + registerInterfaceProvider(voiceServerUpdateEventHandler(nil)) + registerInterfaceProvider(voiceStateUpdateEventHandler(nil)) +} diff --git a/vendor/github.com/bwmarrin/discordgo/events.go b/vendor/github.com/bwmarrin/discordgo/events.go index 72aabf67..19c11bda 100644 --- a/vendor/github.com/bwmarrin/discordgo/events.go +++ b/vendor/github.com/bwmarrin/discordgo/events.go @@ -1,159 +1,238 @@ package discordgo -// eventToInterface is a mapping of Discord WSAPI events to their -// DiscordGo event container. -// Each Discord WSAPI event maps to a unique interface. -// Use Session.AddHandler with one of these types to handle that -// type of event. -// eg: -// Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { -// }) -// -// or: -// Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) { -// }) -var eventToInterface = map[string]interface{}{ - "CHANNEL_CREATE": ChannelCreate{}, - "CHANNEL_UPDATE": ChannelUpdate{}, - "CHANNEL_DELETE": ChannelDelete{}, - "GUILD_CREATE": GuildCreate{}, - "GUILD_UPDATE": GuildUpdate{}, - "GUILD_DELETE": GuildDelete{}, - "GUILD_BAN_ADD": GuildBanAdd{}, - "GUILD_BAN_REMOVE": GuildBanRemove{}, - "GUILD_MEMBER_ADD": GuildMemberAdd{}, - "GUILD_MEMBER_UPDATE": GuildMemberUpdate{}, - "GUILD_MEMBER_REMOVE": GuildMemberRemove{}, - "GUILD_ROLE_CREATE": GuildRoleCreate{}, - "GUILD_ROLE_UPDATE": GuildRoleUpdate{}, - "GUILD_ROLE_DELETE": GuildRoleDelete{}, - "GUILD_INTEGRATIONS_UPDATE": GuildIntegrationsUpdate{}, - "GUILD_EMOJIS_UPDATE": GuildEmojisUpdate{}, - "MESSAGE_ACK": MessageAck{}, - "MESSAGE_CREATE": MessageCreate{}, - "MESSAGE_UPDATE": MessageUpdate{}, - "MESSAGE_DELETE": MessageDelete{}, - "PRESENCE_UPDATE": PresenceUpdate{}, - "PRESENCES_REPLACE": PresencesReplace{}, - "READY": Ready{}, - "USER_UPDATE": UserUpdate{}, - "USER_SETTINGS_UPDATE": UserSettingsUpdate{}, - "USER_GUILD_SETTINGS_UPDATE": UserGuildSettingsUpdate{}, - "TYPING_START": TypingStart{}, - "VOICE_SERVER_UPDATE": VoiceServerUpdate{}, - "VOICE_STATE_UPDATE": VoiceStateUpdate{}, - "RESUMED": Resumed{}, -} - -// Connect is an empty struct for an event. +import ( + "encoding/json" + "time" +) + +// 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 an empty struct for an event. +// Disconnect is the data for a Disconnect event. +// This is a sythetic event and is not dispatched by Discord. type Disconnect struct{} -// RateLimit is a struct for the RateLimited event +// 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 } -// MessageCreate is a wrapper struct for an event. -type MessageCreate struct { - *Message +// Event provides a basic initial struct for all websocket events. +type Event struct { + Operation int `json:"op"` + Sequence int `json:"s"` + Type string `json:"t"` + RawData json.RawMessage `json:"d"` + // Struct contains one of the other types in this file. + Struct interface{} `json:"-"` } -// MessageUpdate is a wrapper struct for an event. -type MessageUpdate struct { - *Message -} +// A Ready stores all data for the websocket READY event. +type Ready struct { + Version int `json:"v"` + SessionID string `json:"session_id"` + HeartbeatInterval time.Duration `json:"heartbeat_interval"` + User *User `json:"user"` + ReadState []*ReadState `json:"read_state"` + PrivateChannels []*Channel `json:"private_channels"` + Guilds []*Guild `json:"guilds"` -// MessageDelete is a wrapper struct for an event. -type MessageDelete struct { - *Message + // Undocumented fields + Settings *Settings `json:"user_settings"` + UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"` + Relationships []*Relationship `json:"relationships"` + Presences []*Presence `json:"presences"` } -// ChannelCreate is a wrapper struct for an event. +// ChannelCreate is the data for a ChannelCreate event. type ChannelCreate struct { *Channel } -// ChannelUpdate is a wrapper struct for an event. +// ChannelUpdate is the data for a ChannelUpdate event. type ChannelUpdate struct { *Channel } -// ChannelDelete is a wrapper struct for an event. +// ChannelDelete is the data for a ChannelDelete event. type ChannelDelete struct { *Channel } -// GuildCreate is a wrapper struct for an event. +// 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 a wrapper struct for an event. +// GuildUpdate is the data for a GuildUpdate event. type GuildUpdate struct { *Guild } -// GuildDelete is a wrapper struct for an event. +// GuildDelete is the data for a GuildDelete event. type GuildDelete struct { *Guild } -// GuildBanAdd is a wrapper struct for an event. +// GuildBanAdd is the data for a GuildBanAdd event. type GuildBanAdd struct { - *GuildBan + User *User `json:"user"` + GuildID string `json:"guild_id"` } -// GuildBanRemove is a wrapper struct for an event. +// GuildBanRemove is the data for a GuildBanRemove event. type GuildBanRemove struct { - *GuildBan + User *User `json:"user"` + GuildID string `json:"guild_id"` } -// GuildMemberAdd is a wrapper struct for an event. +// GuildMemberAdd is the data for a GuildMemberAdd event. type GuildMemberAdd struct { *Member } -// GuildMemberUpdate is a wrapper struct for an event. +// GuildMemberUpdate is the data for a GuildMemberUpdate event. type GuildMemberUpdate struct { *Member } -// GuildMemberRemove is a wrapper struct for an event. +// GuildMemberRemove is the data for a GuildMemberRemove event. type GuildMemberRemove struct { *Member } -// GuildRoleCreate is a wrapper struct for an event. +// GuildRoleCreate is the data for a GuildRoleCreate event. type GuildRoleCreate struct { *GuildRole } -// GuildRoleUpdate is a wrapper struct for an event. +// GuildRoleUpdate is the data for a GuildRoleUpdate event. type GuildRoleUpdate struct { *GuildRole } -// PresencesReplace is an array of Presences for an event. +// 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 +} + +// PresencesReplace is the data for a PresencesReplace event. type PresencesReplace []*Presence -// VoiceStateUpdate is a wrapper struct for an event. -type VoiceStateUpdate struct { - *VoiceState +// 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 { + HeartbeatInterval time.Duration `json:"heartbeat_interval"` + Trace []string `json:"_trace"` } -// UserUpdate is a wrapper struct for an event. +// 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 a map for an event. +// UserSettingsUpdate is the data for a UserSettingsUpdate event. type UserSettingsUpdate map[string]interface{} -// UserGuildSettingsUpdate is a map for an event. +// UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event. type UserGuildSettingsUpdate struct { *UserGuildSettings } + +// 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 +} diff --git a/vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go b/vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go index cc61301c..ff5e5214 100644 --- a/vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go +++ b/vendor/github.com/bwmarrin/discordgo/examples/airhorn/main.go @@ -13,7 +13,7 @@ import ( ) func init() { - flag.StringVar(&token, "t", "", "Account Token") + flag.StringVar(&token, "t", "", "Bot Token") flag.Parse() } @@ -34,8 +34,8 @@ func main() { return } - // Create a new Discord session using the provided token. - dg, err := discordgo.New(token) + // 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 @@ -102,7 +102,7 @@ func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { // 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 != nil { + if event.Guild.Unavailable { return } @@ -131,6 +131,10 @@ func loadSound() error { // If this is the end of the file, just return. if err == io.EOF || err == io.ErrUnexpectedEOF { + file.Close() + if err != nil { + return err + } return nil } diff --git a/vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go b/vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go index c3861ac0..0191bb06 100644 --- a/vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go +++ b/vendor/github.com/bwmarrin/discordgo/examples/new_basic/main.go @@ -10,24 +10,19 @@ import ( // Variables used for command line parameters var ( - Email string - Password string - Token string + Token string ) func init() { - flag.StringVar(&Email, "e", "", "Account Email") - flag.StringVar(&Password, "p", "", "Account Password") - flag.StringVar(&Token, "t", "", "Account Token") + flag.StringVar(&Token, "t", "", "Bot Token") flag.Parse() } func main() { - // Create a new Discord session using the provided login information. - // Use discordgo.New(Token) to just use a token for login. - dg, err := discordgo.New(Email, Password, Token) + // 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 diff --git a/vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go b/vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go index e6893ca1..2edd957e 100644 --- a/vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go +++ b/vendor/github.com/bwmarrin/discordgo/examples/pingpong/main.go @@ -9,24 +9,20 @@ import ( // Variables used for command line parameters var ( - Email string - Password string - Token string - BotID string + Token string + BotID string ) func init() { - flag.StringVar(&Email, "e", "", "Account Email") - flag.StringVar(&Password, "p", "", "Account Password") - flag.StringVar(&Token, "t", "", "Account Token") + flag.StringVar(&Token, "t", "", "Bot Token") flag.Parse() } func main() { - // Create a new Discord session using the provided login information. - dg, err := discordgo.New(Email, Password, Token) + // 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 diff --git a/vendor/github.com/bwmarrin/discordgo/message.go b/vendor/github.com/bwmarrin/discordgo/message.go index 8966c161..d7abda60 100644 --- a/vendor/github.com/bwmarrin/discordgo/message.go +++ b/vendor/github.com/bwmarrin/discordgo/message.go @@ -19,8 +19,8 @@ type Message struct { ID string `json:"id"` ChannelID string `json:"channel_id"` Content string `json:"content"` - Timestamp string `json:"timestamp"` - EditedTimestamp string `json:"edited_timestamp"` + Timestamp Timestamp `json:"timestamp"` + EditedTimestamp Timestamp `json:"edited_timestamp"` MentionRoles []string `json:"mention_roles"` Tts bool `json:"tts"` MentionEveryone bool `json:"mention_everyone"` @@ -28,6 +28,7 @@ type Message struct { Attachments []*MessageAttachment `json:"attachments"` Embeds []*MessageEmbed `json:"embeds"` Mentions []*User `json:"mentions"` + Reactions []*MessageReactions `json:"reactions"` } // A MessageAttachment stores data for message attachments. @@ -41,31 +42,80 @@ type MessageAttachment struct { 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"` - Type string `json:"type"` - Title string `json:"title"` - Description string `json:"description"` - Thumbnail *struct { - URL string `json:"url"` - ProxyURL string `json:"proxy_url"` - Width int `json:"width"` - Height int `json:"height"` - } `json:"thumbnail"` - Provider *struct { - URL string `json:"url"` - Name string `json:"name"` - } `json:"provider"` - Author *struct { - URL string `json:"url"` - Name string `json:"name"` - } `json:"author"` - Video *struct { - URL string `json:"url"` - Width int `json:"width"` - Height int `json:"height"` - } `json:"video"` + 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 @ mentions with the diff --git a/vendor/github.com/bwmarrin/discordgo/oauth2.go b/vendor/github.com/bwmarrin/discordgo/oauth2.go index de2848db..14ba6bbe 100644 --- a/vendor/github.com/bwmarrin/discordgo/oauth2.go +++ b/vendor/github.com/bwmarrin/discordgo/oauth2.go @@ -21,13 +21,14 @@ type Application struct { Icon string `json:"icon,omitempty"` Secret string `json:"secret,omitempty"` RedirectURIs *[]string `json:"redirect_uris,omitempty"` + Owner *User `json:"owner"` } // 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.Request("GET", EndpointApplication(appID), nil) + body, err := s.RequestWithBucketID("GET", EndpointApplication(appID), nil, EndpointApplication("")) if err != nil { return } @@ -39,7 +40,7 @@ func (s *Session) Application(appID string) (st *Application, err error) { // Applications returns all applications for the authenticated user func (s *Session) Applications() (st []*Application, err error) { - body, err := s.Request("GET", EndpointApplications, nil) + body, err := s.RequestWithBucketID("GET", EndpointApplications, nil, EndpointApplications) if err != nil { return } @@ -59,7 +60,7 @@ func (s *Session) ApplicationCreate(ap *Application) (st *Application, err error RedirectURIs *[]string `json:"redirect_uris,omitempty"` }{ap.Name, ap.Description, ap.RedirectURIs} - body, err := s.Request("POST", EndpointApplications, data) + body, err := s.RequestWithBucketID("POST", EndpointApplications, data, EndpointApplications) if err != nil { return } @@ -78,7 +79,7 @@ func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Applicat RedirectURIs *[]string `json:"redirect_uris,omitempty"` }{ap.Name, ap.Description, ap.RedirectURIs} - body, err := s.Request("PUT", EndpointApplication(appID), data) + body, err := s.RequestWithBucketID("PUT", EndpointApplication(appID), data, EndpointApplication("")) if err != nil { return } @@ -91,7 +92,7 @@ func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Applicat // appID : The ID of an Application func (s *Session) ApplicationDelete(appID string) (err error) { - _, err = s.Request("DELETE", EndpointApplication(appID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointApplication(appID), nil, EndpointApplication("")) if err != nil { return } @@ -110,7 +111,7 @@ func (s *Session) ApplicationDelete(appID string) (err error) { // 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.Request("POST", EndpointApplicationsBot(appID), nil) + body, err := s.RequestWithBucketID("POST", EndpointApplicationsBot(appID), nil, EndpointApplicationsBot("")) if err != nil { return } diff --git a/vendor/github.com/bwmarrin/discordgo/ratelimit.go b/vendor/github.com/bwmarrin/discordgo/ratelimit.go new file mode 100644 index 00000000..bc320f0e --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/ratelimit.go @@ -0,0 +1,157 @@ +package discordgo + +import ( + "net/http" + "strconv" + "sync" + "time" +) + +// RateLimiter holds all ratelimit buckets +type RateLimiter struct { + sync.Mutex + global *Bucket + buckets map[string]*Bucket + globalRateLimit time.Duration +} + +// NewRatelimiter returns a new RateLimiter +func NewRatelimiter() *RateLimiter { + + return &RateLimiter{ + buckets: make(map[string]*Bucket), + global: &Bucket{Key: "global"}, + } +} + +// 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, + } + + r.buckets[key] = b + return b +} + +// LockBucket Locks until a request can be made +func (r *RateLimiter) LockBucket(bucketID string) *Bucket { + + b := r.getBucket(bucketID) + + b.Lock() + + // 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 < 1 && b.reset.After(time.Now()) { + time.Sleep(b.reset.Sub(time.Now())) + + } + + // Check for global ratelimits + r.global.Lock() + r.global.Unlock() + + 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 *Bucket +} + +// 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() + 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") + + // If it's global just keep the main ratelimit mutex locked + if global != "" { + parsedAfter, err := strconv.Atoi(retryAfter) + if err != nil { + return err + } + + // Lock it in a new goroutine so that this isn't a blocking call + go func() { + // Make sure if several requests were waiting we don't sleep for n * retry-after + // where n is the amount of requests that were going on + sleepTo := time.Now().Add(time.Duration(parsedAfter) * time.Millisecond) + + b.global.Lock() + + sleepDuration := sleepTo.Sub(time.Now()) + if sleepDuration > 0 { + time.Sleep(sleepDuration) + } + + b.global.Unlock() + }() + + return nil + } + + // Update reset time if either retry after or reset headers are present + // Prefer retryafter because it's more accurate with time sync and whatnot + if retryAfter != "" { + parsedAfter, err := strconv.ParseInt(retryAfter, 10, 64) + if err != nil { + return err + } + b.reset = time.Now().Add(time.Duration(parsedAfter) * time.Millisecond) + + } 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 +} diff --git a/vendor/github.com/bwmarrin/discordgo/restapi.go b/vendor/github.com/bwmarrin/discordgo/restapi.go index 337a7f82..5389c860 100644 --- a/vendor/github.com/bwmarrin/discordgo/restapi.go +++ b/vendor/github.com/bwmarrin/discordgo/restapi.go @@ -26,17 +26,19 @@ import ( "net/url" "strconv" "strings" - "sync" "time" ) // ErrJSONUnmarshal is returned for JSON Unmarshall errors. var ErrJSONUnmarshal = errors.New("json unmarshal") -// Request makes a (GET/POST/...) Requests to Discord REST API with JSON data. -// All the other Discord REST Calls in this file use this function. +// Request is the same as RequestWithBucketID but the bucket id is the same as the urlStr func (s *Session) Request(method, urlStr string, data interface{}) (response []byte, err error) { + return s.RequestWithBucketID(method, urlStr, data, strings.SplitN(urlStr, "?", 2)[0]) +} +// RequestWithBucketID makes a (GET/POST/...) Requests to Discord REST API with JSON data. +func (s *Session) RequestWithBucketID(method, urlStr string, data interface{}, bucketID string) (response []byte, err error) { var body []byte if data != nil { body, err = json.Marshal(data) @@ -45,34 +47,19 @@ func (s *Session) Request(method, urlStr string, data interface{}) (response []b } } - return s.request(method, urlStr, "application/json", body) + return s.request(method, urlStr, "application/json", body, bucketID, 0) } // request makes a (GET/POST/...) Requests to Discord REST API. -func (s *Session) request(method, urlStr, contentType string, b []byte) (response []byte, err error) { - - // rate limit mutex for this url - // TODO: review for performance improvements - // ideally we just ignore endpoints that we've never - // received a 429 on. But this simple method works and - // is a lot less complex :) It also might even be more - // performat due to less checks and maps. - var mu *sync.Mutex - - s.rateLimit.Lock() - if s.rateLimit.url == nil { - s.rateLimit.url = make(map[string]*sync.Mutex) +// Sequence is the sequence number, if it fails with a 502 it will +// retry with sequence+1 until it either succeeds or sequence >= session.MaxRestRetries +func (s *Session) request(method, urlStr, contentType string, b []byte, bucketID string, sequence int) (response []byte, err error) { + if bucketID == "" { + bucketID = strings.SplitN(urlStr, "?", 2)[0] } - bu := strings.Split(urlStr, "?") - mu, _ = s.rateLimit.url[bu[0]] - if mu == nil { - mu = new(sync.Mutex) - s.rateLimit.url[urlStr] = mu - } - s.rateLimit.Unlock() + bucket := s.ratelimiter.LockBucket(bucketID) - mu.Lock() // lock this URL for ratelimiting if s.Debug { log.Printf("API REQUEST %8s :: %s\n", method, urlStr) log.Printf("API REQUEST PAYLOAD :: [%s]\n", string(b)) @@ -80,6 +67,7 @@ func (s *Session) request(method, urlStr, contentType string, b []byte) (respons req, err := http.NewRequest(method, urlStr, bytes.NewBuffer(b)) if err != nil { + bucket.Release(nil) return } @@ -102,8 +90,8 @@ func (s *Session) request(method, urlStr, contentType string, b []byte) (respons client := &http.Client{Timeout: (20 * time.Second)} resp, err := client.Do(req) - mu.Unlock() // unlock ratelimit mutex if err != nil { + bucket.Release(nil) return } defer func() { @@ -113,6 +101,11 @@ func (s *Session) request(method, urlStr, contentType string, b []byte) (respons } }() + err = bucket.Release(resp.Header) + if err != nil { + return + } + response, err = ioutil.ReadAll(resp.Body) if err != nil { return @@ -135,29 +128,34 @@ func (s *Session) request(method, urlStr, contentType string, b []byte) (respons // TODO check for 401 response, invalidate token if we get one. - case 429: // TOO MANY REQUESTS - Rate limiting + case http.StatusBadGateway: + // Retry sending request if possible + if sequence < s.MaxRestRetries { - mu.Lock() // lock URL ratelimit mutex + s.log(LogInformational, "%s Failed (%s), Retrying...", urlStr, resp.Status) + response, err = s.request(method, urlStr, contentType, b, bucketID, sequence+1) + } else { + err = fmt.Errorf("Exceeded Max retries HTTP %s, %s", resp.Status, response) + } + case 429: // TOO MANY REQUESTS - Rate limiting rl := TooManyRequests{} err = json.Unmarshal(response, &rl) if err != nil { s.log(LogError, "rate limit unmarshal error, %s", err) - mu.Unlock() return } s.log(LogInformational, "Rate Limiting %s, retry in %d", urlStr, rl.RetryAfter) - s.handle(RateLimit{TooManyRequests: &rl, URL: urlStr}) + s.handleEvent(rateLimitEventType, RateLimit{TooManyRequests: &rl, URL: urlStr}) time.Sleep(rl.RetryAfter * time.Millisecond) // we can make the above smarter - // this method can cause longer delays then required + // this method can cause longer delays than required - mu.Unlock() // we have to unlock here - response, err = s.request(method, urlStr, contentType, b) + response, err = s.request(method, urlStr, contentType, b, bucketID, sequence) default: // Error condition - err = fmt.Errorf("HTTP %s, %s", resp.Status, response) + err = newRestError(req, resp, response) } return @@ -184,7 +182,7 @@ func (s *Session) Login(email, password string) (err error) { Password string `json:"password"` }{email, password} - response, err := s.Request("POST", EndpointLogin, data) + response, err := s.RequestWithBucketID("POST", EndpointLogin, data, EndpointLogin) if err != nil { return } @@ -211,7 +209,7 @@ func (s *Session) Register(username string) (token string, err error) { Username string `json:"username"` }{username} - response, err := s.Request("POST", EndpointRegister, data) + response, err := s.RequestWithBucketID("POST", EndpointRegister, data, EndpointRegister) if err != nil { return } @@ -245,7 +243,7 @@ func (s *Session) Logout() (err error) { Token string `json:"token"` }{s.Token} - _, err = s.Request("POST", EndpointLogout, data) + _, err = s.RequestWithBucketID("POST", EndpointLogout, data, EndpointLogout) return } @@ -257,7 +255,7 @@ func (s *Session) Logout() (err error) { // userID : A user ID or "@me" which is a shortcut of current user ID func (s *Session) User(userID string) (st *User, err error) { - body, err := s.Request("GET", EndpointUser(userID), nil) + body, err := s.RequestWithBucketID("GET", EndpointUser(userID), nil, EndpointUsers) if err != nil { return } @@ -274,7 +272,7 @@ func (s *Session) UserAvatar(userID string) (img image.Image, err error) { return } - body, err := s.Request("GET", EndpointUserAvatar(userID, u.Avatar), nil) + body, err := s.RequestWithBucketID("GET", EndpointUserAvatar(userID, u.Avatar), nil, EndpointUserAvatar("", "")) if err != nil { return } @@ -299,7 +297,7 @@ func (s *Session) UserUpdate(email, password, username, avatar, newPassword stri NewPassword string `json:"new_password,omitempty"` }{email, password, username, avatar, newPassword} - body, err := s.Request("PATCH", EndpointUser("@me"), data) + body, err := s.RequestWithBucketID("PATCH", EndpointUser("@me"), data, EndpointUsers) if err != nil { return } @@ -311,7 +309,28 @@ func (s *Session) UserUpdate(email, password, username, avatar, newPassword stri // UserSettings returns the settings for a given user func (s *Session) UserSettings() (st *Settings, err error) { - body, err := s.Request("GET", EndpointUserSettings("@me"), nil) + body, err := s.RequestWithBucketID("GET", EndpointUserSettings("@me"), nil, EndpointUserSettings("")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// UserUpdateStatus update the user status +// status : The new status (Actual valid status are 'online','idle','dnd','invisible') +func (s *Session) UserUpdateStatus(status Status) (st *Settings, err error) { + if status == StatusOffline { + err = errors.New("You can't set your Status to offline") + return + } + + data := struct { + Status Status `json:"status"` + }{status} + + body, err := s.RequestWithBucketID("PATCH", EndpointUserSettings("@me"), data, EndpointUserSettings("")) if err != nil { return } @@ -324,7 +343,7 @@ func (s *Session) UserSettings() (st *Settings, err error) { // channels. func (s *Session) UserChannels() (st []*Channel, err error) { - body, err := s.Request("GET", EndpointUserChannels("@me"), nil) + body, err := s.RequestWithBucketID("GET", EndpointUserChannels("@me"), nil, EndpointUserChannels("")) if err != nil { return } @@ -341,7 +360,7 @@ func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) RecipientID string `json:"recipient_id"` }{recipientID} - body, err := s.Request("POST", EndpointUserChannels("@me"), data) + body, err := s.RequestWithBucketID("POST", EndpointUserChannels("@me"), data, EndpointUserChannels("")) if err != nil { return } @@ -350,10 +369,10 @@ func (s *Session) UserChannelCreate(recipientID string) (st *Channel, err error) return } -// UserGuilds returns an array of Guild structures for all guilds. -func (s *Session) UserGuilds() (st []*Guild, err error) { +// UserGuilds returns an array of UserGuild structures for all guilds. +func (s *Session) UserGuilds() (st []*UserGuild, err error) { - body, err := s.Request("GET", EndpointUserGuilds("@me"), nil) + body, err := s.RequestWithBucketID("GET", EndpointUserGuilds("@me"), nil, EndpointUserGuilds("")) if err != nil { return } @@ -367,7 +386,7 @@ func (s *Session) UserGuilds() (st []*Guild, err error) { // settings : The settings to update func (s *Session) UserGuildSettingsEdit(guildID string, settings *UserGuildSettingsEdit) (st *UserGuildSettings, err error) { - body, err := s.Request("PATCH", EndpointUserGuildSettings("@me", guildID), settings) + body, err := s.RequestWithBucketID("PATCH", EndpointUserGuildSettings("@me", guildID), settings, EndpointUserGuildSettings("", guildID)) if err != nil { return } @@ -376,11 +395,12 @@ func (s *Session) UserGuildSettingsEdit(guildID string, settings *UserGuildSetti return } -// NOTE: This function is now deprecated and will be removed in the future. -// Please see the same function inside state.go // 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. +// +// NOTE: This function is now deprecated and will be removed in the future. +// Please see the same function inside state.go func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions int, err error) { channel, err := s.State.Channel(channelID) if err != nil || channel == nil { @@ -411,6 +431,13 @@ func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions } } + for _, role := range guild.Roles { + if role.ID == guild.ID { + apermissions |= role.Permissions + break + } + } + for _, role := range guild.Roles { for _, roleID := range member.Roles { if role.ID == roleID { @@ -420,7 +447,7 @@ func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions } } - if apermissions&PermissionManageRoles > 0 { + if apermissions&PermissionAdministrator > 0 { apermissions |= PermissionAll } @@ -443,7 +470,7 @@ func (s *Session) UserChannelPermissions(userID, channelID string) (apermissions } } - if apermissions&PermissionManageRoles > 0 { + if apermissions&PermissionAdministrator > 0 { apermissions |= PermissionAllChannel } @@ -465,7 +492,7 @@ func (s *Session) Guild(guildID string) (st *Guild, err error) { } } - body, err := s.Request("GET", EndpointGuild(guildID), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuild(guildID), nil, EndpointGuild(guildID)) if err != nil { return } @@ -482,7 +509,7 @@ func (s *Session) GuildCreate(name string) (st *Guild, err error) { Name string `json:"name"` }{name} - body, err := s.Request("POST", EndpointGuilds, data) + body, err := s.RequestWithBucketID("POST", EndpointGuilds, data, EndpointGuilds) if err != nil { return } @@ -530,7 +557,7 @@ func (s *Session) GuildEdit(guildID string, g GuildParams) (st *Guild, err error VerificationLevel *VerificationLevel `json:"verification_level,omitempty"` }{g.Name, g.Region, g.VerificationLevel} - body, err := s.Request("PATCH", EndpointGuild(guildID), data) + body, err := s.RequestWithBucketID("PATCH", EndpointGuild(guildID), data, EndpointGuild(guildID)) if err != nil { return } @@ -543,7 +570,7 @@ func (s *Session) GuildEdit(guildID string, g GuildParams) (st *Guild, err error // guildID : The ID of a Guild func (s *Session) GuildDelete(guildID string) (st *Guild, err error) { - body, err := s.Request("DELETE", EndpointGuild(guildID), nil) + body, err := s.RequestWithBucketID("DELETE", EndpointGuild(guildID), nil, EndpointGuild(guildID)) if err != nil { return } @@ -556,16 +583,16 @@ func (s *Session) GuildDelete(guildID string) (st *Guild, err error) { // guildID : The ID of a Guild func (s *Session) GuildLeave(guildID string) (err error) { - _, err = s.Request("DELETE", EndpointUserGuild("@me", guildID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointUserGuild("@me", guildID), nil, EndpointUserGuild("", guildID)) return } // GuildBans returns an array of User structures for all bans of a // given guild. // guildID : The ID of a Guild. -func (s *Session) GuildBans(guildID string) (st []*User, err error) { +func (s *Session) GuildBans(guildID string) (st []*GuildBan, err error) { - body, err := s.Request("GET", EndpointGuildBans(guildID), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuildBans(guildID), nil, EndpointGuildBans(guildID)) if err != nil { return } @@ -587,7 +614,7 @@ func (s *Session) GuildBanCreate(guildID, userID string, days int) (err error) { uri = fmt.Sprintf("%s?delete-message-days=%d", uri, days) } - _, err = s.Request("PUT", uri, nil) + _, err = s.RequestWithBucketID("PUT", uri, nil, EndpointGuildBan(guildID, "")) return } @@ -596,22 +623,22 @@ func (s *Session) GuildBanCreate(guildID, userID string, days int) (err error) { // userID : The ID of a User func (s *Session) GuildBanDelete(guildID, userID string) (err error) { - _, err = s.Request("DELETE", EndpointGuildBan(guildID, userID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointGuildBan(guildID, userID), nil, EndpointGuildBan(guildID, "")) return } // GuildMembers returns a list of members for a guild. // guildID : The ID of a Guild. -// offset : A number of members to skip +// after : The id of the member to return members after // limit : max number of members to return (max 1000) -func (s *Session) GuildMembers(guildID string, offset, limit int) (st []*Member, err error) { +func (s *Session) GuildMembers(guildID string, after string, limit int) (st []*Member, err error) { uri := EndpointGuildMembers(guildID) v := url.Values{} - if offset > 0 { - v.Set("offset", strconv.Itoa(offset)) + if after != "" { + v.Set("after", after) } if limit > 0 { @@ -622,7 +649,7 @@ func (s *Session) GuildMembers(guildID string, offset, limit int) (st []*Member, uri = fmt.Sprintf("%s?%s", uri, v.Encode()) } - body, err := s.Request("GET", uri, nil) + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildMembers(guildID)) if err != nil { return } @@ -636,7 +663,7 @@ func (s *Session) GuildMembers(guildID string, offset, limit int) (st []*Member, // userID : The ID of a User func (s *Session) GuildMember(guildID, userID string) (st *Member, err error) { - body, err := s.Request("GET", EndpointGuildMember(guildID, userID), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuildMember(guildID, userID), nil, EndpointGuildMember(guildID, "")) if err != nil { return } @@ -650,7 +677,7 @@ func (s *Session) GuildMember(guildID, userID string) (st *Member, err error) { // userID : The ID of a User func (s *Session) GuildMemberDelete(guildID, userID string) (err error) { - _, err = s.Request("DELETE", EndpointGuildMember(guildID, userID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointGuildMember(guildID, userID), nil, EndpointGuildMember(guildID, "")) return } @@ -664,7 +691,7 @@ func (s *Session) GuildMemberEdit(guildID, userID string, roles []string) (err e Roles []string `json:"roles"` }{roles} - _, err = s.Request("PATCH", EndpointGuildMember(guildID, userID), data) + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) if err != nil { return } @@ -684,7 +711,7 @@ func (s *Session) GuildMemberMove(guildID, userID, channelID string) (err error) ChannelID string `json:"channel_id"` }{channelID} - _, err = s.Request("PATCH", EndpointGuildMember(guildID, userID), data) + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) if err != nil { return } @@ -701,7 +728,29 @@ func (s *Session) GuildMemberNickname(guildID, userID, nickname string) (err err Nick string `json:"nick"` }{nickname} - _, err = s.Request("PATCH", EndpointGuildMember(guildID, userID), data) + _, err = s.RequestWithBucketID("PATCH", EndpointGuildMember(guildID, userID), data, EndpointGuildMember(guildID, "")) + return +} + +// GuildMemberRoleAdd adds the specified role to a given member +// guildID : The ID of a Guild. +// userID : The ID of a User. +// roleID : The ID of a Role to be assigned to the user. +func (s *Session) GuildMemberRoleAdd(guildID, userID, roleID string) (err error) { + + _, err = s.RequestWithBucketID("PUT", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, userID, roleID)) + + return +} + +// GuildMemberRoleRemove removes the specified role to a given member +// guildID : The ID of a Guild. +// userID : The ID of a User. +// roleID : The ID of a Role to be removed from the user. +func (s *Session) GuildMemberRoleRemove(guildID, userID, roleID string) (err error) { + + _, err = s.RequestWithBucketID("DELETE", EndpointGuildMemberRole(guildID, userID, roleID), nil, EndpointGuildMemberRole(guildID, userID, roleID)) + return } @@ -710,7 +759,7 @@ func (s *Session) GuildMemberNickname(guildID, userID, nickname string) (err err // guildID : The ID of a Guild. func (s *Session) GuildChannels(guildID string) (st []*Channel, err error) { - body, err := s.request("GET", EndpointGuildChannels(guildID), "", nil) + body, err := s.request("GET", EndpointGuildChannels(guildID), "", nil, EndpointGuildChannels(guildID), 0) if err != nil { return } @@ -731,7 +780,7 @@ func (s *Session) GuildChannelCreate(guildID, name, ctype string) (st *Channel, Type string `json:"type"` }{name, ctype} - body, err := s.Request("POST", EndpointGuildChannels(guildID), data) + body, err := s.RequestWithBucketID("POST", EndpointGuildChannels(guildID), data, EndpointGuildChannels(guildID)) if err != nil { return } @@ -745,14 +794,14 @@ func (s *Session) GuildChannelCreate(guildID, name, ctype string) (st *Channel, // channels : Updated channels. func (s *Session) GuildChannelsReorder(guildID string, channels []*Channel) (err error) { - _, err = s.Request("PATCH", EndpointGuildChannels(guildID), channels) + _, err = s.RequestWithBucketID("PATCH", EndpointGuildChannels(guildID), channels, EndpointGuildChannels(guildID)) return } // GuildInvites returns an array of Invite structures for the given guild // guildID : The ID of a Guild. func (s *Session) GuildInvites(guildID string) (st []*Invite, err error) { - body, err := s.Request("GET", EndpointGuildInvites(guildID), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuildInvites(guildID), nil, EndpointGuildInivtes(guildID)) if err != nil { return } @@ -765,7 +814,7 @@ func (s *Session) GuildInvites(guildID string) (st []*Invite, err error) { // guildID : The ID of a Guild. func (s *Session) GuildRoles(guildID string) (st []*Role, err error) { - body, err := s.Request("GET", EndpointGuildRoles(guildID), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuildRoles(guildID), nil, EndpointGuildRoles(guildID)) if err != nil { return } @@ -779,7 +828,7 @@ func (s *Session) GuildRoles(guildID string) (st []*Role, err error) { // guildID: The ID of a Guild. func (s *Session) GuildRoleCreate(guildID string) (st *Role, err error) { - body, err := s.Request("POST", EndpointGuildRoles(guildID), nil) + body, err := s.RequestWithBucketID("POST", EndpointGuildRoles(guildID), nil, EndpointGuildRoles(guildID)) if err != nil { return } @@ -796,7 +845,8 @@ func (s *Session) GuildRoleCreate(guildID string) (st *Role, err error) { // color : The color of the role (decimal, not hex). // hoist : Whether to display the role's users separately. // perm : The permissions for the role. -func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist bool, perm int) (st *Role, err error) { +// mention : Whether this role is mentionable +func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist bool, perm int, mention bool) (st *Role, err error) { // Prevent sending a color int that is too big. if color > 0xFFFFFF { @@ -804,13 +854,14 @@ func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist b } data := struct { - Name string `json:"name"` // The color the role should have (as a decimal, not hex) - Color int `json:"color"` // Whether to display the role's users separately - Hoist bool `json:"hoist"` // The role's name (overwrites existing) + Name string `json:"name"` // The role's name (overwrites existing) + Color int `json:"color"` // The color the role should have (as a decimal, not hex) + Hoist bool `json:"hoist"` // Whether to display the role's users separately Permissions int `json:"permissions"` // The overall permissions number of the role (overwrites existing) - }{name, color, hoist, perm} + Mentionable bool `json:"mentionable"` // Whether this role is mentionable + }{name, color, hoist, perm, mention} - body, err := s.Request("PATCH", EndpointGuildRole(guildID, roleID), data) + body, err := s.RequestWithBucketID("PATCH", EndpointGuildRole(guildID, roleID), data, EndpointGuildRole(guildID, "")) if err != nil { return } @@ -825,7 +876,7 @@ func (s *Session) GuildRoleEdit(guildID, roleID, name string, color int, hoist b // roles : A list of ordered roles. func (s *Session) GuildRoleReorder(guildID string, roles []*Role) (st []*Role, err error) { - body, err := s.Request("PATCH", EndpointGuildRoles(guildID), roles) + body, err := s.RequestWithBucketID("PATCH", EndpointGuildRoles(guildID), roles, EndpointGuildRoles(guildID)) if err != nil { return } @@ -840,7 +891,72 @@ func (s *Session) GuildRoleReorder(guildID string, roles []*Role) (st []*Role, e // roleID : The ID of a Role. func (s *Session) GuildRoleDelete(guildID, roleID string) (err error) { - _, err = s.Request("DELETE", EndpointGuildRole(guildID, roleID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointGuildRole(guildID, roleID), nil, EndpointGuildRole(guildID, "")) + + return +} + +// GuildPruneCount Returns the number of members that would be removed in a prune operation. +// Requires 'KICK_MEMBER' permission. +// guildID : The ID of a Guild. +// days : The number of days to count prune for (1 or more). +func (s *Session) GuildPruneCount(guildID string, days uint32) (count uint32, err error) { + count = 0 + + if days <= 0 { + err = errors.New("The number of days should be more than or equal to 1.") + return + } + + p := struct { + Pruned uint32 `json:"pruned"` + }{} + + uri := EndpointGuildPrune(guildID) + fmt.Sprintf("?days=%d", days) + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointGuildPrune(guildID)) + + err = unmarshal(body, &p) + if err != nil { + return + } + + count = p.Pruned + + return +} + +// GuildPrune Begin as prune operation. Requires the 'KICK_MEMBERS' permission. +// Returns an object with one 'pruned' key indicating the number of members that were removed in the prune operation. +// guildID : The ID of a Guild. +// days : The number of days to count prune for (1 or more). +func (s *Session) GuildPrune(guildID string, days uint32) (count uint32, err error) { + + count = 0 + + if days <= 0 { + err = errors.New("The number of days should be more than or equal to 1.") + return + } + + data := struct { + days uint32 + }{days} + + p := struct { + Pruned uint32 `json:"pruned"` + }{} + + body, err := s.RequestWithBucketID("POST", EndpointGuildPrune(guildID), data, EndpointGuildPrune(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &p) + if err != nil { + return + } + + count = p.Pruned return } @@ -849,7 +965,7 @@ func (s *Session) GuildRoleDelete(guildID, roleID string) (err error) { // guildID : The ID of a Guild. func (s *Session) GuildIntegrations(guildID string) (st []*GuildIntegration, err error) { - body, err := s.Request("GET", EndpointGuildIntegrations(guildID), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuildIntegrations(guildID), nil, EndpointGuildIntegrations(guildID)) if err != nil { return } @@ -870,7 +986,7 @@ func (s *Session) GuildIntegrationCreate(guildID, integrationType, integrationID ID string `json:"id"` }{integrationType, integrationID} - _, err = s.Request("POST", EndpointGuildIntegrations(guildID), data) + _, err = s.RequestWithBucketID("POST", EndpointGuildIntegrations(guildID), data, EndpointGuildIntegrations(guildID)) return } @@ -889,7 +1005,7 @@ func (s *Session) GuildIntegrationEdit(guildID, integrationID string, expireBeha EnableEmoticons bool `json:"enable_emoticons"` }{expireBehavior, expireGracePeriod, enableEmoticons} - _, err = s.Request("PATCH", EndpointGuildIntegration(guildID, integrationID), data) + _, err = s.RequestWithBucketID("PATCH", EndpointGuildIntegration(guildID, integrationID), data, EndpointGuildIntegration(guildID, "")) return } @@ -898,7 +1014,7 @@ func (s *Session) GuildIntegrationEdit(guildID, integrationID string, expireBeha // integrationID : The ID of an integration. func (s *Session) GuildIntegrationDelete(guildID, integrationID string) (err error) { - _, err = s.Request("DELETE", EndpointGuildIntegration(guildID, integrationID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointGuildIntegration(guildID, integrationID), nil, EndpointGuildIntegration(guildID, "")) return } @@ -907,7 +1023,7 @@ func (s *Session) GuildIntegrationDelete(guildID, integrationID string) (err err // integrationID : The ID of an integration. func (s *Session) GuildIntegrationSync(guildID, integrationID string) (err error) { - _, err = s.Request("POST", EndpointGuildIntegrationSync(guildID, integrationID), nil) + _, err = s.RequestWithBucketID("POST", EndpointGuildIntegrationSync(guildID, integrationID), nil, EndpointGuildIntegration(guildID, "")) return } @@ -924,7 +1040,7 @@ func (s *Session) GuildIcon(guildID string) (img image.Image, err error) { return } - body, err := s.Request("GET", EndpointGuildIcon(guildID, g.Icon), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuildIcon(guildID, g.Icon), nil, EndpointGuildIcon(guildID, "")) if err != nil { return } @@ -946,7 +1062,7 @@ func (s *Session) GuildSplash(guildID string) (img image.Image, err error) { return } - body, err := s.Request("GET", EndpointGuildSplash(guildID, g.Splash), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuildSplash(guildID, g.Splash), nil, EndpointGuildSplash(guildID, "")) if err != nil { return } @@ -959,7 +1075,7 @@ func (s *Session) GuildSplash(guildID string) (img image.Image, err error) { // guildID : The ID of a Guild. func (s *Session) GuildEmbed(guildID string) (st *GuildEmbed, err error) { - body, err := s.Request("GET", EndpointGuildEmbed(guildID), nil) + body, err := s.RequestWithBucketID("GET", EndpointGuildEmbed(guildID), nil, EndpointGuildEmbed(guildID)) if err != nil { return } @@ -974,7 +1090,7 @@ func (s *Session) GuildEmbedEdit(guildID string, enabled bool, channelID string) data := GuildEmbed{enabled, channelID} - _, err = s.Request("PATCH", EndpointGuildEmbed(guildID), data) + _, err = s.RequestWithBucketID("PATCH", EndpointGuildEmbed(guildID), data, EndpointGuildEmbed(guildID)) return } @@ -985,7 +1101,7 @@ func (s *Session) GuildEmbedEdit(guildID string, enabled bool, channelID string) // Channel returns a Channel strucutre of a specific Channel. // channelID : The ID of the Channel you want returned. func (s *Session) Channel(channelID string) (st *Channel, err error) { - body, err := s.Request("GET", EndpointChannel(channelID), nil) + body, err := s.RequestWithBucketID("GET", EndpointChannel(channelID), nil, EndpointChannel(channelID)) if err != nil { return } @@ -1003,7 +1119,7 @@ func (s *Session) ChannelEdit(channelID, name string) (st *Channel, err error) { Name string `json:"name"` }{name} - body, err := s.Request("PATCH", EndpointChannel(channelID), data) + body, err := s.RequestWithBucketID("PATCH", EndpointChannel(channelID), data, EndpointChannel(channelID)) if err != nil { return } @@ -1016,7 +1132,7 @@ func (s *Session) ChannelEdit(channelID, name string) (st *Channel, err error) { // channelID : The ID of a Channel func (s *Session) ChannelDelete(channelID string) (st *Channel, err error) { - body, err := s.Request("DELETE", EndpointChannel(channelID), nil) + body, err := s.RequestWithBucketID("DELETE", EndpointChannel(channelID), nil, EndpointChannel(channelID)) if err != nil { return } @@ -1030,7 +1146,7 @@ func (s *Session) ChannelDelete(channelID string) (st *Channel, err error) { // channelID : The ID of a Channel func (s *Session) ChannelTyping(channelID string) (err error) { - _, err = s.Request("POST", EndpointChannelTyping(channelID), nil) + _, err = s.RequestWithBucketID("POST", EndpointChannelTyping(channelID), nil, EndpointChannelTyping(channelID)) return } @@ -1058,7 +1174,7 @@ func (s *Session) ChannelMessages(channelID string, limit int, beforeID, afterID uri = fmt.Sprintf("%s?%s", uri, v.Encode()) } - body, err := s.Request("GET", uri, nil) + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointChannelMessages(channelID)) if err != nil { return } @@ -1072,7 +1188,7 @@ func (s *Session) ChannelMessages(channelID string, limit int, beforeID, afterID // messageID : the ID of a Message func (s *Session) ChannelMessage(channelID, messageID string) (st *Message, err error) { - response, err := s.Request("GET", EndpointChannelMessage(channelID, messageID), nil) + response, err := s.RequestWithBucketID("GET", EndpointChannelMessage(channelID, messageID), nil, EndpointChannelMessage(channelID, "")) if err != nil { return } @@ -1084,9 +1200,15 @@ func (s *Session) ChannelMessage(channelID, messageID string) (st *Message, err // ChannelMessageAck acknowledges and marks the given message as read // channeld : The ID of a Channel // messageID : the ID of a Message -func (s *Session) ChannelMessageAck(channelID, messageID string) (err error) { +// lastToken : token returned by last ack +func (s *Session) ChannelMessageAck(channelID, messageID, lastToken string) (st *Ack, err error) { - _, err = s.request("POST", EndpointChannelMessageAck(channelID, messageID), "", nil) + body, err := s.RequestWithBucketID("POST", EndpointChannelMessageAck(channelID, messageID), &Ack{Token: lastToken}, EndpointChannelMessageAck(channelID, "")) + if err != nil { + return + } + + err = unmarshal(body, &st) return } @@ -1103,7 +1225,7 @@ func (s *Session) channelMessageSend(channelID, content string, tts bool) (st *M }{content, tts} // Send the message to the given channel - response, err := s.Request("POST", EndpointChannelMessages(channelID), data) + response, err := s.RequestWithBucketID("POST", EndpointChannelMessages(channelID), data, EndpointChannelMessages(channelID)) if err != nil { return } @@ -1128,6 +1250,28 @@ func (s *Session) ChannelMessageSendTTS(channelID string, content string) (st *M return s.channelMessageSend(channelID, content, true) } +// ChannelMessageSendEmbed sends a message to the given channel with embedded data (bot only). +// channelID : The ID of a Channel. +// embed : The embed data to send. +func (s *Session) ChannelMessageSendEmbed(channelID string, embed *MessageEmbed) (st *Message, err error) { + if embed != nil && embed.Type == "" { + embed.Type = "rich" + } + + data := struct { + Embed *MessageEmbed `json:"embed"` + }{embed} + + // Send the message to the given channel + response, err := s.RequestWithBucketID("POST", EndpointChannelMessages(channelID), data, EndpointChannelMessages(channelID)) + if err != nil { + return + } + + err = unmarshal(response, &st) + return +} + // ChannelMessageEdit edits an existing message, replacing it entirely with // the given content. // channeld : The ID of a Channel @@ -1138,7 +1282,29 @@ func (s *Session) ChannelMessageEdit(channelID, messageID, content string) (st * Content string `json:"content"` }{content} - response, err := s.Request("PATCH", EndpointChannelMessage(channelID, messageID), data) + response, err := s.RequestWithBucketID("PATCH", EndpointChannelMessage(channelID, messageID), data, EndpointChannelMessage(channelID, "")) + if err != nil { + return + } + + err = unmarshal(response, &st) + return +} + +// ChannelMessageEditEmbed edits an existing message with embedded data (bot only). +// channelID : The ID of a Channel +// messageID : The ID of a Message +// embed : The embed data to send +func (s *Session) ChannelMessageEditEmbed(channelID, messageID string, embed *MessageEmbed) (st *Message, err error) { + if embed != nil && embed.Type == "" { + embed.Type = "rich" + } + + data := struct { + Embed *MessageEmbed `json:"embed"` + }{embed} + + response, err := s.RequestWithBucketID("PATCH", EndpointChannelMessage(channelID, messageID), data, EndpointChannelMessage(channelID, "")) if err != nil { return } @@ -1150,7 +1316,7 @@ func (s *Session) ChannelMessageEdit(channelID, messageID, content string) (st * // ChannelMessageDelete deletes a message from the Channel. func (s *Session) ChannelMessageDelete(channelID, messageID string) (err error) { - _, err = s.Request("DELETE", EndpointChannelMessage(channelID, messageID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointChannelMessage(channelID, messageID), nil, EndpointChannelMessage(channelID, "")) return } @@ -1178,7 +1344,7 @@ func (s *Session) ChannelMessagesBulkDelete(channelID string, messages []string) Messages []string `json:"messages"` }{messages} - _, err = s.Request("POST", EndpointChannelMessagesBulkDelete(channelID), data) + _, err = s.RequestWithBucketID("POST", EndpointChannelMessagesBulkDelete(channelID), data, EndpointChannelMessagesBulkDelete(channelID)) return } @@ -1187,7 +1353,7 @@ func (s *Session) ChannelMessagesBulkDelete(channelID string, messages []string) // messageID: The ID of a message. func (s *Session) ChannelMessagePin(channelID, messageID string) (err error) { - _, err = s.Request("PUT", EndpointChannelMessagePin(channelID, messageID), nil) + _, err = s.RequestWithBucketID("PUT", EndpointChannelMessagePin(channelID, messageID), nil, EndpointChannelMessagePin(channelID, "")) return } @@ -1196,7 +1362,7 @@ func (s *Session) ChannelMessagePin(channelID, messageID string) (err error) { // messageID: The ID of a message. func (s *Session) ChannelMessageUnpin(channelID, messageID string) (err error) { - _, err = s.Request("DELETE", EndpointChannelMessagePin(channelID, messageID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointChannelMessagePin(channelID, messageID), nil, EndpointChannelMessagePin(channelID, "")) return } @@ -1205,7 +1371,7 @@ func (s *Session) ChannelMessageUnpin(channelID, messageID string) (err error) { // channelID : The ID of a Channel. func (s *Session) ChannelMessagesPinned(channelID string) (st []*Message, err error) { - body, err := s.Request("GET", EndpointChannelMessagesPins(channelID), nil) + body, err := s.RequestWithBucketID("GET", EndpointChannelMessagesPins(channelID), nil, EndpointChannelMessagesPins(channelID)) if err != nil { return @@ -1217,12 +1383,28 @@ func (s *Session) ChannelMessagesPinned(channelID string) (st []*Message, err er // ChannelFileSend sends a file to the given channel. // 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) +} + +// ChannelFileSendWithMessage sends a file to the given channel with an message. +// 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 @@ -1238,7 +1420,7 @@ func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (st *Mess return } - response, err := s.request("POST", EndpointChannelMessages(channelID), bodywriter.FormDataContentType(), body.Bytes()) + response, err := s.request("POST", EndpointChannelMessages(channelID), bodywriter.FormDataContentType(), body.Bytes(), EndpointChannelMessages(channelID), 0) if err != nil { return } @@ -1251,7 +1433,7 @@ func (s *Session) ChannelFileSend(channelID, name string, r io.Reader) (st *Mess // channelID : The ID of a Channel func (s *Session) ChannelInvites(channelID string) (st []*Invite, err error) { - body, err := s.Request("GET", EndpointChannelInvites(channelID), nil) + body, err := s.RequestWithBucketID("GET", EndpointChannelInvites(channelID), nil, EndpointChannelInvites(channelID)) if err != nil { return } @@ -1273,7 +1455,7 @@ func (s *Session) ChannelInviteCreate(channelID string, i Invite) (st *Invite, e XKCDPass string `json:"xkcdpass"` }{i.MaxAge, i.MaxUses, i.Temporary, i.XkcdPass} - body, err := s.Request("POST", EndpointChannelInvites(channelID), data) + body, err := s.RequestWithBucketID("POST", EndpointChannelInvites(channelID), data, EndpointChannelInvites(channelID)) if err != nil { return } @@ -1294,7 +1476,7 @@ func (s *Session) ChannelPermissionSet(channelID, targetID, targetType string, a Deny int `json:"deny"` }{targetID, targetType, allow, deny} - _, err = s.Request("PUT", EndpointChannelPermission(channelID, targetID), data) + _, err = s.RequestWithBucketID("PUT", EndpointChannelPermission(channelID, targetID), data, EndpointChannelPermission(channelID, "")) return } @@ -1302,7 +1484,7 @@ func (s *Session) ChannelPermissionSet(channelID, targetID, targetType string, a // NOTE: Name of this func may change. func (s *Session) ChannelPermissionDelete(channelID, targetID string) (err error) { - _, err = s.Request("DELETE", EndpointChannelPermission(channelID, targetID), nil) + _, err = s.RequestWithBucketID("DELETE", EndpointChannelPermission(channelID, targetID), nil, EndpointChannelPermission(channelID, "")) return } @@ -1314,7 +1496,7 @@ func (s *Session) ChannelPermissionDelete(channelID, targetID string) (err error // inviteID : The invite code (or maybe xkcdpass?) func (s *Session) Invite(inviteID string) (st *Invite, err error) { - body, err := s.Request("GET", EndpointInvite(inviteID), nil) + body, err := s.RequestWithBucketID("GET", EndpointInvite(inviteID), nil, EndpointInvite("")) if err != nil { return } @@ -1327,7 +1509,7 @@ func (s *Session) Invite(inviteID string) (st *Invite, err error) { // inviteID : the code (or maybe xkcdpass?) of an invite func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { - body, err := s.Request("DELETE", EndpointInvite(inviteID), nil) + body, err := s.RequestWithBucketID("DELETE", EndpointInvite(inviteID), nil, EndpointInvite("")) if err != nil { return } @@ -1340,7 +1522,7 @@ func (s *Session) InviteDelete(inviteID string) (st *Invite, err error) { // inviteID : The invite code (or maybe xkcdpass?) func (s *Session) InviteAccept(inviteID string) (st *Invite, err error) { - body, err := s.Request("POST", EndpointInvite(inviteID), nil) + body, err := s.RequestWithBucketID("POST", EndpointInvite(inviteID), nil, EndpointInvite("")) if err != nil { return } @@ -1356,7 +1538,7 @@ func (s *Session) InviteAccept(inviteID string) (st *Invite, err error) { // VoiceRegions returns the voice server regions func (s *Session) VoiceRegions() (st []*VoiceRegion, err error) { - body, err := s.Request("GET", EndpointVoiceRegions, nil) + body, err := s.RequestWithBucketID("GET", EndpointVoiceRegions, nil, EndpointVoiceRegions) if err != nil { return } @@ -1368,7 +1550,7 @@ func (s *Session) VoiceRegions() (st []*VoiceRegion, err error) { // VoiceICE returns the voice server ICE information func (s *Session) VoiceICE() (st *VoiceICE, err error) { - body, err := s.Request("GET", EndpointVoiceIce, nil) + body, err := s.RequestWithBucketID("GET", EndpointVoiceIce, nil, EndpointVoiceIce) if err != nil { return } @@ -1384,7 +1566,7 @@ func (s *Session) VoiceICE() (st *VoiceICE, err error) { // Gateway returns the a websocket Gateway address func (s *Session) Gateway() (gateway string, err error) { - response, err := s.Request("GET", EndpointGateway, nil) + response, err := s.RequestWithBucketID("GET", EndpointGateway, nil, EndpointGateway) if err != nil { return } @@ -1399,5 +1581,295 @@ func (s *Session) Gateway() (gateway string, err error) { } gateway = temp.URL + + // Ensure the gateway always has a trailing slash. + // MacOS will fail to connect if we add query params without a trailing slash on the base domain. + if !strings.HasSuffix(gateway, "/") { + gateway += "/" + } + + return +} + +// Functions specific to Webhooks + +// WebhookCreate returns a new Webhook. +// channelID: The ID of a Channel. +// name : The name of the webhook. +// avatar : The avatar of the webhook. +func (s *Session) WebhookCreate(channelID, name, avatar string) (st *Webhook, err error) { + + data := struct { + Name string `json:"name"` + Avatar string `json:"avatar,omitempty"` + }{name, avatar} + + body, err := s.RequestWithBucketID("POST", EndpointChannelWebhooks(channelID), data, EndpointChannelWebhooks(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// ChannelWebhooks returns all webhooks for a given channel. +// channelID: The ID of a channel. +func (s *Session) ChannelWebhooks(channelID string) (st []*Webhook, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointChannelWebhooks(channelID), nil, EndpointChannelWebhooks(channelID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// GuildWebhooks returns all webhooks for a given guild. +// guildID: The ID of a Guild. +func (s *Session) GuildWebhooks(guildID string) (st []*Webhook, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointGuildWebhooks(guildID), nil, EndpointGuildWebhooks(guildID)) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// Webhook returns a webhook for a given ID +// webhookID: The ID of a webhook. +func (s *Session) Webhook(webhookID string) (st *Webhook, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointWebhook(webhookID), nil, EndpointWebhooks) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookWithToken returns a webhook for a given ID +// webhookID: The ID of a webhook. +// token : The auth token for the webhook. +func (s *Session) WebhookWithToken(webhookID, token string) (st *Webhook, err error) { + + body, err := s.RequestWithBucketID("GET", EndpointWebhookToken(webhookID, token), nil, EndpointWebhookToken("", "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookEdit updates an existing Webhook. +// webhookID: The ID of a webhook. +// name : The name of the webhook. +// avatar : The avatar of the webhook. +func (s *Session) WebhookEdit(webhookID, name, avatar string) (st *Role, err error) { + + data := struct { + Name string `json:"name,omitempty"` + Avatar string `json:"avatar,omitempty"` + }{name, avatar} + + body, err := s.RequestWithBucketID("PATCH", EndpointWebhook(webhookID), data, EndpointWebhooks) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookEditWithToken updates an existing Webhook with an auth token. +// webhookID: The ID of a webhook. +// token : The auth token for the webhook. +// name : The name of the webhook. +// avatar : The avatar of the webhook. +func (s *Session) WebhookEditWithToken(webhookID, token, name, avatar string) (st *Role, err error) { + + data := struct { + Name string `json:"name,omitempty"` + Avatar string `json:"avatar,omitempty"` + }{name, avatar} + + body, err := s.RequestWithBucketID("PATCH", EndpointWebhookToken(webhookID, token), data, EndpointWebhookToken("", "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookDelete deletes a webhook for a given ID +// webhookID: The ID of a webhook. +func (s *Session) WebhookDelete(webhookID string) (st *Webhook, err error) { + + body, err := s.RequestWithBucketID("DELETE", EndpointWebhook(webhookID), nil, EndpointWebhooks) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookDeleteWithToken deletes a webhook for a given ID with an auth token. +// webhookID: The ID of a webhook. +// token : The auth token for the webhook. +func (s *Session) WebhookDeleteWithToken(webhookID, token string) (st *Webhook, err error) { + + body, err := s.RequestWithBucketID("DELETE", EndpointWebhookToken(webhookID, token), nil, EndpointWebhookToken("", "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + + return +} + +// WebhookExecute executes a webhook. +// webhookID: The ID of a webhook. +// token : The auth token for the webhook +func (s *Session) WebhookExecute(webhookID, token string, wait bool, data *WebhookParams) (err error) { + uri := EndpointWebhookToken(webhookID, token) + + if wait { + uri += "?wait=true" + } + + _, err = s.RequestWithBucketID("POST", uri, data, EndpointWebhookToken("", "")) + + return +} + +// MessageReactionAdd creates an emoji reaction to a message. +// channelID : The channel ID. +// messageID : The message ID. +// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. +func (s *Session) MessageReactionAdd(channelID, messageID, emojiID string) error { + + _, err := s.RequestWithBucketID("PUT", EndpointMessageReaction(channelID, messageID, emojiID, "@me"), nil, EndpointMessageReaction(channelID, "", "", "")) + + return err +} + +// MessageReactionRemove deletes an emoji reaction to a message. +// channelID : The channel ID. +// messageID : The message ID. +// emojiID : Either the unicode emoji for the reaction, or a guild emoji identifier. +// userID : @me or ID of the user to delete the reaction for. +func (s *Session) MessageReactionRemove(channelID, messageID, emojiID, userID string) error { + + _, err := s.RequestWithBucketID("DELETE", EndpointMessageReaction(channelID, messageID, emojiID, userID), nil, EndpointMessageReaction(channelID, "", "", "")) + + return err +} + +// MessageReactions gets all the users reactions for a specific emoji. +// channelID : The channel ID. +// 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) { + uri := EndpointMessageReactions(channelID, messageID, emojiID) + + v := url.Values{} + + if limit > 0 { + v.Set("limit", strconv.Itoa(limit)) + } + + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + body, err := s.RequestWithBucketID("GET", uri, nil, EndpointMessageReaction(channelID, "", "", "")) + if err != nil { + return + } + + err = unmarshal(body, &st) + return +} + +// ------------------------------------------------------------------------------------------------ +// Functions specific to Discord Relationships (Friends list) +// ------------------------------------------------------------------------------------------------ + +// RelationshipsGet returns an array of all the relationships of the user. +func (s *Session) RelationshipsGet() (r []*Relationship, err error) { + body, err := s.RequestWithBucketID("GET", EndpointRelationships(), nil, EndpointRelationships()) + if err != nil { + return + } + + err = unmarshal(body, &r) + return +} + +// relationshipCreate creates a new relationship. (I.e. send or accept a friend request, block a user.) +// relationshipType : 1 = friend, 2 = blocked, 3 = incoming friend req, 4 = sent friend req +func (s *Session) relationshipCreate(userID string, relationshipType int) (err error) { + data := struct { + Type int `json:"type"` + }{relationshipType} + + _, err = s.RequestWithBucketID("PUT", EndpointRelationship(userID), data, EndpointRelationships()) + return +} + +// RelationshipFriendRequestSend sends a friend request to a user. +// userID: ID of the user. +func (s *Session) RelationshipFriendRequestSend(userID string) (err error) { + err = s.relationshipCreate(userID, 4) + return +} + +// RelationshipFriendRequestAccept accepts a friend request from a user. +// userID: ID of the user. +func (s *Session) RelationshipFriendRequestAccept(userID string) (err error) { + err = s.relationshipCreate(userID, 1) + return +} + +// RelationshipUserBlock blocks a user. +// userID: ID of the user. +func (s *Session) RelationshipUserBlock(userID string) (err error) { + err = s.relationshipCreate(userID, 2) + return +} + +// RelationshipDelete removes the relationship with a user. +// userID: ID of the user. +func (s *Session) RelationshipDelete(userID string) (err error) { + _, err = s.RequestWithBucketID("DELETE", EndpointRelationship(userID), nil, EndpointRelationships()) + return +} + +// RelationshipsMutualGet returns an array of all the users both @me and the given user is friends with. +// userID: ID of the user. +func (s *Session) RelationshipsMutualGet(userID string) (mf []*User, err error) { + body, err := s.RequestWithBucketID("GET", EndpointRelationshipsMutual(userID), nil, EndpointRelationshipsMutual(userID)) + if err != nil { + return + } + + err = unmarshal(body, &mf) return } diff --git a/vendor/github.com/bwmarrin/discordgo/state.go b/vendor/github.com/bwmarrin/discordgo/state.go index e9eb4d67..25dd3d16 100644 --- a/vendor/github.com/bwmarrin/discordgo/state.go +++ b/vendor/github.com/bwmarrin/discordgo/state.go @@ -55,33 +55,6 @@ func NewState() *State { } } -// OnReady takes a Ready event and updates all internal state. -func (s *State) OnReady(r *Ready) error { - if s == nil { - return ErrNilState - } - - s.Lock() - defer s.Unlock() - - s.Ready = *r - - for _, g := range s.Guilds { - s.guildMap[g.ID] = g - - for _, c := range g.Channels { - c.GuildID = g.ID - s.channelMap[c.ID] = c - } - } - - for _, c := range s.PrivateChannels { - s.channelMap[c.ID] = c - } - - return nil -} - // GuildAdd adds a guild to the current world state, or // updates it if it already exists. func (s *State) GuildAdd(guild *Guild) error { @@ -94,20 +67,30 @@ func (s *State) GuildAdd(guild *Guild) error { // Update the channels to point to the right guild, adding them to the channelMap as we go for _, c := range guild.Channels { - c.GuildID = guild.ID s.channelMap[c.ID] = c } - // If the guild exists, replace it. if g, ok := s.guildMap[guild.ID]; ok { - // If this guild already exists with data, don't stomp on props. - if g.Unavailable != nil && !*g.Unavailable { + // 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 } @@ -325,8 +308,12 @@ func (s *State) ChannelAdd(channel *Channel) error { // If the channel exists, replace it if c, ok := s.channelMap[channel.ID]; ok { - channel.Messages = c.Messages - channel.PermissionOverwrites = c.PermissionOverwrites + if channel.Messages == nil { + channel.Messages = c.Messages + } + if channel.PermissionOverwrites == nil { + channel.PermissionOverwrites = c.PermissionOverwrites + } *c = *channel return nil @@ -511,6 +498,12 @@ func (s *State) MessageAdd(message *Message) error { 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 } @@ -602,18 +595,63 @@ func (s *State) Message(channelID, messageID string) (*Message, error) { return nil, errors.New("Message not found.") } +// 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, + HeartbeatInterval: r.HeartbeatInterval, + User: r.User, + } + + s.Ready = ready + + return nil + } + + s.Ready = *r + + for _, g := range s.Guilds { + s.guildMap[g.ID] = 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 *Ready: - err = s.OnReady(t) case *GuildCreate: err = s.GuildAdd(t.Guild) case *GuildUpdate: @@ -685,6 +723,9 @@ func (s *State) onInterface(se *Session, i interface{}) (err error) { // 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 { @@ -706,6 +747,13 @@ func (s *State) UserChannelPermissions(userID, channelID string) (apermissions i return } + for _, role := range guild.Roles { + if role.ID == guild.ID { + apermissions |= role.Permissions + break + } + } + for _, role := range guild.Roles { for _, roleID := range member.Roles { if role.ID == roleID { @@ -715,7 +763,7 @@ func (s *State) UserChannelPermissions(userID, channelID string) (apermissions i } } - if apermissions&PermissionManageRoles > 0 { + if apermissions&PermissionAdministrator > 0 { apermissions |= PermissionAll } @@ -738,7 +786,7 @@ func (s *State) UserChannelPermissions(userID, channelID string) (apermissions i } } - if apermissions&PermissionManageRoles > 0 { + if apermissions&PermissionAdministrator > 0 { apermissions |= PermissionAllChannel } diff --git a/vendor/github.com/bwmarrin/discordgo/structs.go b/vendor/github.com/bwmarrin/discordgo/structs.go index 19a291f8..548ee52c 100644 --- a/vendor/github.com/bwmarrin/discordgo/structs.go +++ b/vendor/github.com/bwmarrin/discordgo/structs.go @@ -13,7 +13,7 @@ package discordgo import ( "encoding/json" - "reflect" + "strconv" "sync" "time" @@ -53,6 +53,9 @@ type Session struct { // 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 @@ -70,13 +73,10 @@ type Session struct { // StateEnabled is true. State *State - handlersMu sync.RWMutex - // This is a mapping of event struct to a reflected value - // for event handlers. - // We store the reflected value instead of the function - // reference as it is more performant, instead of re-reflecting - // the function each event. - handlers map[interface{}][]reflect.Value + // Event handlers + handlersMu sync.RWMutex + handlers map[string][]*eventHandlerInstance + onceHandlers map[string][]*eventHandlerInstance // The websocket connection. wsConn *websocket.Conn @@ -85,9 +85,7 @@ type Session struct { listening chan interface{} // used to deal with rate limits - // may switch to slices later - // TODO: performance test map vs slices - rateLimit rateLimitMutex + ratelimiter *RateLimiter // sequence tracks the current gateway api websocket sequence number sequence int @@ -108,12 +106,6 @@ type rateLimitMutex struct { // bucket map[string]*sync.Mutex // TODO :) } -// A Resumed struct holds the data received in a RESUMED event -type Resumed struct { - HeartbeatInterval time.Duration `json:"heartbeat_interval"` - Trace []string `json:"_trace"` -} - // A VoiceRegion stores data for a specific voice region server. type VoiceRegion struct { ID string `json:"id"` @@ -137,17 +129,17 @@ type ICEServer struct { // 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 string `json:"created_at"` // TODO make timestamp - MaxAge int `json:"max_age"` - Uses int `json:"uses"` - MaxUses int `json:"max_uses"` - XkcdPass string `json:"xkcdpass"` - Revoked bool `json:"revoked"` - Temporary bool `json:"temporary"` + 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"` + XkcdPass string `json:"xkcdpass"` + Revoked bool `json:"revoked"` + Temporary bool `json:"temporary"` } // A Channel holds all data related to an individual Discord channel. @@ -183,6 +175,17 @@ type Emoji struct { RequireColons bool `json:"require_colons"` } +// 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 defination type VerificationLevel int @@ -204,9 +207,10 @@ type Guild struct { AfkChannelID string `json:"afk_channel_id"` EmbedChannelID string `json:"embed_channel_id"` OwnerID string `json:"owner_id"` - JoinedAt string `json:"joined_at"` // make this a timestamp + 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"` // ?? @@ -217,7 +221,16 @@ type Guild struct { Presences []*Presence `json:"presences"` Channels []*Channel `json:"channels"` VoiceStates []*VoiceState `json:"voice_states"` - Unavailable *bool `json:"unavailable"` + 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 @@ -232,6 +245,7 @@ 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"` @@ -253,9 +267,11 @@ 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 string `json:"status"` - Game *Game `json:"game"` + User *User `json:"user"` + Status Status `json:"status"` + Game *Game `json:"game"` + Nick string `json:"nick"` + Roles []string `json:"roles"` } // A Game struct holds the name of the "playing .." game for a user @@ -265,6 +281,38 @@ type Game struct { URL string `json:"url"` } +// UnmarshalJSON unmarshals json to Game struct +func (g *Game) UnmarshalJSON(bytes []byte) error { + temp := &struct { + Name string `json:"name"` + Type json.RawMessage `json:"type"` + URL string `json:"url"` + }{} + err := json.Unmarshal(bytes, temp) + if err != nil { + return err + } + g.Name = temp.Name + g.URL = temp.URL + + if temp.Type != nil { + err = json.Unmarshal(temp.Type, &g.Type) + if err == nil { + return nil + } + + s := "" + err = json.Unmarshal(temp.Type, &s) + if err == nil { + g.Type, err = strconv.Atoi(s) + } + + return err + } + + return nil +} + // A Member stores user information for Guild members. type Member struct { GuildID string `json:"guild_id"` @@ -291,20 +339,34 @@ type User struct { // 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"` - AllowEmailFriendRequest bool `json:"allow_email_friend_request"` - 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"` -} + 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 defination +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 { @@ -313,32 +375,6 @@ type FriendSourceFlags struct { MutualFriends bool `json:"mutual_friends"` } -// An Event provides a basic initial struct for all websocket event. -type Event struct { - Operation int `json:"op"` - Sequence int `json:"s"` - Type string `json:"t"` - RawData json.RawMessage `json:"d"` - 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"` - HeartbeatInterval time.Duration `json:"heartbeat_interval"` - 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"` -} - // A Relationship between the logged in user and Relationship.User type Relationship struct { User *User `json:"user"` @@ -361,54 +397,21 @@ type ReadState struct { ID string `json:"id"` } -// A TypingStart stores data for the typing start websocket event. -type TypingStart struct { - UserID string `json:"user_id"` - ChannelID string `json:"channel_id"` - Timestamp int `json:"timestamp"` +// An Ack is used to ack messages +type Ack struct { + Token string `json:"token"` } -// A PresenceUpdate stores data for the presence update websocket event. -type PresenceUpdate struct { - Presence - GuildID string `json:"guild_id"` - Roles []string `json:"roles"` -} - -// A MessageAck stores data for the message ack websocket event. -type MessageAck struct { - MessageID string `json:"message_id"` - ChannelID string `json:"channel_id"` -} - -// A GuildIntegrationsUpdate stores data for the guild integrations update -// websocket event. -type GuildIntegrationsUpdate struct { - GuildID string `json:"guild_id"` -} - -// A GuildRole stores data for guild role websocket events. +// A GuildRole stores data for guild roles. type GuildRole struct { Role *Role `json:"role"` GuildID string `json:"guild_id"` } -// A GuildRoleDelete stores data for the guild role delete websocket event. -type GuildRoleDelete struct { - RoleID string `json:"role_id"` - GuildID string `json:"guild_id"` -} - // A GuildBan stores data for a guild ban. type GuildBan struct { - User *User `json:"user"` - GuildID string `json:"guild_id"` -} - -// A GuildEmojisUpdate stores data for a guild emoji update event. -type GuildEmojisUpdate struct { - GuildID string `json:"guild_id"` - Emojis []*Emoji `json:"emojis"` + Reason string `json:"reason"` + User *User `json:"user"` } // A GuildIntegration stores data for a guild integration. @@ -464,6 +467,41 @@ type UserGuildSettingsEdit struct { 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"` +} + // Constants for the different bit offsets of text channel permissions const ( PermissionReadMessages = 1 << (iota + 10) @@ -474,6 +512,7 @@ const ( PermissionAttachFiles PermissionReadMessageHistory PermissionMentionEveryone + PermissionUseExternalEmojis ) // Constants for the different bit offsets of voice permissions @@ -486,12 +525,21 @@ const ( 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 - PermissionManageRoles + PermissionAdministrator PermissionManageChannels PermissionManageServer diff --git a/vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go b/vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go new file mode 100644 index 00000000..f3894085 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/tools/cmd/eventhandlers/main.go @@ -0,0 +1,123 @@ +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 +} + +// New returns a new instance of {{.}}. +func (eh {{privateName .}}EventHandler) New() interface{} { + return &{{.}}{} +} + +// 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:] +} diff --git a/vendor/github.com/bwmarrin/discordgo/types.go b/vendor/github.com/bwmarrin/discordgo/types.go new file mode 100644 index 00000000..780b6bb9 --- /dev/null +++ b/vendor/github.com/bwmarrin/discordgo/types.go @@ -0,0 +1,58 @@ +// Discordgo - Discord bindings for Go +// Available at https://github.com/bwmarrin/discordgo + +// Copyright 2015-2016 Bruce Marriner . 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) +} diff --git a/vendor/github.com/bwmarrin/discordgo/voice.go b/vendor/github.com/bwmarrin/discordgo/voice.go index 094aa59e..43de329e 100644 --- a/vendor/github.com/bwmarrin/discordgo/voice.go +++ b/vendor/github.com/bwmarrin/discordgo/voice.go @@ -441,7 +441,7 @@ func (v *VoiceConnection) onEvent(message []byte) { } default: - v.log(LogError, "unknown voice operation, %d, %s", e.Operation, string(e.RawData)) + v.log(LogDebug, "unknown voice operation, %d, %s", e.Operation, string(e.RawData)) } return @@ -570,7 +570,7 @@ func (v *VoiceConnection) udpOpen() (err error) { return fmt.Errorf("received udp packet too small") } - // Loop over position 4 though 20 to grab the IP address + // 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++ { diff --git a/vendor/github.com/bwmarrin/discordgo/wsapi.go b/vendor/github.com/bwmarrin/discordgo/wsapi.go index a19c3842..99a6236a 100644 --- a/vendor/github.com/bwmarrin/discordgo/wsapi.go +++ b/vendor/github.com/bwmarrin/discordgo/wsapi.go @@ -17,9 +17,7 @@ import ( "errors" "fmt" "io" - "log" "net/http" - "reflect" "runtime" "time" @@ -47,6 +45,17 @@ func (s *Session) Open() (err error) { } }() + // A basic state is a hard requirement for Voice. + if s.State == nil { + state := NewState() + state.TrackChannels = false + state.TrackEmojis = false + state.TrackMembers = false + state.TrackRoles = false + state.TrackVoice = false + s.State = state + } + if s.wsConn != nil { err = errors.New("Web socket already opened.") return @@ -111,9 +120,8 @@ func (s *Session) Open() (err error) { s.Unlock() - s.initialize() s.log(LogInformational, "emit connect event") - s.handle(&Connect{}) + s.handleEvent(connectEventType, &Connect{}) s.log(LogInformational, "exiting") return @@ -269,6 +277,44 @@ 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 errors.New("no websocket connection exists") + } + + 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. // @@ -361,16 +407,12 @@ func (s *Session) onEvent(messageType int, message []byte) { // Store the message sequence s.sequence = e.Sequence - // Map event to registered event handlers and pass it along - // to any registered functions - i := eventToInterface[e.Type] - if i != nil { - - // Create a new instance of the event type. - i = reflect.New(reflect.TypeOf(i)).Interface() + // 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, i); err != nil { + if err = json.Unmarshal(e.RawData, e.Struct); err != nil { s.log(LogError, "error unmarshalling %s event, %s", e.Type, err) } @@ -381,30 +423,19 @@ func (s *Session) onEvent(messageType int, message []byte) { // 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. - go s.handle(i) - + 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)) } - // Emit event to the OnEvent handler - e.Struct = i - go s.handle(e) + // For legacy reasons, we send the raw event also, this could be useful for handling unknown events. + s.handleEvent(eventEventType, e) } // ------------------------------------------------------------------------------------------------ // Code related to voice connections that initiate over the data websocket // ------------------------------------------------------------------------------------------------ -// A VoiceServerUpdate stores the data received during the Voice Server Update -// data websocket event. This data is used during the initial Voice Channel -// join handshaking. -type VoiceServerUpdate struct { - Token string `json:"token"` - GuildID string `json:"guild_id"` - Endpoint string `json:"endpoint"` -} - type voiceChannelJoinData struct { GuildID *string `json:"guild_id"` ChannelID *string `json:"channel_id"` @@ -461,7 +492,7 @@ func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *Voi } // onVoiceStateUpdate handles Voice State Update events on the data websocket. -func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) { +func (s *Session) onVoiceStateUpdate(st *VoiceStateUpdate) { // If we don't have a connection for the channel, don't bother if st.ChannelID == "" { @@ -474,22 +505,13 @@ func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) { return } - // Need to have this happen at login and store it in the Session - // TODO : This should be done upon connecting to Discord, or - // be moved to a small helper function - self, err := s.User("@me") // TODO: move to Login/New - if err != nil { - log.Println(err) - return - } - - // We only care about events that are about us - if st.UserID != self.ID { + // We only care about events that are about us. + if s.State.User.ID != st.UserID { return } // Store the SessionID for later use. - voice.UserID = self.ID // TODO: Review + voice.UserID = st.UserID voice.sessionID = st.SessionID } @@ -498,7 +520,7 @@ func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) { // 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(se *Session, st *VoiceServerUpdate) { +func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) { s.log(LogInformational, "called") @@ -655,7 +677,7 @@ func (s *Session) Close() (err error) { // frame and wait for the server to close the connection. err := s.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { - s.log(LogError, "error closing websocket, %s", err) + s.log(LogInformational, "error closing websocket, %s", err) } // TODO: Wait for Discord to actually close the connection. @@ -664,7 +686,7 @@ func (s *Session) Close() (err error) { s.log(LogInformational, "closing gateway websocket") err = s.wsConn.Close() if err != nil { - s.log(LogError, "error closing websocket, %s", err) + s.log(LogInformational, "error closing websocket, %s", err) } s.wsConn = nil @@ -673,7 +695,7 @@ func (s *Session) Close() (err error) { s.Unlock() s.log(LogInformational, "emit disconnect event") - s.handle(&Disconnect{}) + s.handleEvent(disconnectEventType, &Disconnect{}) return } diff --git a/vendor/github.com/mattn/go-xmpp/xmpp.go b/vendor/github.com/mattn/go-xmpp/xmpp.go index e20b57a8..11424f82 100644 --- a/vendor/github.com/mattn/go-xmpp/xmpp.go +++ b/vendor/github.com/mattn/go-xmpp/xmpp.go @@ -404,9 +404,13 @@ func (c *Client) init(o *Options) error { switch v := val.(type) { case *saslSuccess: case *saslFailure: - // v.Any is type of sub-element in failure, - // which gives a description of what failed. - return errors.New("auth failure: " + v.Any.Local) + errorMessage := v.Text + if errorMessage == "" { + // v.Any is type of sub-element in failure, + // which gives a description of what failed if there was no text element + errorMessage = v.Any.Local + } + return errors.New("auth failure: " + errorMessage) default: return errors.New("expected or , got <" + name.Local + "> in " + name.Space) } @@ -699,6 +703,7 @@ type saslSuccess struct { type saslFailure struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"` Any xml.Name `xml:",any"` + Text string `xml:"text"` } // RFC 3920 C.5 Resource binding name space diff --git a/vendor/github.com/sromku/go-gitter/LICENSE b/vendor/github.com/sromku/go-gitter/LICENSE new file mode 100644 index 00000000..8dada3ed --- /dev/null +++ b/vendor/github.com/sromku/go-gitter/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/sromku/go-gitter/faye.go b/vendor/github.com/sromku/go-gitter/faye.go new file mode 100644 index 00000000..dcd3e210 --- /dev/null +++ b/vendor/github.com/sromku/go-gitter/faye.go @@ -0,0 +1,70 @@ +package gitter + +import ( + "encoding/json" + "fmt" + + "github.com/mrexodia/wray" +) + +type Faye struct { + endpoint string + Event chan Event + client *wray.FayeClient + gitter *Gitter +} + +func (gitter *Gitter) Faye(roomID string) *Faye { + wray.RegisterTransports([]wray.Transport{ + &wray.HttpTransport{ + SendHook: func(data map[string]interface{}) { + if channel, ok := data["channel"]; ok && channel == "/meta/handshake" { + data["ext"] = map[string]interface{}{"token": gitter.config.token} + } + }, + }, + }) + return &Faye{ + endpoint: "/api/v1/rooms/" + roomID + "/chatMessages", + Event: make(chan Event), + client: wray.NewFayeClient(fayeBaseURL), + gitter: gitter, + } +} + +func (faye *Faye) Listen() { + defer faye.destroy() + + faye.client.Subscribe(faye.endpoint, false, func(message wray.Message) { + dataBytes, err := json.Marshal(message.Data["model"]) + if err != nil { + fmt.Printf("JSON Marshal error: %v\n", err) + return + } + var gitterMessage Message + err = json.Unmarshal(dataBytes, &gitterMessage) + if err != nil { + fmt.Printf("JSON Unmarshal error: %v\n", err) + return + } + faye.Event <- Event{ + Data: &MessageReceived{ + Message: gitterMessage, + }, + } + }) + + //TODO: this might be needed in the future + /*go func() { + for { + faye.client.Publish("/api/v1/ping2", map[string]interface{}{"reason": "ping"}) + time.Sleep(60 * time.Second) + } + }()*/ + + faye.client.Listen() +} + +func (faye *Faye) destroy() { + close(faye.Event) +} diff --git a/vendor/github.com/sromku/go-gitter/gitter.go b/vendor/github.com/sromku/go-gitter/gitter.go new file mode 100644 index 00000000..0eff42a8 --- /dev/null +++ b/vendor/github.com/sromku/go-gitter/gitter.go @@ -0,0 +1,468 @@ +// Package gitter is a Go client library for the Gitter API. +// +// Author: sromku +package gitter + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/mreiferson/go-httpclient" +) + +var ( + apiBaseURL = "https://api.gitter.im/v1/" + streamBaseURL = "https://stream.gitter.im/v1/" + fayeBaseURL = "https://ws.gitter.im/faye" +) + +type Gitter struct { + config struct { + apiBaseURL string + streamBaseURL string + token string + client *http.Client + } + debug bool + logWriter io.Writer +} + +// New initializes the Gitter API client +// +// For example: +// api := gitter.New("YOUR_ACCESS_TOKEN") +func New(token string) *Gitter { + + transport := &httpclient.Transport{ + ConnectTimeout: 5 * time.Second, + ReadWriteTimeout: 40 * time.Second, + } + defer transport.Close() + + s := &Gitter{} + s.config.apiBaseURL = apiBaseURL + s.config.streamBaseURL = streamBaseURL + s.config.token = token + s.config.client = &http.Client{ + Transport: transport, + } + return s +} + +// SetClient sets a custom http client. Can be useful in App Engine case. +func (gitter *Gitter) SetClient(client *http.Client) { + gitter.config.client = client +} + +// GetUser returns the current user +func (gitter *Gitter) GetUser() (*User, error) { + + var users []User + response, err := gitter.get(gitter.config.apiBaseURL + "user") + if err != nil { + gitter.log(err) + return nil, err + } + + err = json.Unmarshal(response, &users) + if err != nil { + gitter.log(err) + return nil, err + } + + if len(users) > 0 { + return &users[0], nil + } + + err = APIError{What: "Failed to retrieve current user"} + gitter.log(err) + return nil, err +} + +// GetUserRooms returns a list of Rooms the user is part of +func (gitter *Gitter) GetUserRooms(userID string) ([]Room, error) { + + var rooms []Room + response, err := gitter.get(gitter.config.apiBaseURL + "user/" + userID + "/rooms") + if err != nil { + gitter.log(err) + return nil, err + } + + err = json.Unmarshal(response, &rooms) + if err != nil { + gitter.log(err) + return nil, err + } + + return rooms, nil +} + +// GetRooms returns a list of rooms the current user is in +func (gitter *Gitter) GetRooms() ([]Room, error) { + + var rooms []Room + response, err := gitter.get(gitter.config.apiBaseURL + "rooms") + if err != nil { + gitter.log(err) + return nil, err + } + + err = json.Unmarshal(response, &rooms) + if err != nil { + gitter.log(err) + return nil, err + } + + return rooms, nil +} + +// GetUsersInRoom returns the users in the room with the passed id +func (gitter *Gitter) GetUsersInRoom(roomID string) ([]User, error) { + var users []User + response, err := gitter.get(gitter.config.apiBaseURL + "rooms/" + roomID + "/users") + if err != nil { + gitter.log(err) + return nil, err + } + + err = json.Unmarshal(response, &users) + if err != nil { + gitter.log(err) + return nil, err + } + return users, nil +} + +// GetRoom returns a room with the passed id +func (gitter *Gitter) GetRoom(roomID string) (*Room, error) { + + var room Room + response, err := gitter.get(gitter.config.apiBaseURL + "rooms/" + roomID) + if err != nil { + gitter.log(err) + return nil, err + } + + err = json.Unmarshal(response, &room) + if err != nil { + gitter.log(err) + return nil, err + } + + return &room, nil +} + +// GetMessages returns a list of messages in a room. +// Pagination is optional. You can pass nil or specific pagination params. +func (gitter *Gitter) GetMessages(roomID string, params *Pagination) ([]Message, error) { + + var messages []Message + url := gitter.config.apiBaseURL + "rooms/" + roomID + "/chatMessages" + if params != nil { + url += "?" + params.encode() + } + response, err := gitter.get(url) + if err != nil { + gitter.log(err) + return nil, err + } + + err = json.Unmarshal(response, &messages) + if err != nil { + gitter.log(err) + return nil, err + } + + return messages, nil +} + +// GetMessage returns a message in a room. +func (gitter *Gitter) GetMessage(roomID, messageID string) (*Message, error) { + + var message Message + response, err := gitter.get(gitter.config.apiBaseURL + "rooms/" + roomID + "/chatMessages/" + messageID) + if err != nil { + gitter.log(err) + return nil, err + } + + err = json.Unmarshal(response, &message) + if err != nil { + gitter.log(err) + return nil, err + } + + return &message, nil +} + +// SendMessage sends a message to a room +func (gitter *Gitter) SendMessage(roomID, text string) error { + + message := Message{Text: text} + body, _ := json.Marshal(message) + _, err := gitter.post(gitter.config.apiBaseURL+"rooms/"+roomID+"/chatMessages", body) + if err != nil { + gitter.log(err) + return err + } + + return nil +} + +// JoinRoom joins a room +func (gitter *Gitter) JoinRoom(roomID, userID string) (*Room, error) { + + message := Room{ID: roomID} + body, _ := json.Marshal(message) + response, err := gitter.post(gitter.config.apiBaseURL+"user/"+userID+"/rooms", body) + + if err != nil { + gitter.log(err) + return nil, err + } + + var room Room + err = json.Unmarshal(response, &room) + if err != nil { + gitter.log(err) + return nil, err + } + + return &room, nil +} + +// LeaveRoom removes a user from the room +func (gitter *Gitter) LeaveRoom(roomID, userID string) error { + + _, err := gitter.delete(gitter.config.apiBaseURL + "rooms/" + roomID + "/users/" + userID) + if err != nil { + gitter.log(err) + return err + } + + return nil +} + +// SetDebug traces errors if it's set to true. +func (gitter *Gitter) SetDebug(debug bool, logWriter io.Writer) { + gitter.debug = debug + gitter.logWriter = logWriter +} + +// SearchRooms queries the Rooms resources of gitter API +func (gitter *Gitter) SearchRooms(room string) ([]Room, error) { + + var rooms struct { + Results []Room `json:"results"` + } + + response, err := gitter.get(gitter.config.apiBaseURL + "rooms?q=" + room ) + + if err != nil { + gitter.log(err) + return nil, err + } + + err = json.Unmarshal(response, &rooms) + if err != nil { + gitter.log(err) + return nil, err + } + return rooms.Results, nil +} + +// GetRoomId returns the room ID of a given URI +func (gitter *Gitter) GetRoomId(uri string) (string, error) { + + rooms, err := gitter.SearchRooms(uri) + if err != nil { + gitter.log(err) + return "", err + } + + for _, element := range rooms { + if element.URI == uri { + return element.ID, nil + } + } + return "", APIError{What: "Room not found."} +} + +// Pagination params +type Pagination struct { + + // Skip n messages + Skip int + + // Get messages before beforeId + BeforeID string + + // Get messages after afterId + AfterID string + + // Maximum number of messages to return + Limit int + + // Search query + Query string +} + +func (messageParams *Pagination) encode() string { + values := url.Values{} + + if messageParams.AfterID != "" { + values.Add("afterId", messageParams.AfterID) + } + + if messageParams.BeforeID != "" { + values.Add("beforeId", messageParams.BeforeID) + } + + if messageParams.Skip > 0 { + values.Add("skip", strconv.Itoa(messageParams.Skip)) + } + + if messageParams.Limit > 0 { + values.Add("limit", strconv.Itoa(messageParams.Limit)) + } + + return values.Encode() +} + +func (gitter *Gitter) getResponse(url string, stream *Stream) (*http.Response, error) { + r, err := http.NewRequest("GET", url, nil) + if err != nil { + gitter.log(err) + return nil, err + } + r.Header.Set("Content-Type", "application/json") + r.Header.Set("Accept", "application/json") + r.Header.Set("Authorization", "Bearer "+gitter.config.token) + if stream != nil { + stream.streamConnection.request = r + } + response, err := gitter.config.client.Do(r) + if err != nil { + gitter.log(err) + return nil, err + } + return response, nil +} + +func (gitter *Gitter) get(url string) ([]byte, error) { + resp, err := gitter.getResponse(url, nil) + if err != nil { + gitter.log(err) + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)} + gitter.log(err) + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + gitter.log(err) + return nil, err + } + + return body, nil +} + +func (gitter *Gitter) post(url string, body []byte) ([]byte, error) { + r, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) + if err != nil { + gitter.log(err) + return nil, err + } + + r.Header.Set("Content-Type", "application/json") + r.Header.Set("Accept", "application/json") + r.Header.Set("Authorization", "Bearer "+gitter.config.token) + + resp, err := gitter.config.client.Do(r) + if err != nil { + gitter.log(err) + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)} + gitter.log(err) + return nil, err + } + + result, err := ioutil.ReadAll(resp.Body) + if err != nil { + gitter.log(err) + return nil, err + } + + return result, nil +} + +func (gitter *Gitter) delete(url string) ([]byte, error) { + r, err := http.NewRequest("delete", url, nil) + if err != nil { + gitter.log(err) + return nil, err + } + + r.Header.Set("Content-Type", "application/json") + r.Header.Set("Accept", "application/json") + r.Header.Set("Authorization", "Bearer "+gitter.config.token) + + resp, err := gitter.config.client.Do(r) + if err != nil { + gitter.log(err) + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err = APIError{What: fmt.Sprintf("Status code: %v", resp.StatusCode)} + gitter.log(err) + return nil, err + } + + result, err := ioutil.ReadAll(resp.Body) + if err != nil { + gitter.log(err) + return nil, err + } + + return result, nil +} + +func (gitter *Gitter) log(a interface{}) { + if gitter.debug { + log.Println(a) + if gitter.logWriter != nil { + timestamp := time.Now().Format(time.RFC3339) + msg := fmt.Sprintf("%v: %v", timestamp, a) + fmt.Fprintln(gitter.logWriter, msg) + } + } +} + +// APIError holds data of errors returned from the API. +type APIError struct { + What string +} + +func (e APIError) Error() string { + return fmt.Sprintf("%v", e.What) +} diff --git a/vendor/github.com/sromku/go-gitter/model.go b/vendor/github.com/sromku/go-gitter/model.go new file mode 100644 index 00000000..7a3d0729 --- /dev/null +++ b/vendor/github.com/sromku/go-gitter/model.go @@ -0,0 +1,142 @@ +package gitter + +import "time" + +// A Room in Gitter can represent a GitHub Organization, a GitHub Repository, a Gitter Channel or a One-to-one conversation. +// In the case of the Organizations and Repositories, the access control policies are inherited from GitHub. +type Room struct { + + // Room ID + ID string `json:"id"` + + // Room name + Name string `json:"name"` + + // Room topic. (default: GitHub repo description) + Topic string `json:"topic"` + + // Room URI on Gitter + URI string `json:"uri"` + + // Indicates if the room is a one-to-one chat + OneToOne bool `json:"oneToOne"` + + // Count of users in the room + UserCount int `json:"userCount"` + + // Number of unread messages for the current user + UnreadItems int `json:"unreadItems"` + + // Number of unread mentions for the current user + Mentions int `json:"mentions"` + + // Last time the current user accessed the room in ISO format + LastAccessTime time.Time `json:"lastAccessTime"` + + // Indicates if the current user has disabled notifications + Lurk bool `json:"lurk"` + + // Path to the room on gitter + URL string `json:"url"` + + // Type of the room + // - ORG: A room that represents a GitHub Organization. + // - REPO: A room that represents a GitHub Repository. + // - ONETOONE: A one-to-one chat. + // - ORG_CHANNEL: A Gitter channel nested under a GitHub Organization. + // - REPO_CHANNEL A Gitter channel nested under a GitHub Repository. + // - USER_CHANNEL A Gitter channel nested under a GitHub User. + GithubType string `json:"githubType"` + + // Tags that define the room + Tags []string `json:"tags"` + + RoomMember bool `json:"roomMember"` + + // Room version. + Version int `json:"v"` +} + +type User struct { + + // Gitter User ID + ID string `json:"id"` + + // Gitter/GitHub username + Username string `json:"username"` + + // Gitter/GitHub user real name + DisplayName string `json:"displayName"` + + // Path to the user on Gitter + URL string `json:"url"` + + // User avatar URI (small) + AvatarURLSmall string `json:"avatarUrlSmall"` + + // User avatar URI (medium) + AvatarURLMedium string `json:"avatarUrlMedium"` +} + +type Message struct { + + // ID of the message + ID string `json:"id"` + + // Original message in plain-text/markdown + Text string `json:"text"` + + // HTML formatted message + HTML string `json:"html"` + + // ISO formatted date of the message + Sent time.Time `json:"sent"` + + // ISO formatted date of the message if edited + EditedAt time.Time `json:"editedAt"` + + // User that sent the message + From User `json:"fromUser"` + + // Boolean that indicates if the current user has read the message. + Unread bool `json:"unread"` + + // Number of users that have read the message + ReadBy int `json:"readBy"` + + // List of URLs present in the message + Urls []URL `json:"urls"` + + // List of @Mentions in the message + Mentions []Mention `json:"mentions"` + + // List of #Issues referenced in the message + Issues []Issue `json:"issues"` + + // Version + Version int `json:"v"` +} + +// Mention holds data about mentioned user in the message +type Mention struct { + + // User's username + ScreenName string `json:"screenName"` + + // Gitter User ID + UserID string `json:"userID"` +} + +// Issue references issue in the message +type Issue struct { + + // Issue number + Number string `json:"number"` +} + +// URL presented in the message +type URL struct { + + // URL + URL string `json:"url"` +} diff --git a/vendor/github.com/sromku/go-gitter/stream.go b/vendor/github.com/sromku/go-gitter/stream.go new file mode 100644 index 00000000..4a5a3c68 --- /dev/null +++ b/vendor/github.com/sromku/go-gitter/stream.go @@ -0,0 +1,221 @@ +package gitter + +import ( + "bufio" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/mreiferson/go-httpclient" +) + +var defaultConnectionWaitTime time.Duration = 3000 // millis +var defaultConnectionMaxRetries = 5 + +// Stream initialize stream +func (gitter *Gitter) Stream(roomID string) *Stream { + return &Stream{ + url: streamBaseURL + "rooms/" + roomID + "/chatMessages", + Event: make(chan Event), + gitter: gitter, + streamConnection: gitter.newStreamConnection( + defaultConnectionWaitTime, + defaultConnectionMaxRetries), + } +} + +// Implemented to conform with https://developer.gitter.im/docs/streaming-api +func (gitter *Gitter) Listen(stream *Stream) { + + defer stream.destroy() + + var reader *bufio.Reader + var gitterMessage Message + lastKeepalive := time.Now().Unix() + + // connect + stream.connect() + +Loop: + for { + + // if closed then stop trying + if stream.isClosed() { + stream.Event <- Event{ + Data: &GitterConnectionClosed{}, + } + break Loop + } + + resp := stream.getResponse() + if resp.StatusCode != 200 { + gitter.log(fmt.Sprintf("Unexpected response code %v", resp.StatusCode)) + continue + } + + //"The JSON stream returns messages as JSON objects that are delimited by carriage return (\r)" <- Not true crap it's (\n) only + reader = bufio.NewReader(resp.Body) + line, err := reader.ReadBytes('\n') + if err != nil { + gitter.log("ReadBytes error: " + err.Error()) + stream.connect() + continue + } + + //Check if the line only consists of whitespace + onlyWhitespace := true + for _, b := range line { + if b != ' ' && b != '\t' && b != '\r' && b != '\n' { + onlyWhitespace = false + } + } + + if onlyWhitespace { + //"Parsers must be tolerant of occasional extra newline characters placed between messages." + currentKeepalive := time.Now().Unix() //interesting behavior of 100+ keepalives per seconds was observed + if currentKeepalive-lastKeepalive > 10 { + lastKeepalive = currentKeepalive + gitter.log("Keepalive was received") + } + continue + } else if stream.isClosed() { + gitter.log("Stream closed") + continue + } + + // unmarshal the streamed data + err = json.Unmarshal(line, &gitterMessage) + if err != nil { + gitter.log("JSON Unmarshal error: " + err.Error()) + continue + } + + // we are here, then we got the good message. pipe it forward. + stream.Event <- Event{ + Data: &MessageReceived{ + Message: gitterMessage, + }, + } + } + + gitter.log("Listening was completed") +} + +// Stream holds stream data. +type Stream struct { + url string + Event chan Event + streamConnection *streamConnection + gitter *Gitter +} + +func (stream *Stream) destroy() { + close(stream.Event) +} + +type Event struct { + Data interface{} +} + +type GitterConnectionClosed struct { +} + +type MessageReceived struct { + Message Message +} + +// connect and try to reconnect with +func (stream *Stream) connect() { + + if stream.streamConnection.retries == stream.streamConnection.currentRetries { + stream.Close() + stream.gitter.log("Number of retries exceeded the max retries number, we are done here") + return + } + + res, err := stream.gitter.getResponse(stream.url, stream) + if stream.streamConnection.canceled { + // do nothing + } else if err != nil || res.StatusCode != 200 { + stream.gitter.log("Failed to get response, trying reconnect ") + stream.gitter.log(err) + + // sleep and wait + stream.streamConnection.currentRetries++ + time.Sleep(time.Millisecond * stream.streamConnection.wait * time.Duration(stream.streamConnection.currentRetries)) + + // connect again + stream.Close() + stream.connect() + } else { + stream.gitter.log("Response was received") + stream.streamConnection.currentRetries = 0 + stream.streamConnection.closed = false + stream.streamConnection.response = res + } +} + +type streamConnection struct { + + // connection was closed + closed bool + + // canceled + canceled bool + + // wait time till next try + wait time.Duration + + // max tries to recover + retries int + + // current streamed response + response *http.Response + + // current request + request *http.Request + + // current status + currentRetries int +} + +// Close the stream connection and stop receiving streamed data +func (stream *Stream) Close() { + conn := stream.streamConnection + conn.closed = true + if conn.response != nil { + stream.gitter.log("Stream connection close response") + defer conn.response.Body.Close() + } + if conn.request != nil { + stream.gitter.log("Stream connection close request") + switch transport := stream.gitter.config.client.Transport.(type) { + case *httpclient.Transport: + stream.streamConnection.canceled = true + transport.CancelRequest(conn.request) + default: + } + + } + conn.currentRetries = 0 +} + +func (stream *Stream) isClosed() bool { + return stream.streamConnection.closed +} + +func (stream *Stream) getResponse() *http.Response { + return stream.streamConnection.response +} + +// Optional, set stream connection properties +// wait - time in milliseconds of waiting between reconnections. Will grow exponentially. +// retries - number of reconnections retries before dropping the stream. +func (gitter *Gitter) newStreamConnection(wait time.Duration, retries int) *streamConnection { + return &streamConnection{ + closed: true, + wait: wait, + retries: retries, + } +} diff --git a/vendor/github.com/sromku/go-gitter/test_utils.go b/vendor/github.com/sromku/go-gitter/test_utils.go new file mode 100644 index 00000000..6703da2e --- /dev/null +++ b/vendor/github.com/sromku/go-gitter/test_utils.go @@ -0,0 +1,30 @@ +package gitter + +import ( + "net/http" + "net/http/httptest" + "net/url" +) + +var ( + mux *http.ServeMux + gitter *Gitter + server *httptest.Server +) + +func setup() { + mux = http.NewServeMux() + server = httptest.NewServer(mux) + + gitter = New("abc") + + // Fake the API and Stream base URLs by using the test + // server URL instead. + url, _ := url.Parse(server.URL) + gitter.config.apiBaseURL = url.String() + "/" + gitter.config.streamBaseURL = url.String() + "/" +} + +func teardown() { + server.Close() +} diff --git a/vendor/manifest b/vendor/manifest index 1c810a72..c93d9984 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -1,32 +1,6 @@ { "version": 0, "dependencies": [ - { - "importpath": "github.com/42wim/go-gitter", - "repository": "https://github.com/42wim/go-gitter", - "vcs": "git", - "revision": "ae777f740326ef6b4d910496022649859232ff38", - "branch": "fixloop", - "notests": true - }, - { - "importpath": "github.com/42wim/matterbridge-plus/bridge", - "repository": "https://github.com/42wim/matterbridge-plus", - "vcs": "git", - "revision": "63c7caabf4b92fa47060cf37beb0ed814cefbaea", - "branch": "master", - "path": "/bridge", - "notests": true - }, - { - "importpath": "github.com/42wim/matterbridge-plus/matterclient", - "repository": "https://github.com/42wim/matterbridge-plus", - "vcs": "git", - "revision": "63c7caabf4b92fa47060cf37beb0ed814cefbaea", - "branch": "master", - "path": "/matterclient", - "notests": true - }, { "importpath": "github.com/BurntSushi/toml", "repository": "https://github.com/BurntSushi/toml", @@ -55,7 +29,7 @@ "importpath": "github.com/bwmarrin/discordgo", "repository": "https://github.com/bwmarrin/discordgo", "vcs": "git", - "revision": "11fa9dc906c531a85cd969b7b6b77c2f452a61b3", + "revision": "5835676872d7fa65ee37b32672079c4ede2c1855", "branch": "master", "notests": true }, @@ -113,7 +87,7 @@ "importpath": "github.com/mattn/go-xmpp", "repository": "https://github.com/mattn/go-xmpp", "vcs": "git", - "revision": "f4550b5399387339df5ce4c3f88c1ef85333bdd5", + "revision": "325c112042ffd34aa24365590d3a6fdd1a79c011", "branch": "master", "notests": true }, @@ -174,6 +148,14 @@ "branch": "master", "notests": true }, + { + "importpath": "github.com/sromku/go-gitter", + "repository": "https://github.com/sromku/go-gitter", + "vcs": "git", + "revision": "792abbfcc172b35bbf5f8b260ef184e38bbd90bd", + "branch": "master", + "notests": true + }, { "importpath": "github.com/technoweenie/multipartstreamer", "repository": "https://github.com/technoweenie/multipartstreamer", -- cgit v1.2.3