Package list golang-github-nlopes-slack / 16288f9
Major changes: Slack => Client, SlackWS => RTM. All events are now pointers (HelloEvent was not a pointer, and LatencyReport not either). Changed import names to "github.com/abourget/slack", since changes are quite major. Keep what's in BaseChannel instead of duplicating in Group/Channel. Added IsGroup to Channel type. Updated example to reflect new RTM API. Added Debugf/Debugln on the Client, respects the "SetDebug" flag. Got rid of Ping, Keepalive, and HandleIncomingEvents. Replaced by ManageConnection(), rtm.IncomingEvents, and listening for LatencyReports. Ping is automatic, each 30 seconds, and will help trigger a reconnect faster. Reconnections are now handled automatically, and yield events through the IncomingEvents chan. Added: * "ConnectedEvent" event (with Info and ConnectionCount) * "ConnectingEvent" event, with attempt count and connection count. * "DisconnectedEvent" Alexandre Bourget authored 6 years ago Norberto Lopes committed 6 years ago
20 changed file(s) with 570 addition(s) and 361 deletion(s). Raw diff Collapse all Expand all
0 # Slack API For Go [![Build Status](https://travis-ci.org/nlopes/slack.svg)](https://travis-ci.org/nlopes/slack)
0 Slack API in Go [![GoDoc](https://godoc.org/github.com/nlopes/slack?status.png)](https://godoc.org/github.com/nlopes/slack) [![Build Status](https://travis-ci.org/nlopes/slack.svg)](https://travis-ci.org/nlopes/slack)
1 ===============
12
2 [![GoDoc](https://godoc.org/github.com/nlopes/slack?status.png)](https://godoc.org/github.com/nlopes/slack)
3 This library supports most if not all of the `api.slack.com` REST
4 calls, as well as the Real-Time Messaging protocol over websocket, in
5 a fully managed way.
6
7 This fork breaks many things `github.com/nlopes/slack`, and improves
8 the RTM a lot.
39
410 ## Installing
511
5056 fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email)
5157 }
5258
53 ## Why?
54 I am currently learning Go and this seemed like a good idea.
59 ## Minimal RTM usage:
5560
56 ## Stability
57 As with any other piece of software expect bugs. Also, the design isn't finalized yet because I am not happy with how I laid out some things. Especially the websocket stuff. It is functional but very incomplete and buggy.
5861
59 ## Help
60 Anyone is welcome to contribute. Either open a PR or create an issue.
62
63
64 ## Contributing
65
66 You are more than welcome to contribute to this project. Fork and
67 make a Pull Request, or create an Issue if you see any problem.
6168
6269 ## License
70
6371 BSD 2 Clause license
0 package slack
1
2 import (
3 "math"
4 "math/rand"
5 "time"
6 )
7
8 // This one was ripped from https://github.com/jpillora/backoff/blob/master/backoff.go
9
10 // Backoff is a time.Duration counter. It starts at Min. After every
11 // call to Duration() it is multiplied by Factor. It is capped at
12 // Max. It returns to Min on every call to Reset(). Used in
13 // conjunction with the time package.
14 type backoff struct {
15 //Factor is the multiplying factor for each increment step
16 attempts, Factor float64
17 //Jitter eases contention by randomizing backoff steps
18 Jitter bool
19 //Min and Max are the minimum and maximum values of the counter
20 Min, Max time.Duration
21 }
22
23 // Returns the current value of the counter and then multiplies it
24 // Factor
25 func (b *backoff) Duration() time.Duration {
26 //Zero-values are nonsensical, so we use
27 //them to apply defaults
28 if b.Min == 0 {
29 b.Min = 100 * time.Millisecond
30 }
31 if b.Max == 0 {
32 b.Max = 10 * time.Second
33 }
34 if b.Factor == 0 {
35 b.Factor = 2
36 }
37 //calculate this duration
38 dur := float64(b.Min) * math.Pow(b.Factor, b.attempts)
39 if b.Jitter == true {
40 dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min)
41 }
42 //cap!
43 if dur > float64(b.Max) {
44 return b.Max
45 }
46 //bump attempts count
47 b.attempts++
48 //return as a time.Duration
49 return time.Duration(dur)
50 }
51
52 //Resets the current value of the counter back to Min
53 func (b *backoff) Reset() {
54 b.attempts = 0
55 }
3636 }
3737
3838 // ArchiveChannel archives the given channel
39 func (api *Slack) ArchiveChannel(channel string) error {
39 func (api *Client) ArchiveChannel(channel string) error {
4040 values := url.Values{
4141 "token": {api.config.token},
4242 "channel": {channel},
4949 }
5050
5151 // UnarchiveChannel unarchives the given channel
52 func (api *Slack) UnarchiveChannel(channel string) error {
52 func (api *Client) UnarchiveChannel(channel string) error {
5353 values := url.Values{
5454 "token": {api.config.token},
5555 "channel": {channel},
6262 }
6363
6464 // CreateChannel creates a channel with the given name and returns a *Channel
65 func (api *Slack) CreateChannel(channel string) (*Channel, error) {
65 func (api *Client) CreateChannel(channel string) (*Channel, error) {
6666 values := url.Values{
6767 "token": {api.config.token},
6868 "name": {channel},
7575 }
7676
7777 // GetChannelHistory retrieves the channel history
78 func (api *Slack) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
78 func (api *Client) GetChannelHistory(channel string, params HistoryParameters) (*History, error) {
7979 values := url.Values{
8080 "token": {api.config.token},
8181 "channel": {channel},
104104 }
105105
106106 // GetChannelInfo retrieves the given channel
107 func (api *Slack) GetChannelInfo(channel string) (*Channel, error) {
107 func (api *Client) GetChannelInfo(channel string) (*Channel, error) {
108108 values := url.Values{
109109 "token": {api.config.token},
110110 "channel": {channel},
117117 }
118118
119119 // InviteUserToChannel invites a user to a given channel and returns a *Channel
120 func (api *Slack) InviteUserToChannel(channel, user string) (*Channel, error) {
120 func (api *Client) InviteUserToChannel(channel, user string) (*Channel, error) {
121121 values := url.Values{
122122 "token": {api.config.token},
123123 "channel": {channel},
131131 }
132132
133133 // JoinChannel joins the currently authenticated user to a channel
134 func (api *Slack) JoinChannel(channel string) (*Channel, error) {
134 func (api *Client) JoinChannel(channel string) (*Channel, error) {
135135 values := url.Values{
136136 "token": {api.config.token},
137137 "name": {channel},
144144 }
145145
146146 // LeaveChannel makes the authenticated user leave the given channel
147 func (api *Slack) LeaveChannel(channel string) (bool, error) {
147 func (api *Client) LeaveChannel(channel string) (bool, error) {
148148 values := url.Values{
149149 "token": {api.config.token},
150150 "channel": {channel},
160160 }
161161
162162 // KickUserFromChannel kicks a user from a given channel
163 func (api *Slack) KickUserFromChannel(channel, user string) error {
163 func (api *Client) KickUserFromChannel(channel, user string) error {
164164 values := url.Values{
165165 "token": {api.config.token},
166166 "channel": {channel},
174174 }
175175
176176 // GetChannels retrieves all the channels
177 func (api *Slack) GetChannels(excludeArchived bool) ([]Channel, error) {
177 func (api *Client) GetChannels(excludeArchived bool) ([]Channel, error) {
178178 values := url.Values{
179179 "token": {api.config.token},
180180 }
193193 // timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls
194194 // (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A
195195 // timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
196 func (api *Slack) SetChannelReadMark(channel, ts string) error {
196 func (api *Client) SetChannelReadMark(channel, ts string) error {
197197 values := url.Values{
198198 "token": {api.config.token},
199199 "channel": {channel},
207207 }
208208
209209 // RenameChannel renames a given channel
210 func (api *Slack) RenameChannel(channel, name string) (*Channel, error) {
210 func (api *Client) RenameChannel(channel, name string) (*Channel, error) {
211211 values := url.Values{
212212 "token": {api.config.token},
213213 "channel": {channel},
225225
226226 // SetChannelPurpose sets the channel purpose and returns the purpose that was
227227 // successfully set
228 func (api *Slack) SetChannelPurpose(channel, purpose string) (string, error) {
228 func (api *Client) SetChannelPurpose(channel, purpose string) (string, error) {
229229 values := url.Values{
230230 "token": {api.config.token},
231231 "channel": {channel},
239239 }
240240
241241 // SetChannelTopic sets the channel topic and returns the topic that was successfully set
242 func (api *Slack) SetChannelTopic(channel, topic string) (string, error) {
242 func (api *Client) SetChannelTopic(channel, topic string) (string, error) {
243243 values := url.Values{
244244 "token": {api.config.token},
245245 "channel": {channel},
7272 }
7373
7474 // DeleteMessage deletes a message in a channel
75 func (api *Slack) DeleteMessage(channel, messageTimestamp string) (string, string, error) {
75 func (api *Client) DeleteMessage(channel, messageTimestamp string) (string, string, error) {
7676 values := url.Values{
7777 "token": {api.config.token},
7878 "channel": {channel},
9393 // PostMessage sends a message to a channel.
9494 // Message is escaped by default according to https://api.slack.com/docs/formatting
9595 // Use http://davestevens.github.io/slack-message-builder/ to help crafting your message.
96 func (api *Slack) PostMessage(channel string, text string, params PostMessageParameters) (string, string, error) {
96 func (api *Client) PostMessage(channel, text string, params PostMessageParameters) (channel, timestamp string, err error) {
9797 if params.EscapeText {
9898 text = escapeMessage(text)
9999 }
145145 }
146146
147147 // UpdateMessage updates a message in a channel
148 func (api *Slack) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
148 func (api *Client) UpdateMessage(channel, timestamp, text string) (string, string, string, error) {
149149 values := url.Values{
150150 "token": {api.config.token},
151151 "channel": {channel},
1010 }
1111
1212 // GetEmoji retrieves all the emojis
13 func (api *Slack) GetEmoji() (map[string]string, error) {
13 func (api *Client) GetEmoji() (map[string]string, error) {
1414 values := url.Values{
1515 "token": {api.config.token},
1616 }
11
22 import (
33 "fmt"
4 "time"
54
6 "github.com/nlopes/slack"
5 "github.com/abourget/slack"
76 )
87
98 func main() {
109 chSender := make(chan slack.OutgoingMessage)
11 chReceiver := make(chan slack.SlackEvent)
1210
1311 api := slack.New("YOUR TOKEN HERE")
1412 api.SetDebug(true)
15 wsAPI, err := api.StartRTM("", "http://example.com")
16 if err != nil {
17 _ = fmt.Errorf("%s\n", err)
18 }
19 go wsAPI.HandleIncomingEvents(chReceiver)
20 go wsAPI.Keepalive(20 * time.Second)
21 go func(wsAPI *slack.WS, chSender chan slack.OutgoingMessage) {
13
14 rtm := api.NewRTM()
15 go rtm.ManageConnection()
16
17 go func(rtm *slack.RTM, chSender chan slack.OutgoingMessage) {
2218 for {
2319 select {
2420 case msg := <-chSender:
25 wsAPI.SendMessage(&msg)
21 rtm.SendMessage(&msg)
2622 }
2723 }
28 }(wsAPI, chSender)
24 }(rtm, chSender)
25
2926 for {
3027 select {
31 case msg := <-chReceiver:
28 case msg := <-rtm.IncomingEvents:
3229 fmt.Print("Event Received: ")
33 switch msg.Data.(type) {
34 case slack.HelloEvent:
30 switch ev := msg.Data.(type) {
31 case *slack.HelloEvent:
3532 // Ignore hello
33
34 case *slack.ConnectedEvent:
35 fmt.Println("Infos:", ev.Info)
36 fmt.Println("Connection counter:", ev.ConnectionCount)
37 rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "#general"))
38
3639 case *slack.MessageEvent:
37 a := msg.Data.(*slack.MessageEvent)
38 fmt.Printf("Message: %v\n", a)
40 fmt.Printf("Message: %v\n", ev)
41
3942 case *slack.PresenceChangeEvent:
40 a := msg.Data.(*slack.PresenceChangeEvent)
41 fmt.Printf("Presence Change: %v\n", a)
42 case slack.LatencyReport:
43 a := msg.Data.(slack.LatencyReport)
44 fmt.Printf("Current latency: %v\n", a.Value)
45 case *slack.WSError:
46 error := msg.Data.(*slack.WSError)
47 fmt.Printf("Error: %d - %s\n", error.Code, error.Msg)
43 fmt.Printf("Presence Change: %v\n", ev)
44
45 case *slack.LatencyReport:
46 fmt.Printf("Current latency: %v\n", ev.Value)
47
48 case *slack.SlackWSError:
49 fmt.Printf("Error: %d - %s\n", ev.Code, ev.Msg)
50
4851 default:
49 fmt.Printf("Unexpected: %v\n", msg.Data)
52 // Ignore other events..
53 //fmt.Printf("Unexpected: %v\n", msg.Data)
5054 }
5155 }
5256 }
119119 }
120120
121121 // GetFileInfo retrieves a file and related comments
122 func (api *Slack) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) {
122 func (api *Client) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) {
123123 values := url.Values{
124124 "token": {api.config.token},
125125 "file": {fileID},
134134 }
135135
136136 // GetFiles retrieves all files according to the parameters given
137 func (api *Slack) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
137 func (api *Client) GetFiles(params GetFilesParameters) ([]File, *Paging, error) {
138138 values := url.Values{
139139 "token": {api.config.token},
140140 }
165165 }
166166
167167 // UploadFile uploads a file
168 func (api *Slack) UploadFile(params FileUploadParameters) (file *File, err error) {
168 func (api *Client) UploadFile(params FileUploadParameters) (file *File, err error) {
169169 // Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
170170 // investigation needed, but for now this will do.
171171 _, err = api.AuthTest()
207207 }
208208
209209 // DeleteFile deletes a file
210 func (api *Slack) DeleteFile(fileID string) error {
210 func (api *Client) DeleteFile(fileID string) error {
211211 values := url.Values{
212212 "token": {api.config.token},
213213 "file": {fileID},
3838 return response, nil
3939 }
4040
41 // ArchiveGroup archives a private group
42 func (api *Slack) ArchiveGroup(group string) error {
41 func (api *Client) ArchiveGroup(group string) error {
4342 values := url.Values{
4443 "token": {api.config.token},
4544 "channel": {group},
5251 }
5352
5453 // UnarchiveGroup unarchives a private group
55 func (api *Slack) UnarchiveGroup(group string) error {
54 func (api *Client) UnarchiveGroup(group string) error {
5655 values := url.Values{
5756 "token": {api.config.token},
5857 "channel": {group},
6564 }
6665
6766 // CreateGroup creates a private group
68 func (api *Slack) CreateGroup(group string) (*Group, error) {
67 func (api *Client) CreateGroup(group string) (*Group, error) {
6968 values := url.Values{
7069 "token": {api.config.token},
7170 "name": {group},
8382 // 2. Archives the existing group.
8483 // 3. Creates a new group with the name of the existing group.
8584 // 4. Adds all members of the existing group to the new group.
86 func (api *Slack) CreateChildGroup(group string) (*Group, error) {
85 func (api *Client) CreateChildGroup(group string) (*Group, error) {
8786 values := url.Values{
8887 "token": {api.config.token},
8988 "channel": {group},
9594 return &response.Group, nil
9695 }
9796
98 // CloseGroup closes a private group
99 func (api *Slack) CloseGroup(group string) (bool, bool, error) {
97 func (api *Client) CloseGroup(group string) (bool, bool, error) {
10098 values := url.Values{
10199 "token": {api.config.token},
102100 "channel": {group},
108106 return response.NoOp, response.AlreadyClosed, nil
109107 }
110108
111 // GetGroupHistory retrieves message history for a give group
112 func (api *Slack) GetGroupHistory(group string, params HistoryParameters) (*History, error) {
109 func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*History, error) {
113110 values := url.Values{
114111 "token": {api.config.token},
115112 "channel": {group},
137134 return &response.History, nil
138135 }
139136
140 // InviteUserToGroup invites a user to a group
141 func (api *Slack) InviteUserToGroup(group, user string) (*Group, bool, error) {
137 func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) {
142138 values := url.Values{
143139 "token": {api.config.token},
144140 "channel": {group},
152148 }
153149
154150 // LeaveGroup makes authenticated user leave the group
155 func (api *Slack) LeaveGroup(group string) error {
151 func (api *Client) LeaveGroup(group string) error {
156152 values := url.Values{
157153 "token": {api.config.token},
158154 "channel": {group},
165161 }
166162
167163 // KickUserFromGroup kicks a user from a group
168 func (api *Slack) KickUserFromGroup(group, user string) error {
164 func (api *Client) KickUserFromGroup(group, user string) error {
169165 values := url.Values{
170166 "token": {api.config.token},
171167 "channel": {group},
179175 }
180176
181177 // GetGroups retrieves all groups
182 func (api *Slack) GetGroups(excludeArchived bool) ([]Group, error) {
178 func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
183179 values := url.Values{
184180 "token": {api.config.token},
185181 }
194190 }
195191
196192 // GetGroupInfo retrieves the given group
197 func (api *Slack) GetGroupInfo(group string) (*Group, error) {
193 func (api *Client) GetGroupInfo(group string) (*Group, error) {
198194 values := url.Values{
199195 "token": {api.config.token},
200196 "channel": {group},
211207 // timer before making the call. In this way, any further updates needed during the timeout will not generate extra
212208 // calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live
213209 // channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
214 func (api *Slack) SetGroupReadMark(group, ts string) error {
210 func (api *Client) SetGroupReadMark(group, ts string) error {
215211 values := url.Values{
216212 "token": {api.config.token},
217213 "channel": {group},
225221 }
226222
227223 // OpenGroup opens a private group
228 func (api *Slack) OpenGroup(group string) (bool, bool, error) {
224 func (api *Client) OpenGroup(group string) (bool, bool, error) {
229225 values := url.Values{
230226 "token": {api.config.token},
231227 "user": {group},
240236 // RenameGroup renames a group
241237 // XXX: They return a channel, not a group. What is this crap? :(
242238 // Inconsistent api it seems.
243 func (api *Slack) RenameGroup(group, name string) (*Channel, error) {
239 func (api *Client) RenameGroup(group, name string) (*Channel, error) {
244240 values := url.Values{
245241 "token": {api.config.token},
246242 "channel": {group},
257253 }
258254
259255 // SetGroupPurpose sets the group purpose
260 func (api *Slack) SetGroupPurpose(group, purpose string) (string, error) {
256 func (api *Client) SetGroupPurpose(group, purpose string) (string, error) {
261257 values := url.Values{
262258 "token": {api.config.token},
263259 "channel": {group},
271267 }
272268
273269 // SetGroupTopic sets the group topic
274 func (api *Slack) SetGroupTopic(group, topic string) (string, error) {
270 func (api *Client) SetGroupTopic(group, topic string) (string, error) {
275271 values := url.Values{
276272 "token": {api.config.token},
277273 "channel": {group},
4040 }
4141
4242 // CloseIMChannel closes the direct message channel
43 func (api *Slack) CloseIMChannel(channel string) (bool, bool, error) {
43 func (api *Client) CloseIMChannel(channel string) (bool, bool, error) {
4444 values := url.Values{
4545 "token": {api.config.token},
4646 "channel": {channel},
5353 }
5454
5555 // OpenIMChannel opens a direct message channel to the user provided as argument
56 // Returns some status and the channel
57 func (api *Slack) OpenIMChannel(user string) (bool, bool, string, error) {
56 // Returns some status and the channel ID
57 func (api *Client) OpenIMChannel(user string) (bool, bool, string, error) {
5858 values := url.Values{
5959 "token": {api.config.token},
6060 "user": {user},
6767 }
6868
6969 // MarkIMChannel sets the read mark of a direct message channel to a specific point
70 func (api *Slack) MarkIMChannel(channel, ts string) (err error) {
70 func (api *Client) MarkIMChannel(channel, ts string) (err error) {
7171 values := url.Values{
7272 "token": {api.config.token},
7373 "channel": {channel},
8181 }
8282
8383 // GetIMHistory retrieves the direct message channel history
84 func (api *Slack) GetIMHistory(channel string, params HistoryParameters) (*History, error) {
84 func (api *Client) GetIMHistory(channel string, params HistoryParameters) (*History, error) {
8585 values := url.Values{
8686 "token": {api.config.token},
8787 "channel": {channel},
110110 }
111111
112112 // GetIMChannels returns the list of direct message channels
113 func (api *Slack) GetIMChannels() ([]IM, error) {
113 func (api *Client) GetIMChannels() ([]IM, error) {
114114 values := url.Values{
115115 "token": {api.config.token},
116116 }
142142 }
143143
144144 // Info contains various details about Users, Channels, Bots and the authenticated user.
145 // It is returned by StartRTM
145 // It is returned by StartRTM or included in the "ConnectedEvent" RTM event.
146146 type Info struct {
147147 URL string `json:"url,omitempty"`
148148 User *UserDetails `json:"self,omitempty"`
101101 ReplyTo int `json:"reply_to"`
102102 }
103103
104 // NewOutgoingMessage prepares an OutgoingMessage that the user can use to send a message
105 func (api *WS) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
106 api.mutex.Lock()
107 defer api.mutex.Unlock()
108 api.messageID++
104 // NewOutgoingMessage prepares an OutgoingMessage that the user can
105 // use to send a message. Use this function to properly set the
106 // messageID.
107 func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
108 rtm.mutex.Lock()
109 defer rtm.mutex.Unlock()
110 rtm.messageID++
109111 return &OutgoingMessage{
110 ID: api.messageID,
111 Type: "message",
112 Channel: channel,
113 Text: text,
112 Id: rtm.messageID,
113 Type: "message",
114 ChannelId: channel,
115 Text: text,
114116 }
115117 }
6363 return err
6464 }
6565
66 // FIXME: will be api.Debugf
6667 if debug {
6768 log.Printf("parseResponseBody: %s\n", string(response))
6869 }
0 package slack
1
2 import (
3 "fmt"
4 "net/url"
5 )
6
7 // StartRTM calls the "rtm.start" endpoint and returns the provided URL and the full Info
8 // block.
9 //
10 // To have a fully managed Websocket connection, use `NewRTM`, and call `ManageConnection()`
11 // on it.
12 func (api *Client) StartRTM() (info *Info, websocketURL string, err error) {
13 response := &infoResponseFull{}
14 err = post("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
15 if err != nil {
16 return nil, "", fmt.Errorf("post: %s", err)
17 }
18 if !response.Ok {
19 return nil, "", fmt.Errorf("post response: %s", response.Error)
20 }
21
22 // websocket.Dial does not accept url without the port (yet)
23 // Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
24 // but slack returns the address with no port, so we have to fix it
25 api.Debugln("Using URL:", response.Info.URL)
26 websocketURL, err = websocketizeUrlPort(response.Info.URL)
27 if err != nil {
28 return nil, "", fmt.Errorf("parsing response URL: %s", err)
29 }
30
31 return &response.Info, websocketURL, nil
32 }
33
34 // NewRTM returns a RTM, which provides a fully managed connection to
35 // Slack's websocket-based Real-Time Messaging protocol./
36 func (api *Client) NewRTM() *RTM {
37 return newRTM(api)
38 }
7979 }
8080 }
8181
82 func (api *Slack) _search(path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
82 func (api *Client) _search(path, query string, params SearchParameters, files, messages bool) (response *searchResponseFull, error error) {
8383 values := url.Values{
8484 "token": {api.config.token},
8585 "query": {query},
111111
112112 }
113113
114 func (api *Slack) Search(query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
114 func (api *Client) Search(query string, params SearchParameters) (*SearchMessages, *SearchFiles, error) {
115115 response, err := api._search("search.all", query, params, true, true)
116116 if err != nil {
117117 return nil, nil, err
119119 return &response.SearchMessages, &response.SearchFiles, nil
120120 }
121121
122 func (api *Slack) SearchFiles(query string, params SearchParameters) (*SearchFiles, error) {
122 func (api *Client) SearchFiles(query string, params SearchParameters) (*SearchFiles, error) {
123123 response, err := api._search("search.files", query, params, true, false)
124124 if err != nil {
125125 return nil, err
127127 return &response.SearchFiles, nil
128128 }
129129
130 func (api *Slack) SearchMessages(query string, params SearchParameters) (*SearchMessages, error) {
130 func (api *Client) SearchMessages(query string, params SearchParameters) (*SearchMessages, error) {
131131 response, err := api._search("search.messages", query, params, false, true)
132132 if err != nil {
133133 return nil, err
11
22 import (
33 "errors"
4 "log"
45 "net/url"
56 )
67
2829 AuthTestResponse
2930 }
3031
31 type Slack struct {
32 type Client struct {
3233 config struct {
3334 token string
3435 }
3637 debug bool
3738 }
3839
39 func New(token string) *Slack {
40 s := &Slack{}
40 func New(token string) *Client {
41 s := &Client{}
4142 s.config.token = token
4243 return s
4344 }
4445
4546 // AuthTest tests if the user is able to do authenticated requests or not
46 func (api *Slack) AuthTest() (response *AuthTestResponse, error error) {
47 func (api *Client) AuthTest() (response *AuthTestResponse, error error) {
4748 responseFull := &authTestResponseFull{}
4849 err := post("auth.test", url.Values{"token": {api.config.token}}, responseFull, api.debug)
4950 if err != nil {
5859 // SetDebug switches the api into debug mode
5960 // When in debug mode, it logs various info about what its doing
6061 // If you ever use this in production, don't call SetDebug(true)
61 func (api *Slack) SetDebug(debug bool) {
62 func (api *Client) SetDebug(debug bool) {
6263 api.debug = debug
6364 }
65
66 func (api *Client) Debugf(format string, v ...interface{}) {
67 if api.debug {
68 log.Printf(format, v...)
69 }
70 }
71
72 func (api *Client) Debugln(v ...interface{}) {
73 if api.debug {
74 log.Println(v...)
75 }
76 }
4646 // ...
4747 // }
4848 // }
49 func (api *Slack) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) {
49 func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) {
5050 values := url.Values{
5151 "token": {api.config.token},
5252 }
7474 }
7575
7676 // GetUserPresence will retrieve the current presence status of given user.
77 func (api *Slack) GetUserPresence(user string) (*UserPresence, error) {
77 func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
7878 values := url.Values{
7979 "token": {api.config.token},
8080 "user": {user},
8787 }
8888
8989 // GetUserInfo will retrive the complete user information
90 func (api *Slack) GetUserInfo(user string) (*User, error) {
90 func (api *Client) GetUserInfo(user string) (*User, error) {
9191 values := url.Values{
9292 "token": {api.config.token},
9393 "user": {user},
100100 }
101101
102102 // GetUsers returns the list of users (with their detailed information)
103 func (api *Slack) GetUsers() ([]User, error) {
103 func (api *Client) GetUsers() ([]User, error) {
104104 values := url.Values{
105105 "token": {api.config.token},
106106 }
112112 }
113113
114114 // SetUserAsActive marks the currently authenticated user as active
115 func (api *Slack) SetUserAsActive() error {
115 func (api *Client) SetUserAsActive() error {
116116 values := url.Values{
117117 "token": {api.config.token},
118118 }
124124 }
125125
126126 // SetUserPresence changes the currently authenticated user presence
127 func (api *Slack) SetUserPresence(presence string) error {
127 func (api *Client) SetUserPresence(presence string) error {
128128 values := url.Values{
129129 "token": {api.config.token},
130130 "presence": {presence},
00 package slack
11
22 import (
3 "encoding/json"
4 "fmt"
5 "io"
63 "log"
7 "net/url"
8 "reflect"
94 "sync"
105 "time"
116
127 "golang.org/x/net/websocket"
138 )
149
15 // SlackWS represents a managed websocket connection. It also supports all the methods of the `Slack` type.
16 type SlackWS struct {
10 // RTM represents a managed websocket connection. It also supports
11 // all the methods of the `Slack` type.
12 type RTM struct {
1713 mutex sync.Mutex
18 messageId int
14 messageID int
1915 pings map[int]time.Time
2016
2117 // Connection life-cycle
2218 conn *websocket.Conn
2319 IncomingEvents chan SlackEvent
24 connectionErrors chan error
25 killRoutines chan bool
20 outgoingMessages chan OutgoingMessage
2621
2722 // Slack is the main API, embedded
28 Slack
23 Client
24 websocketURL string
2925
3026 // UserDetails upon connection
3127 info *Info
3228 }
3329
34 // StartRTM starts a Websocket used to do all common chat client operations.
35 func (api *Slack) StartRTM() (*SlackWS, error) {
36 response := &infoResponseFull{}
37 err := post("rtm.start", url.Values{"token": {api.config.token}}, response, api.debug)
38 if err != nil {
39 return nil, err
30 // NewRTM returns a RTM, which provides a fully managed connection to
31 // Slack's websocket-based Real-Time Messaging protocol.
32 func newRTM(api *Client) *RTM {
33 return &RTM{
34 Client: *api,
35 pings: make(map[int]time.Time),
36 IncomingEvents: make(chan SlackEvent, 50),
4037 }
41 if !response.Ok {
42 return nil, response.Error
43 }
44
45 // websocket.Dial does not accept url without the port (yet)
46 // Fixed by: https://github.com/golang/net/commit/5058c78c3627b31e484a81463acd51c7cecc06f3
47 // but slack returns the address with no port, so we have to fix it
48 websocketUrl, err := websocketizeUrlPort(response.Info.URL)
49 if err != nil {
50 return nil, err
51 }
52
53 ws := &SlackWS{
54 Slack: *api,
55 info: &response.Info,
56 }
57 ws.pings = make(map[int]time.Time)
58
59 ws.conn, err = websocket.Dial(websocketUrl, "", "")
60 if err != nil {
61 return nil, err
62 }
63
64 ws.IncomingEvents = make(chan SlackEvent, 50)
65 ws.killRoutines = make(chan bool, 10)
66 ws.connectionErrors = make(chan error, 10)
67 go ws.manageConnection(websocketUrl)
68 return ws, nil
69 }
70
71 func (ws *SlackWS) manageConnection(url string) {
72 // launch keepalive with fixed timings
73 // listen on
74 // receive any connectionErrors, killall goroutines
75 // reconnect and restart them all
7638 }
7739
7840 // Disconnect and wait, blocking until a successful disconnection.
79 func (ws *SlackWS) Disconnect() error {
41 func (rtm *RTM) Disconnect() error {
42 log.Println("RTM::Disconnect not implemented!")
8043 return nil
8144 }
8245
83 // Reconnect, only makes sense if you've successfully disconnectd with Disconnect().
84 func (ws *SlackWS) Reconnect() error {
46 // Reconnect only makes sense if you've successfully disconnectd with Disconnect().
47 func (rtm *RTM) Reconnect() error {
48 log.Println("RTM::Reconnect not implemented!")
8549 return nil
8650 }
8751
8953 // "startrtm", holding all channels, groups and other metadata needed
9054 // to implement a full chat client. It will be non-nil after a call to
9155 // StartRTM().
92 func (ws *SlackWS) GetInfo() *Info {
93 return ws.info
56 func (rtm *RTM) GetInfo() *Info {
57 return rtm.info
9458 }
9559
96 func (ws *SlackWS) Ping() error {
97 ws.mutex.Lock()
98 defer ws.mutex.Unlock()
99 ws.messageId++
100 msg := &Ping{Id: ws.messageId, Type: "ping"}
101 if err := websocket.JSON.Send(ws.conn, msg); err != nil {
102 return err
103 }
104 // TODO: What happens if we already have this id?
105 ws.pings[ws.messageId] = time.Now()
106 return nil
107 }
108
109 func (ws *SlackWS) Keepalive(interval time.Duration) {
110 ticker := time.NewTicker(interval)
111 defer ticker.Stop()
112
113 for {
114 select {
115 case <-ticker.C:
116 if err := ws.Ping(); err != nil {
117 log.Fatal(err)
118 }
119 }
120 }
121 }
122
123 func (ws *SlackWS) SendMessage(msg *OutgoingMessage) error {
124 ws.mutex.Lock()
125 defer ws.mutex.Unlock()
126
60 // SendMessage submits a simple message through the websocket. For
61 // more complicated messages, use `rtm.PostMessage` with a complete
62 // struct describing your attachments and all.
63 func (rtm *RTM) SendMessage(msg *OutgoingMessage) {
12764 if msg == nil {
128 return fmt.Errorf("Can't send a nil message")
65 rtm.Debugln("Error: Attempted to SendMessage(nil)")
66 return
12967 }
13068
131 if err := websocket.JSON.Send(ws.conn, *msg); err != nil {
132 return err
133 }
134 return nil
69 rtm.outgoingMessages <- *msg
13570 }
136
137 func (ws *SlackWS) HandleIncomingEvents(ch chan SlackEvent) {
138 for {
139 event := json.RawMessage{}
140 if err := websocket.JSON.Receive(ws.conn, &event); err == io.EOF {
141 //log.Println("Derpi derp, should we destroy conn and start over?")
142 //if err = ws.StartRTM(); err != nil {
143 // log.Fatal(err)
144 //}
145 // should we reconnect here?
146
147 if !ws.conn.IsClientConn() {
148 // FIXME: take the URL from the connection manager.
149 url := "boo"
150 ws.conn, err = websocket.Dial(url, "", "")
151 if err != nil {
152 log.Panic(err)
153 }
154 }
155 // XXX: check for timeout and implement exponential backoff
156 } else if err != nil {
157 log.Panic(err)
158 }
159 if len(event) == 0 {
160 if ws.debug {
161 log.Println("Event Empty. WTF?")
162 }
163 } else {
164 if ws.debug {
165 log.Println(string(event[:]))
166 }
167 ws.handleEvent(ch, event)
168 }
169 time.Sleep(time.Millisecond * 500)
170 }
171 }
172
173 func (ws *SlackWS) handleEvent(ch chan SlackEvent, event json.RawMessage) {
174 em := Event{}
175 err := json.Unmarshal(event, &em)
176 if err != nil {
177 log.Fatal(err)
178 }
179 switch em.Type {
180 case "":
181 // try ok
182 ack := AckMessage{}
183 if err = json.Unmarshal(event, &ack); err != nil {
184 // FIXME: never do that mama!
185 log.Fatal(err)
186 }
187
188 if ack.Ok {
189 ch <- SlackEvent{"ack", ack}
190 } else {
191 ch <- SlackEvent{"error", ack.Error}
192 }
193 case "hello":
194 ch <- SlackEvent{"hello", HelloEvent{}}
195 case "pong":
196 pong := Pong{}
197 if err = json.Unmarshal(event, &pong); err != nil {
198 log.Fatal(err)
199 }
200 ws.mutex.Lock()
201 latency := time.Since(ws.pings[pong.ReplyTo])
202 ws.mutex.Unlock()
203 ch <- SlackEvent{"latency-report", LatencyReport{Value: latency}}
204 default:
205 callEvent(em.Type, ch, event)
206 }
207 }
208
209 func callEvent(eventType string, ch chan SlackEvent, event json.RawMessage) {
210 for k, v := range eventMapping {
211 if eventType == k {
212 t := reflect.TypeOf(v)
213 recvEvent := reflect.New(t).Interface()
214 err := json.Unmarshal(event, recvEvent)
215 if err != nil {
216 log.Println("Unable to unmarshal event:", eventType, event)
217 }
218 ch <- SlackEvent{k, recvEvent}
219 return
220 }
221 }
222 log.Printf("XXX: Not implemented yet: %s -> %v", eventType, event)
223 }
224
225 var eventMapping = map[string]interface{}{
226 "message": &MessageEvent{},
227 "presence_change": &PresenceChangeEvent{},
228 "user_typing": &UserTypingEvent{},
229
230 "channel_marked": &ChannelMarkedEvent{},
231 "channel_created": &ChannelCreatedEvent{},
232 "channel_joined": &ChannelJoinedEvent{},
233 "channel_left": &ChannelLeftEvent{},
234 "channel_deleted": &ChannelDeletedEvent{},
235 "channel_rename": &ChannelRenameEvent{},
236 "channel_archive": &ChannelArchiveEvent{},
237 "channel_unarchive": &ChannelUnarchiveEvent{},
238 "channel_history_changed": &ChannelHistoryChangedEvent{},
239
240 "im_created": &IMCreatedEvent{},
241 "im_open": &IMOpenEvent{},
242 "im_close": &IMCloseEvent{},
243 "im_marked": &IMMarkedEvent{},
244 "im_history_changed": &IMHistoryChangedEvent{},
245
246 "group_marked": &GroupMarkedEvent{},
247 "group_open": &GroupOpenEvent{},
248 "group_joined": &GroupJoinedEvent{},
249 "group_left": &GroupLeftEvent{},
250 "group_close": &GroupCloseEvent{},
251 "group_rename": &GroupRenameEvent{},
252 "group_archive": &GroupArchiveEvent{},
253 "group_unarchive": &GroupUnarchiveEvent{},
254 "group_history_changed": &GroupHistoryChangedEvent{},
255
256 "file_created": &FileCreatedEvent{},
257 "file_shared": &FileSharedEvent{},
258 "file_unshared": &FileUnsharedEvent{},
259 "file_public": &FilePublicEvent{},
260 "file_private": &FilePrivateEvent{},
261 "file_change": &FileChangeEvent{},
262 "file_deleted": &FileDeletedEvent{},
263 "file_comment_added": &FileCommentAddedEvent{},
264 "file_comment_edited": &FileCommentEditedEvent{},
265 "file_comment_deleted": &FileCommentDeletedEvent{},
266
267 "star_added": &StarAddedEvent{},
268 "star_removed": &StarRemovedEvent{},
269
270 "pref_change": &PrefChangeEvent{},
271
272 "team_join": &TeamJoinEvent{},
273 "team_rename": &TeamRenameEvent{},
274 "team_pref_change": &TeamPrefChangeEvent{},
275 "team_domain_change": &TeamDomainChangeEvent{},
276 "team_migration_started": &TeamMigrationStartedEvent{},
277
278 "manual_presence_change": &ManualPresenceChangeEvent{},
279
280 "user_change": &UserChangeEvent{},
281
282 "emoji_changed": &EmojiChangedEvent{},
283
284 "commands_changed": &CommandsChangedEvent{},
285
286 "email_domain_changed": &EmailDomainChangedEvent{},
287
288 "bot_added": &BotAddedEvent{},
289 "bot_changed": &BotChangedEvent{},
290
291 "accounts_changed": &AccountsChangedEvent{},
292 }
44 /**
55 * Internal events, created by this lib and not mapped to Slack APIs.
66 */
7 type Disconnected struct{}
8 type Reconnecting struct {
9 Attempt int
7 type ConnectedEvent struct {
8 ConnectionCount int // 1 = first time, 2 = second time
9 Info *Info
1010 }
11 type Connected struct{}
11
12 type ConnectingEvent struct {
13 Attempt int // 1 = first attempt, 2 = second attempt
14 ConnectionCount int
15 }
16
17 type DisconnectedEvent struct{}
1218
1319 type LatencyReport struct {
1420 Value time.Duration
0 package slack
1
2 import (
3 "encoding/json"
4 "fmt"
5 "reflect"
6 "time"
7
8 "golang.org/x/net/websocket"
9 )
10
11 // ManageConnection is a long-running goroutine that handles
12 // reconnections and piping messages back and to `rtm.IncomingEvents`
13 // and `rtm.OutgoingMessages`.
14 //
15 // Usage would look like:
16 //
17 // bot := slack.New("my-token")
18 // rtm := bot.NewRTM() // check err
19 // setupYourHandlers(rtm.IncomingEvents, rtm.OutgoingMessages)
20 // rtm.ManageConnection()
21 //
22 func (rtm *RTM) ManageConnection() {
23 boff := &backoff{
24 Min: 100 * time.Millisecond,
25 Max: 5 * time.Minute,
26 Factor: 2,
27 Jitter: true,
28 }
29 connectionCount := 0
30
31 for {
32 var conn *websocket.Conn // use as first
33 var err error
34 var info *Info
35
36 connectionCount += 1
37
38 attempts := 1
39 boff.Reset()
40 for {
41 rtm.IncomingEvents <- SlackEvent{"connecting", &ConnectingEvent{
42 Attempt: attempts,
43 ConnectionCount: connectionCount,
44 }}
45
46 info, conn, err = rtm.startRTMAndDial()
47 if err == nil {
48 break // connected
49 }
50
51 dur := boff.Duration()
52 rtm.Debugf("reconnection %d failed: %s", attempts, err)
53 rtm.Debugln(" -> reconnecting in", dur)
54 attempts += 1
55 time.Sleep(dur)
56 }
57
58 rtm.IncomingEvents <- SlackEvent{"connected", &ConnectedEvent{
59 ConnectionCount: connectionCount,
60 Info: info,
61 }}
62
63 killCh := make(chan bool, 3)
64 connErrors := make(chan error, 10) // in case we get many such errors
65
66 go rtm.keepalive(30*time.Second, conn, killCh, connErrors)
67 go rtm.handleIncomingEvents(conn, killCh, connErrors)
68 go rtm.handleOutgoingMessages(conn, killCh, connErrors)
69
70 // Here, block and await for disconnection, if it ever happens.
71 err = <-connErrors
72
73 rtm.Debugln("RTM connection error:", err)
74 rtm.IncomingEvents <- SlackEvent{"disconnected", &DisconnectedEvent{}}
75 killCh <- true // 3 child go-routines
76 killCh <- true
77 killCh <- true
78 }
79 }
80
81 func (rtm *RTM) startRTMAndDial() (*Info, *websocket.Conn, error) {
82 info, url, err := rtm.StartRTM()
83 if err != nil {
84 return nil, nil, err
85 }
86
87 conn, err := websocket.Dial(url, "", "http://api.slack.com")
88 if err != nil {
89 return nil, nil, err
90 }
91
92 return info, conn, err
93 }
94
95 func (rtm *RTM) keepalive(interval time.Duration, conn *websocket.Conn, killCh chan bool, errors chan error) {
96 ticker := time.NewTicker(interval)
97 defer ticker.Stop()
98
99 for {
100 select {
101 case <-killCh:
102 return
103 case <-ticker.C:
104 rtm.ping(conn, errors)
105 }
106 }
107 }
108
109 func (rtm *RTM) ping(conn *websocket.Conn, errors chan error) {
110 rtm.mutex.Lock()
111 defer rtm.mutex.Unlock()
112
113 rtm.messageID++
114 rtm.pings[rtm.messageID] = time.Now()
115
116 msg := &Ping{Id: rtm.messageID, Type: "ping"}
117 if err := websocket.JSON.Send(conn, msg); err != nil {
118 errors <- fmt.Errorf("error sending 'ping': %s", err)
119 }
120 }
121
122 func (rtm *RTM) handleOutgoingMessages(conn *websocket.Conn, killCh chan bool, errors chan error) {
123 // we pass "conn" in case we do a reconnection, in that case we'll
124 // have a new `conn` even though we're dealing with the same
125 // incoming and outgoing channels for messages/events.
126 for {
127 select {
128 case <-killCh:
129 return
130
131 case msg := <-rtm.outgoingMessages:
132 rtm.Debugln("Sending message:", msg)
133
134 rtm.mutex.Lock()
135 err := websocket.JSON.Send(conn, msg)
136 rtm.mutex.Unlock()
137 if err != nil {
138 errors <- fmt.Errorf("error sending 'message': %s", err)
139 return
140 }
141 }
142 }
143 }
144
145 func (rtm *RTM) handleIncomingEvents(conn *websocket.Conn, killCh chan bool, errors chan error) {
146 for {
147
148 select {
149 case <-killCh:
150 return
151 default:
152 }
153
154 event := json.RawMessage{}
155 err := websocket.JSON.Receive(conn, &event)
156 if err != nil {
157 errors <- err
158 return
159 }
160 if len(event) == 0 {
161 //log.Println("Event Empty. WTF?")
162 continue
163 }
164
165 rtm.Debugln("Incoming event:", string(event[:]))
166
167 rtm.handleEvent(event)
168
169 // FIXME: please I hope we don't need to sleep!!!
170 //time.Sleep(time.Millisecond * 500)
171 }
172 }
173
174 func (rtm *RTM) handleEvent(event json.RawMessage) {
175 em := Event{}
176 err := json.Unmarshal(event, &em)
177 if err != nil {
178 rtm.Debugln("RTM Error unmarshalling event:", err)
179 rtm.Debugln(" -> Erroneous event:", string(event))
180 return
181 }
182
183 switch em.Type {
184 case "":
185 // try ok
186 ack := AckMessage{}
187 if err = json.Unmarshal(event, &ack); err != nil {
188 rtm.Debugln("RTM Error unmarshalling 'ack' event:", err)
189 rtm.Debugln(" -> Erroneous 'ack' event:", string(event))
190 return
191 }
192
193 if ack.Ok {
194 rtm.IncomingEvents <- SlackEvent{"ack", ack}
195 } else {
196 rtm.IncomingEvents <- SlackEvent{"error", ack.Error}
197 }
198
199 case "hello":
200 rtm.IncomingEvents <- SlackEvent{"hello", &HelloEvent{}}
201
202 case "pong":
203 pong := Pong{}
204 if err = json.Unmarshal(event, &pong); err != nil {
205 rtm.Debugln("RTM Error unmarshalling 'pong' event:", err)
206 rtm.Debugln(" -> Erroneous 'ping' event:", string(event))
207 return
208 }
209
210 rtm.mutex.Lock()
211 latency := time.Since(rtm.pings[pong.ReplyTo])
212 rtm.mutex.Unlock()
213
214 rtm.IncomingEvents <- SlackEvent{"latency-report", &LatencyReport{Value: latency}}
215
216 default:
217 for k, v := range eventMapping {
218 if em.Type == k {
219 t := reflect.TypeOf(v)
220 recvEvent := reflect.New(t).Interface()
221
222 err := json.Unmarshal(event, recvEvent)
223 if err != nil {
224 rtm.Debugf("RTM Error unmarshalling %q event: %s", em.Type, err)
225 rtm.Debugf(" -> Erroneous %q event: %s", em.Type, string(event))
226 return
227 }
228
229 rtm.IncomingEvents <- SlackEvent{em.Type, recvEvent}
230 return
231 }
232 }
233
234 rtm.Debugf("RTM Error, received unmapped event %q: %s\n", em.Type, string(event))
235 }
236 }
237
238 var eventMapping = map[string]interface{}{
239 "message": MessageEvent{},
240 "presence_change": PresenceChangeEvent{},
241 "user_typing": UserTypingEvent{},
242
243 "channel_marked": ChannelMarkedEvent{},
244 "channel_created": ChannelCreatedEvent{},
245 "channel_joined": ChannelJoinedEvent{},
246 "channel_left": ChannelLeftEvent{},
247 "channel_deleted": ChannelDeletedEvent{},
248 "channel_rename": ChannelRenameEvent{},
249 "channel_archive": ChannelArchiveEvent{},
250 "channel_unarchive": ChannelUnarchiveEvent{},
251 "channel_history_changed": ChannelHistoryChangedEvent{},
252
253 "im_created": IMCreatedEvent{},
254 "im_open": IMOpenEvent{},
255 "im_close": IMCloseEvent{},
256 "im_marked": IMMarkedEvent{},
257 "im_history_changed": IMHistoryChangedEvent{},
258
259 "group_marked": GroupMarkedEvent{},
260 "group_open": GroupOpenEvent{},
261 "group_joined": GroupJoinedEvent{},
262 "group_left": GroupLeftEvent{},
263 "group_close": GroupCloseEvent{},
264 "group_rename": GroupRenameEvent{},
265 "group_archive": GroupArchiveEvent{},
266 "group_unarchive": GroupUnarchiveEvent{},
267 "group_history_changed": GroupHistoryChangedEvent{},
268
269 "file_created": FileCreatedEvent{},
270 "file_shared": FileSharedEvent{},
271 "file_unshared": FileUnsharedEvent{},
272 "file_public": FilePublicEvent{},
273 "file_private": FilePrivateEvent{},
274 "file_change": FileChangeEvent{},
275 "file_deleted": FileDeletedEvent{},
276 "file_comment_added": FileCommentAddedEvent{},
277 "file_comment_edited": FileCommentEditedEvent{},
278 "file_comment_deleted": FileCommentDeletedEvent{},
279
280 "star_added": StarAddedEvent{},
281 "star_removed": StarRemovedEvent{},
282
283 "pref_change": PrefChangeEvent{},
284
285 "team_join": TeamJoinEvent{},
286 "team_rename": TeamRenameEvent{},
287 "team_pref_change": TeamPrefChangeEvent{},
288 "team_domain_change": TeamDomainChangeEvent{},
289 "team_migration_started": TeamMigrationStartedEvent{},
290
291 "manual_presence_change": ManualPresenceChangeEvent{},
292
293 "user_change": UserChangeEvent{},
294
295 "emoji_changed": EmojiChangedEvent{},
296
297 "commands_changed": CommandsChangedEvent{},
298
299 "email_domain_changed": EmailDomainChangedEvent{},
300
301 "bot_added": BotAddedEvent{},
302 "bot_changed": BotChangedEvent{},
303
304 "accounts_changed": AccountsChangedEvent{},
305 }