Codebase list golang-github-nlopes-slack / 676c3c4
Improve websocket types a bit. This makes it possible to select on the event type and act accordingly. Norberto Lopes 9 years ago
13 changed file(s) with 376 addition(s) and 51 deletion(s). Raw diff Collapse all Expand all
0 package main
1
2 import (
3 "fmt"
4 "time"
5
6 "github.com/nlopes/slack"
7 )
8
9 func main() {
10 chSender := make(chan slack.OutgoingMessage)
11 chReceiver := make(chan slack.SlackEvent)
12
13 api := slack.New("YOUR TOKEN HERE")
14 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.SlackWS, chSender chan slack.OutgoingMessage) {
22 for {
23 select {
24 case msg := <-chSender:
25 wsAPI.SendMessage(&msg)
26 }
27 }
28 }(wsAPI, chSender)
29 for {
30 select {
31 case msg := <-chReceiver:
32 fmt.Print("Event Received: ")
33 switch msg.Data.(type) {
34 case slack.HelloEvent:
35 // Ignore hello
36 case *slack.MessageEvent:
37 a := msg.Data.(*slack.MessageEvent)
38 fmt.Printf("%v\n", a)
39 case *slack.PresenceChangeEvent:
40 a := msg.Data.(*slack.PresenceChangeEvent)
41 fmt.Printf("%v\n", a)
42 case slack.LatencyReport:
43 a := msg.Data.(slack.LatencyReport)
44 fmt.Printf("Current latency: %v\n", a.Value)
45 default:
46 fmt.Printf("Unknown: %v\n", msg.Data)
47 }
48 }
49 }
50 }
2222 Timestamp JSONTime `json:"timestamp"`
2323 UserId string `json:"user"`
2424 Comment string `json:"comment"`
25 Created JSONTime `json:"created,omitempty"`
2526 }
2627
2728 // File contains all the information for a file
2222 Timestamp string `json:"ts,omitempty"`
2323 Text string `json:"text,omitempty"`
2424 Team string `json:"team,omitempty"`
25 File File `json:"file,omitempty"`
2526 // Type may come if it's part of a message list
2627 // e.g.: channel.history
2728 Type string `json:"type,omitempty"`
3132 Hidden bool `json:"bool,omitempty"`
3233 DeletedTimestamp string `json:"deleted_ts,omitempty"`
3334 Attachments []Attachment `json:"attachments,omitempty"`
35 ReplyTo int `json:"reply_to,omitempty"`
36 Upload bool `json:"upload,omitempty"`
3437 }
3538
3639 // Presence XXX: not used yet
4851 type Ping struct {
4952 Id int `json:"id"`
5053 Type string `json:"type"`
54 }
55
56 // Pong contains information about a Pong Event
57 type Pong struct {
58 Type string `json:"type"`
59 ReplyTo int `json:"reply_to"`
5160 }
5261
5362 // AckMessage is used for messages received in reply to other messages
88 Added as a var so that we can change this for testing purposes
99 */
1010 var SLACK_API string = "https://slack.com/api/"
11
12 type UserTyping struct {
13 Type string `json:"type"`
14 UserId string `json:"user"`
15 ChannelId string `json:"channel"`
16 }
17
18 type SlackEvent struct {
19 Type int
20 Data interface{}
21 }
2211
2312 type SlackResponse struct {
2413 Ok bool `json:"ok"`
1717 Page int
1818 }
1919
20 // XXX: Verify this. The whole thing is complicated. I don't like the way they mixed things
20 // TODO: Verify this. The whole thing is complicated. I don't like the way they mixed things
21 // It also appears to be a bug in parsing the message
2122 type StarredItem struct {
2223 Type string `json:"type"`
2324 ChannelId string `json:"channel"`
24 Message `json:"message"`
25 File `json:"file"`
26 Comment `json:"comment"`
25 Message `json:"message,omitempty"`
26 File `json:"file,omitempty"`
27 Comment `json:"comment,omitempty"`
2728 }
2829
2930 type starsResponseFull struct {
66 "log"
77 "net"
88 "net/url"
9 "strconv"
910 "sync"
1011 "time"
1112
1213 "golang.org/x/net/websocket"
1314 )
1415
15 const (
16 EV_MESSAGE = iota
17 EV_USER_TYPING
18 )
16 type MessageEvent Message
17
18 var eventMapping map[string]interface{} = map[string]interface{}{
19 "message": &MessageEvent{},
20 "presence_change": &PresenceChangeEvent{},
21 "user_typing": &UserTypingEvent{},
22
23 "channel_marked": &ChannelMarkedEvent{},
24 "channel_created": &ChannelCreatedEvent{},
25 "channel_joined": &ChannelJoinedEvent{},
26 "channel_left": &ChannelLeftEvent{},
27 "channel_deleted": &ChannelDeletedEvent{},
28 "channel_rename": &ChannelRenameEvent{},
29 "channel_archive": &ChannelArchiveEvent{},
30 "channel_unarchive": &ChannelUnarchiveEvent{},
31 "channel_history_changed": &ChannelHistoryChangedEvent{},
32
33 "im_created": &IMCreatedEvent{},
34 "im_open": &IMOpenEvent{},
35 "im_close": &IMCloseEvent{},
36 "im_marked": &IMMarkedEvent{},
37 "im_history_changed": &IMHistoryChangedEvent{},
38
39 "group_marked": &GroupMarkedEvent{},
40 "group_open": &GroupOpenEvent{},
41 "group_joined": &GroupJoinedEvent{},
42 "group_left": &GroupLeftEvent{},
43 "group_close": &GroupCloseEvent{},
44 "group_rename": &GroupRenameEvent{},
45 "group_archive": &GroupArchiveEvent{},
46 "group_unarchive": &GroupUnarchiveEvent{},
47 "group_history_changed": &GroupHistoryChangedEvent{},
48
49 // XXX: Not implemented below here
50 "file_created": &FileCreatedEvent{},
51 "file_shared": &FileSharedEvent{},
52 "file_unshared": &FileUnsharedEvent{},
53 "file_public": &FilePublicEvent{},
54 "file_private": &FilePrivateEvent{},
55 "file_change": &FileChangeEvent{},
56 "file_deleted": &FileDeletedEvent{},
57 "file_comment_added": &FileCommentAddedEvent{},
58 "file_comment_edited": &FileCommentEditedEvent{},
59 "file_comment_deleted": &FileCommentDeletedEvent{},
60
61 "team_join": &TeamJoinEvent{},
62 "team_rename": &TeamRenameEvent{},
63 "team_pref_change": &TeamPrefChangeEvent{},
64 "team_domain_change": &TeamDomainChangeEvent{},
65 "team_migration_started": &TeamMigrationStartedEvent{},
66
67 "manual_presence_change": &ManualPresenceChangeEvent{},
68
69 "pref_change": &PrefChangeEvent{},
70 "user_change": &UserChangeEvent{},
71
72 "star_added": &StarAddedEvent{},
73 "star_removed": &StarRemovedEvent{},
74
75 "emoji_changed": &EmojiChangedEvent{},
76
77 "commands_changed": &CommandsChangedEvent{},
78
79 "email_domain_changed": &EmailDomainChangedEvent{},
80
81 "bot_added": &BotAddedEvent{},
82 "bot_changed": &BotChangedEvent{},
83
84 "accounts_changed": &AccountsChangedEvent{},
85 }
1986
2087 type SlackWS struct {
2188 conn *websocket.Conn
2289 messageId int
2390 mutex sync.Mutex
91 pings map[int]time.Time
2492 Slack
2593 }
2694
32100 type SlackWSError struct {
33101 Code int
34102 Msg string
103 }
104
105 type SlackEvent struct {
106 Type uint64
107 Data interface{}
108 }
109
110 type JSONTimeString string
111
112 // String converts the unix timestamp into a string
113 func (t JSONTimeString) String() string {
114 if t == "" {
115 return ""
116 }
117 floatN, err := strconv.ParseFloat(string(t), 64)
118 if err != nil {
119 log.Panicln(err)
120 return ""
121 }
122 timeStr := int64(floatN)
123 tm := time.Unix(int64(timeStr), 0)
124 return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
35125 }
36126
37127 func (s SlackWSError) Error() string {
75165 if err != nil {
76166 return nil, err
77167 }
168 wsApi.pings = make(map[int]time.Time)
78169 return wsApi, nil
79170 }
80171
86177 if err := websocket.JSON.Send(api.conn, msg); err != nil {
87178 return err
88179 }
180 // TODO: What happens if we already have this id?
181 api.pings[api.messageId] = time.Now()
89182 return nil
90183 }
91184
139232 if api.debug {
140233 log.Println(string(event[:]))
141234 }
142 handleEvent(ch, event)
235 api.handleEvent(ch, event)
143236 }
144237 time.Sleep(time.Millisecond * 500)
145238 }
146239 }
147240
148 func handleEvent(ch chan SlackEvent, event json.RawMessage) {
241 func (api *SlackWS) handleEvent(ch chan SlackEvent, event json.RawMessage) {
149242 em := Event{}
150243 err := json.Unmarshal(event, &em)
151244 if err != nil {
152245 log.Fatal(err)
153246 }
154
247 log.Println(em.Type)
155248 switch em.Type {
156249 case "":
157250 // try ok
161254 }
162255
163256 if ack.Ok {
257 // TODO: Send the ack back (is this useful?)
258 //ch <- SlackEvent{Type: EventAck, Data: ack}
164259 log.Printf("Received an ok for: %d", ack.ReplyTo)
165260 return
166261 }
168263 // TODO: errors end up in this bucket. They shouldn't.
169264 log.Printf("Got error(?): %s", event)
170265 case "hello":
171 return
266 ch <- SlackEvent{Data: HelloEvent{}}
172267 case "pong":
173 // XXX: Eventually check to which ping this matched with
174 // Allows us to have stats about latency and what not
175 return
176 case "presence_change":
177 //log.Printf("`%s is %s`\n", info.GetUserById(event.PUserId).Name, event.Presence)
178 case "message":
179 handleMessage(ch, event)
180 case "channel_marked":
181 log.Printf("XXX: To implement %s", em)
182 case "user_typing":
183 handleUserTyping(ch, event)
268 pong := Pong{}
269 if err = json.Unmarshal(event, &pong); err != nil {
270 log.Fatal(err)
271 }
272 api.mutex.Lock()
273 latency := time.Since(api.pings[pong.ReplyTo])
274 api.mutex.Unlock()
275 ch <- SlackEvent{Data: LatencyReport{Value: latency}}
184276 default:
185 log.Println("XXX: " + string(event))
186 }
187 }
188
189 func handleUserTyping(ch chan SlackEvent, event json.RawMessage) {
190 msg := UserTyping{}
277 callEvent(em.Type, ch, event)
278 }
279 }
280
281 func callEvent(eventType string, ch chan SlackEvent, event json.RawMessage) {
282 msg := eventMapping[eventType]
283 if msg == nil {
284 log.Printf("XXX: Not implemented yet: %s -> %v", eventType, event)
285 }
191286 if err := json.Unmarshal(event, &msg); err != nil {
192287 log.Fatal(err)
193288 }
194 ch <- SlackEvent{Type: EV_USER_TYPING, Data: msg}
195 }
196
197 func handleMessage(ch chan SlackEvent, event json.RawMessage) {
198 msg := Message{}
199 err := json.Unmarshal(event, &msg)
200 if err != nil {
201 log.Fatal(err)
202 }
203 ch <- SlackEvent{Type: EV_MESSAGE, Data: msg}
204 }
289 ch <- SlackEvent{Data: msg}
290 }
0 package slack
1
2 type ChannelCreatedEvent struct {
3 Type string `json:"type"`
4 Channel ChannelCreatedInfo `json:"channel"`
5 EventTimestamp JSONTimeString `json:"event_ts"`
6 }
7
8 type ChannelCreatedInfo struct {
9 Id string `json:"id"`
10 IsChannel bool `json:"is_channel"`
11 Name string `json:"name"`
12 Created JSONTimeString `json:"created"`
13 Creator string `json:"creator"`
14 }
15
16 type ChannelJoinedEvent struct {
17 Type string `json:"type"`
18 Channel Channel `json:"channel"`
19 }
20
21 type ChannelInfoEvent struct {
22 // channel_left
23 // channel_deleted
24 // channel_archive
25 // channel_unarchive
26 Type string `json:"type"`
27 ChannelId string `json:"channel"`
28 UserId string `json:"user,omitempty"`
29 Timestamp JSONTimeString `json:"ts,omitempty"`
30 }
31
32 type ChannelRenameEvent struct {
33 Type string `json:"type"`
34 Channel ChannelRenameInfo `json:"channel"`
35 }
36
37 type ChannelRenameInfo struct {
38 Id string `json:"id"`
39 Name string `json:"name"`
40 Created JSONTime `json:"created"`
41 }
42
43 type ChannelHistoryChangedEvent struct {
44 Type string `json:"type"`
45 Latest JSONTimeString `json:"latest"`
46 Timestamp JSONTimeString `json:"ts"`
47 EventTimestamp JSONTimeString `json:"event_ts"`
48 }
49
50 type ChannelMarkedEvent ChannelInfoEvent
51 type ChannelLeftEvent ChannelInfoEvent
52 type ChannelDeletedEvent ChannelInfoEvent
53 type ChannelArchiveEvent ChannelInfoEvent
54 type ChannelUnarchiveEvent ChannelInfoEvent
0 package slack
1
2 type IMCreatedEvent struct {
3 Type string `json:"type"`
4 UserId string `json:"user"`
5 Channel ChannelCreatedInfo `json:"channel"`
6 }
7
8 type IMHistoryChangedEvent ChannelHistoryChangedEvent
9 type IMOpenEvent ChannelInfoEvent
10 type IMCloseEvent ChannelInfoEvent
11 type IMMarkedEvent ChannelInfoEvent
12 type IMMarkedHistoryChanged ChannelInfoEvent
0 package slack
1
2 type fileActionEvent struct {
3 Type string `json:"type"`
4 EventTimestamp JSONTimeString `json:"event_ts"`
5 File File `json:"file"`
6 // FileId is used for FileDeletedEvent
7 FileId string `json:"file_id,omitempty"`
8 }
9
10 type FileCreatedEvent fileActionEvent
11 type FileSharedEvent fileActionEvent
12 type FilePublicEvent fileActionEvent
13 type FileUnsharedEvent fileActionEvent
14 type FileChangeEvent fileActionEvent
15 type FileDeletedEvent fileActionEvent
16 type FilePrivateEvent fileActionEvent
17
18 type FileCommentAddedEvent struct {
19 fileActionEvent
20 Comment Comment `json:"comment"`
21 }
22
23 type FileCommentEditedEvent struct {
24 fileActionEvent
25 Comment Comment `json:"comment"`
26 }
27
28 type FileCommentDeletedEvent struct {
29 fileActionEvent
30 CommentId string `json:"comment"`
31 }
0 package slack
1
2 type GroupCreatedEvent struct {
3 Type string `json:"type"`
4 UserId string `json:"user"`
5 Channel ChannelCreatedInfo `json:"channel"`
6 }
7
8 // XXX: Should we really do this? event.Group is probably nicer than event.Channel
9 // even though the api returns "channel"
10 type GroupMarkedEvent ChannelInfoEvent
11 type GroupOpenEvent ChannelInfoEvent
12 type GroupCloseEvent ChannelInfoEvent
13 type GroupArchiveEvent ChannelInfoEvent
14 type GroupUnarchiveEvent ChannelInfoEvent
15 type GroupLeftEvent ChannelInfoEvent
16 type GroupJoinedEvent ChannelJoinedEvent
17 type GroupRenameEvent ChannelRenameEvent
18 type GroupHistoryChangedEvent ChannelHistoryChangedEvent
0 package slack
1
2 import (
3 "encoding/json"
4 "time"
5 )
6
7 // TODO: Probably need an error event
8
9 type HelloEvent struct{}
10
11 type PresenceChangeEvent struct {
12 Type string `json:"type"`
13 Presence string `json:"presence"`
14 UserId string `json:"user"`
15 }
16
17 type UserTypingEvent struct {
18 Type string `json:"type"`
19 UserId string `json:"user"`
20 ChannelId string `json:"channel"`
21 }
22
23 type LatencyReport struct {
24 Value time.Duration
25 }
26
27 type PrefChangeEvent struct {
28 Type string `json:"type"`
29 Name string `json:"name"`
30 Value json.RawMessage `json:"value"`
31 }
32
33 // TODO
34 type ManualPresenceChangeEvent struct{}
35 type UserChangeEvent struct{}
36 type EmojiChangedEvent struct{}
37 type CommandsChangedEvent struct{}
38 type EmailDomainChangedEvent struct{}
39 type BotAddedEvent struct{}
40 type BotChangedEvent struct{}
41 type AccountsChangedEvent struct{}
0 package slack
1
2 type starEvent struct {
3 Type string `json:"type"`
4 UserId string `json:"user"`
5 Item StarredItem `json:"item"`
6 EventTimestamp JSONTimeString `json:"event_ts"`
7 }
8 type StarAddedEvent starEvent
9 type StarRemovedEvent starEvent
0 package slack
1
2 // TODO
3 type TeamJoinEvent struct {
4 }
5
6 type TeamRenameEvent struct {
7 }
8
9 type TeamPrefChangeEvent struct {
10 }
11
12 type TeamDomainChangeEvent struct {
13 }
14
15 type TeamMigrationStartedEvent struct {
16 }