Codebase list golang-github-nlopes-slack / 334c7e2
parseResponse -> "post".. it doesn't just parse.. it actually dial out to do the request. parseResponseMultipart => postWithMultipartResponse too. Got rid of the "Config"... we don't need "protocol" and "origin" for the websocket on Slack, no need to keep that around. So only "token" was left. Simplified parseResponseBody.. was slightly convoluted. Attached the "search" to the Slack methods. This way, we don,t need to pass the token or the debug flag. Will do the same with "post()" next. Moved things around in websocket.go websocket_*.go Stubbed "manageConnection" connect/disconnect manager. Alexandre Bourget authored 8 years ago Norberto Lopes committed 8 years ago
17 changed file(s) with 172 addition(s) and 159 deletion(s). Raw diff Collapse all Expand all
2525
2626 func channelRequest(path string, values url.Values, debug bool) (*channelResponseFull, error) {
2727 response := &channelResponseFull{}
28 err := parseResponse(path, values, response, debug)
28 err := post(path, values, response, debug)
2929 if err != nil {
3030 return nil, err
3131 }
6161
6262 func chatRequest(path string, values url.Values, debug bool) (*chatResponseFull, error) {
6363 response := &chatResponseFull{}
64 err := parseResponse(path, values, response, debug)
64 err := post(path, values, response, debug)
6565 if err != nil {
6666 return nil, err
6767 }
9292
9393 // PostMessage sends a message to a channel.
9494 // Message is escaped by default according to https://api.slack.com/docs/formatting
95 // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
9596 func (api *Slack) PostMessage(channel string, text string, params PostMessageParameters) (string, string, error) {
9697 if params.EscapeText {
9798 text = escapeMessage(text)
+0
-10
config.go less more
0 package slack
1
2 // Config contains some config parameters needed
3 // Token always needs to be set for the api to function
4 // Origin and Protocol are optional and only needed for websocket
5 type Config struct {
6 token string
7 origin string
8 protocol string
9 }
1515 "token": {api.config.token},
1616 }
1717 response := &emojiResponseFull{}
18 err := parseResponse("emoji.list", values, response, api.debug)
18 err := post("emoji.list", values, response, api.debug)
1919 if err != nil {
2020 return nil, err
2121 }
108108
109109 func fileRequest(path string, values url.Values, debug bool) (*fileResponseFull, error) {
110110 response := &fileResponseFull{}
111 err := parseResponse(path, values, response, debug)
111 err := post(path, values, response, debug)
112112 if err != nil {
113113 return nil, err
114114 }
193193 }
194194 if params.Content != "" {
195195 values.Add("content", params.Content)
196 err = parseResponse("files.upload", values, response, api.debug)
196 err = post("files.upload", values, response, api.debug)
197197 } else if params.File != "" {
198 err = parseResponseMultipart("files.upload", params.File, values, response, api.debug)
198 err = postWithMultipartResponse("files.upload", params.File, values, response, api.debug)
199199 }
200200 if err != nil {
201201 return nil, err
2828
2929 func groupRequest(path string, values url.Values, debug bool) (*groupResponseFull, error) {
3030 response := &groupResponseFull{}
31 err := parseResponse(path, values, response, debug)
31 err := post(path, values, response, debug)
3232 if err != nil {
3333 return nil, err
3434 }
2929
3030 func imRequest(path string, values url.Values, debug bool) (*imResponseFull, error) {
3131 response := &imResponseFull{}
32 err := parseResponse(path, values, response, debug)
32 err := post(path, values, response, debug)
3333 if err != nil {
3434 return nil, err
3535 }
5858 }
5959
6060 func parseResponseBody(body io.ReadCloser, intf *interface{}, debug bool) error {
61 var decoder *json.Decoder
62 if debug {
63 response, err := ioutil.ReadAll(body)
64 if err != nil {
65 return err
66 }
67 log.Println(string(response))
68 decoder = json.NewDecoder(bytes.NewReader(response))
69 } else {
70 decoder = json.NewDecoder(body)
71 }
72 if err := decoder.Decode(&intf); err != nil {
61 response, err := ioutil.ReadAll(body)
62 if err != nil {
7363 return err
7464 }
65
66 if debug {
67 log.Printf("parseResponseBody: %s\n", string(response))
68 }
69
70 err = json.Unmarshal(response, &intf)
71 if err != nil {
72 return err
73 }
74
7575 return nil
76
7776 }
7877
79 func parseResponseMultipart(path string, filepath string, values url.Values, intf interface{}, debug bool) error {
78 func postWithMultipartResponse(path string, filepath string, values url.Values, intf interface{}, debug bool) error {
8079 req, err := fileUploadReq(SLACK_API+path, filepath, values)
8180 client := &http.Client{}
8281 resp, err := client.Do(req)
9695 return parseResponseBody(resp.Body, &intf, debug)
9796 }
9897
99 func parseResponse(path string, values url.Values, intf interface{}, debug bool) error {
98 func post(path string, values url.Values, intf interface{}, debug bool) error {
10099 return postForm(SLACK_API+path, values, intf, debug)
101100 }
102101
3939 "token": {validToken},
4040 }
4141 responsePartial := &SlackResponse{}
42 err := parseResponse("parseResponse", values, responsePartial, false)
42 err := post("parseResponse", values, responsePartial, false)
4343 if err != nil {
4444 t.Errorf("Unexpected error: %s", err)
4545 }
5151 SLACK_API = "http://" + serverAddr + "/"
5252 values := url.Values{}
5353 responsePartial := &SlackResponse{}
54 err := parseResponse("parseResponse", values, responsePartial, false)
54 err := post("parseResponse", values, responsePartial, false)
5555 if err != nil {
5656 t.Errorf("Unexpected error: %s", err)
5757 return
7171 "token": {"whatever"},
7272 }
7373 responsePartial := &SlackResponse{}
74 err := parseResponse("parseResponse", values, responsePartial, false)
74 err := post("parseResponse", values, responsePartial, false)
7575 if err != nil {
7676 t.Errorf("Unexpected error: %s", err)
7777 return
1919 "redirect_uri": {redirectURI},
2020 }
2121 response := &oAuthResponseFull{}
22 err = parseResponse("oauth.access", values, response, debug)
22 err = post("oauth.access", values, response, debug)
2323 if err != nil {
2424 return "", "", err
2525 }
7979 }
8080 }
8181
82 func search(token, path, query string, params SearchParameters, files, messages, debug bool) (response *searchResponseFull, error error) {
82 func (api *Slack) _search(path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
8383 values := url.Values{
84 "token": {token},
84 "token": {api.config.token},
8585 "query": {query},
8686 }
8787 if params.Sort != DEFAULT_SEARCH_SORT {
100100 values.Add("page", strconv.Itoa(params.Page))
101101 }
102102 response = &searchResponseFull{}
103 err := parseResponse(path, values, response, debug)
103 err := post(path, values, response, api.debug)
104104 if err != nil {
105105 return nil, err
106106 }
112112 }
113113
114114 func (api *Slack) Search(query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
115 response, err := search(api.config.token, "search.all", query, params, true, true, api.debug)
115 response, err := api._search("search.all", query, params, true, true)
116116 if err != nil {
117117 return nil, nil, err
118118 }
120120 }
121121
122122 func (api *Slack) SearchFiles(query string, params SearchParameters) (*SearchFiles, error) {
123 response, err := search(api.config.token, "search.files", query, params, true, false, api.debug)
123 response, err := api._search("search.files", query, params, true, false)
124124 if err != nil {
125125 return nil, err
126126 }
128128 }
129129
130130 func (api *Slack) SearchMessages(query string, params SearchParameters) (*SearchMessages, error) {
131 response, err := search(api.config.token, "search.messages", query, params, false, true, api.debug)
131 response, err := api._search("search.messages", query, params, false, true)
132132 if err != nil {
133133 return nil, err
134134 }
2929 }
3030
3131 type Slack struct {
32 config Config
33 info Info
34 debug bool
32 config struct {
33 token string
34 }
35 info Info
36 debug bool
3537 }
3638
3739 func New(token string) *Slack {
38 return &Slack{
39 config: Config{token: token},
40 }
40 s := &Slack{}
41 s.config.token = token
42 return s
4143 }
4244
4345 func (api *Slack) GetInfo() Info {
4749 // AuthTest tests if the user is able to do authenticated requests or not
4850 func (api *Slack) AuthTest() (response *AuthTestResponse, error error) {
4951 responseFull := &authTestResponseFull{}
50 err := parseResponse("auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug)
52 err := post("auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug)
5153 if err != nil {
5254 return nil, err
5355 }
6060 values.Add("page", strconv.Itoa(params.Page))
6161 }
6262 response := &starsResponseFull{}
63 err := parseResponse("stars.list", values, response, api.debug)
63 err := post("stars.list", values, response, api.debug)
6464 if err != nil {
6565 return nil, nil, err
6666 }
6363
6464 func userRequest(path string, values url.Values, debug bool) (*userResponseFull, error) {
6565 response := &userResponseFull{}
66 err := parseResponse(path, values, response, debug)
66 err := post(path, values, response, debug)
6767 if err != nil {
6868 return nil, err
6969 }
44 "fmt"
55 "io"
66 "log"
7 "net"
87 "net/url"
98 "reflect"
10 "strconv"
119 "sync"
1210 "time"
1311
1412 "golang.org/x/net/websocket"
1513 )
1614
17 // MessageEvent represents the Message event
18 type MessageEvent Message
19
20 // WS contains websocket metadata
21 type WS struct {
22 conn *websocket.Conn
23 messageID int
15 // SlackWS represents a managed websocket connection. It also supports all the methods of the `Slack` type.
16 type SlackWS struct {
2417 mutex sync.Mutex
18 messageId int
2519 pings map[int]time.Time
20
21 // Connection life-cycle
22 conn *websocket.Conn
23 IncomingEvents chan SlackEvent
24 connectionErrors chan error
25 killRoutines chan bool
26
27 // Slack is the main API, embedded
2628 Slack
2729 }
2830
29 // SlackEvent is the main wrapper. You will find all the other messages attached
30 type SlackEvent struct {
31 Type string
32 Data interface{}
33 }
34
35 // AckMessage is used for messages received in reply to other messages
36 type AckMessage struct {
37 ReplyTo int `json:"reply_to"`
38 Timestamp string `json:"ts"`
39 Text string `json:"text"`
40 WSResponse
41 }
42
43 // WSResponse is a websocket response
44 type WSResponse struct {
45 Ok bool `json:"ok"`
46 Error *WSError `json:"error"`
47 }
48
49 // WSError is a websocket error
50 type WSError struct {
51 Code int
52 Msg string
53 }
54
55 // JSONTimeString is a JSON unix timestamp
56 type JSONTimeString string
57
58 // String converts the unix timestamp into a string
59 func (t JSONTimeString) String() string {
60 if t == "" {
61 return ""
62 }
63 floatN, err := strconv.ParseFloat(string(t), 64)
64 if err != nil {
65 log.Panicln(err)
66 return ""
67 }
68 timeStr := int64(floatN)
69 tm := time.Unix(int64(timeStr), 0)
70 return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
71 }
72
73 func (s WSError) Error() string {
74 return s.Msg
75 }
76
77 var portMapping = map[string]string{"ws": "80", "wss": "443"}
78
79 func fixURLPort(orig string) (string, error) {
80 urlObj, err := url.ParseRequestURI(orig)
81 if err != nil {
82 return "", err
83 }
84 _, _, err = net.SplitHostPort(urlObj.Host)
85 if err != nil {
86 return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
87 }
88 return orig, nil
89 }
90
91 func (api *Slack) StartRTM(protocol, origin string) (*WS, error) {
31 // StartRTM starts a Websocket used to do all common chat client operations.
32 func (api *Slack) StartRTM() (*SlackWS, error) {
9233 response := &infoResponseFull{}
93 err := parseResponse("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
34 err := post("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
9435 if err != nil {
9536 return nil, err
9637 }
10142 // websocket.Dial does not accept url without the port (yet)
10243 // Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
10344 // but slack returns the address with no port, so we have to fix it
104 api.info.URL, err = fixURLPort(api.info.URL)
45 websocketUrl, err := websocketizeUrlPort(api.info.Url)
10546 if err != nil {
10647 return nil, err
10748 }
108 api.config.protocol, api.config.origin = protocol, origin
109 wsAPI := &WS{Slack: *api}
110 wsAPI.conn, err = websocket.Dial(api.info.URL, api.config.protocol, api.config.origin)
49 ws := &SlackWS{Slack: *api}
50 ws.pings = make(map[int]time.Time)
51 ws.conn, err = websocket.Dial(websocketUrl, "", "")
11152 if err != nil {
11253 return nil, err
11354 }
114 wsAPI.pings = make(map[int]time.Time)
115 return wsAPI, nil
116 }
117
118 func (api *WS) Ping() error {
119 api.mutex.Lock()
120 defer api.mutex.Unlock()
121 api.messageID++
122 msg := &Ping{ID: api.messageID, Type: "ping"}
123 if err := websocket.JSON.Send(api.conn, msg); err != nil {
55
56 ws.IncomingEvents = make(chan SlackEvent, 50)
57 ws.killRoutines = make(chan bool, 10)
58 ws.connectionErrors = make(chan error, 10)
59 go ws.manageConnection(websocketUrl)
60 return ws, nil
61 }
62
63 func (ws *SlackWS) manageConnection(url string) {
64 // receive any connectionErrors, killall goroutines
65 // reconnect and restart them all
66 }
67
68 func (ws *SlackWS) Ping() error {
69 ws.mutex.Lock()
70 defer ws.mutex.Unlock()
71 ws.messageId++
72 msg := &Ping{Id: ws.messageId, Type: "ping"}
73 if err := websocket.JSON.Send(ws.conn, msg); err != nil {
12474 return err
12575 }
12676 // TODO: What happens if we already have this id?
127 api.pings[api.messageID] = time.Now()
77 ws.pings[ws.messageId] = time.Now()
12878 return nil
12979 }
13080
131 func (api *WS) Keepalive(interval time.Duration) {
81 func (ws *SlackWS) Keepalive(interval time.Duration) {
13282 ticker := time.NewTicker(interval)
13383 defer ticker.Stop()
13484
13585 for {
13686 select {
13787 case <-ticker.C:
138 if err := api.Ping(); err != nil {
88 if err := ws.Ping(); err != nil {
13989 log.Fatal(err)
14090 }
14191 }
14292 }
14393 }
14494
145 func (api *WS) SendMessage(msg *OutgoingMessage) error {
146 api.mutex.Lock()
147 defer api.mutex.Unlock()
95 func (ws *SlackWS) SendMessage(msg *OutgoingMessage) error {
96 ws.mutex.Lock()
97 defer ws.mutex.Unlock()
14898
14999 if msg == nil {
150100 return fmt.Errorf("Can't send a nil message")
151101 }
152102
153 if err := websocket.JSON.Send(api.conn, *msg); err != nil {
103 if err := websocket.JSON.Send(ws.conn, *msg); err != nil {
154104 return err
155105 }
156106 return nil
157107 }
158108
159 func (api *WS) HandleIncomingEvents(ch chan SlackEvent) {
109 func (ws *SlackWS) HandleIncomingEvents(ch chan SlackEvent) {
160110 for {
161111 event := json.RawMessage{}
162 if err := websocket.JSON.Receive(api.conn, &event); err == io.EOF {
112 if err := websocket.JSON.Receive(ws.conn, &event); err == io.EOF {
163113 //log.Println("Derpi derp, should we destroy conn and start over?")
164 //if err = api.StartRTM(); err != nil {
114 //if err = ws.StartRTM(); err != nil {
165115 // log.Fatal(err)
166116 //}
167117 // should we reconnect here?
168 if !api.conn.IsClientConn() {
169 api.conn, err = websocket.Dial(api.info.URL, api.config.protocol, api.config.origin)
118
119 if !ws.conn.IsClientConn() {
120 ws.conn, err = websocket.Dial(ws.info.Url, "", "")
170121 if err != nil {
171122 log.Panic(err)
172123 }
176127 log.Panic(err)
177128 }
178129 if len(event) == 0 {
179 if api.debug {
130 if ws.debug {
180131 log.Println("Event Empty. WTF?")
181132 }
182133 } else {
183 if api.debug {
134 if ws.debug {
184135 log.Println(string(event[:]))
185136 }
186 api.handleEvent(ch, event)
137 ws.handleEvent(ch, event)
187138 }
188139 time.Sleep(time.Millisecond * 500)
189140 }
190141 }
191142
192 func (api *WS) handleEvent(ch chan SlackEvent, event json.RawMessage) {
143 func (ws *SlackWS) handleEvent(ch chan SlackEvent, event json.RawMessage) {
193144 em := Event{}
194145 err := json.Unmarshal(event, &em)
195146 if err != nil {
216167 if err = json.Unmarshal(event, &pong); err != nil {
217168 log.Fatal(err)
218169 }
219 api.mutex.Lock()
220 latency := time.Since(api.pings[pong.ReplyTo])
221 api.mutex.Unlock()
170 ws.mutex.Lock()
171 latency := time.Since(ws.pings[pong.ReplyTo])
172 ws.mutex.Unlock()
222173 ch <- SlackEvent{"latency-report", LatencyReport{Value: latency}}
223174 default:
224175 callEvent(em.Type, ch, event)
11
22 import "encoding/json"
33
4 // TODO: Probably need an error event
4
5 // AckMessage is used for messages received in reply to other messages
6 type AckMessage struct {
7 ReplyTo int `json:"reply_to"`
8 Timestamp string `json:"ts"`
9 Text string `json:"text"`
10 SlackWSResponse
11 }
12
13 type SlackWSResponse struct {
14 Ok bool `json:"ok"`
15 Error *SlackWSError `json:"error"`
16 }
17
18 type SlackWSError struct {
19 Code int
20 Msg string
21 }
22
23 func (s SlackWSError) Error() string {
24 return s.Msg
25 }
26
27 type MessageEvent Message
28
29 // SlackEvent is the main wrapper. You will find all the other messages attached
30 type SlackEvent struct {
31 Type string
32 Data interface{}
33 }
534
635 // HelloEvent represents the hello event
736 type HelloEvent struct{}
0 package slack
1
2 import (
3 "fmt"
4 "log"
5 "net"
6 "net/url"
7 "strconv"
8 "time"
9 )
10
11 type JSONTimeString string
12
13 // String converts the unix timestamp into a string
14 func (t JSONTimeString) String() string {
15 if t == "" {
16 return ""
17 }
18 floatN, err := strconv.ParseFloat(string(t), 64)
19 if err != nil {
20 log.Panicln(err)
21 return ""
22 }
23 timeStr := int64(floatN)
24 tm := time.Unix(int64(timeStr), 0)
25 return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
26 }
27
28 var portMapping = map[string]string{"ws": "80", "wss": "443"}
29
30 func websocketizeUrlPort(orig string) (string, error) {
31 urlObj, err := url.ParseRequestURI(orig)
32 if err != nil {
33 return "", err
34 }
35 _, _, err = net.SplitHostPort(urlObj.Host)
36 if err != nil {
37 return urlObj.Scheme + "://" + urlObj.Host + ":" + portMapping[urlObj.Scheme] + urlObj.Path, nil
38 }
39 return orig, nil
40 }