Codebase list golang-github-nlopes-slack / 8258160
Initial commit Norberto Lopes 9 years ago
6 changed file(s) with 391 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 package slack
1
2 import (
3 "encoding/json"
4 "log"
5 "net/http"
6 "net/url"
7 )
8
9 type ChannelHistory struct {
10 Ok bool `json:"ok"`
11 Latest string `json:"latest"`
12 Messages []Message `json:"messages"`
13 HasMore bool `json:"has_more"`
14
15 Error string `json:"error"`
16 }
17
18 type ChannelTopic struct {
19 Value string `json:"value"`
20 Creator string `json:"creator"`
21 LastSet JSONTime `json:"last_set"`
22 }
23
24 type Channel struct {
25 Id string `json:"id"`
26 Name string `json:"name"`
27 IsChannel bool `json:"is_channel"`
28 Creator string `json:"creator"`
29 IsArchived bool `json:"is_archived"`
30 IsGeneral bool `json:"is_general"`
31 Members []string `json:"members"`
32 Topic ChannelTopic `json:"topic"`
33 Created JSONTime `json:"created"`
34 IsMember bool `json:"is_member"`
35 LastRead string `json:"last_read"`
36 Latest Message `json:"latest"`
37 UnreadCount int `json:"unread_count"`
38 }
39
40 func (api *SlackAPI) GetChannelHistory(channel_id string, latest string) ChannelHistory {
41 channel_history := ChannelHistory{}
42 resp, err := http.PostForm(SLACK_API+"channels.history",
43 url.Values{
44 "token": {api.config.token},
45 "channel": {channel_id},
46 "latest": {latest},
47 })
48 if err != nil {
49 log.Fatal(err)
50 }
51 defer resp.Body.Close()
52 decoder := json.NewDecoder(resp.Body)
53 if err = decoder.Decode(&channel_history); err != nil {
54 log.Fatal(err)
55 }
56 if !channel_history.Ok {
57 log.Fatal(channel_history.Error)
58 }
59 return channel_history
60 }
0 package slack
1
2 type Config struct {
3 token string
4 origin string
5 protocol string
6 }
0 package slack
1
2 const (
3 SLACK_API = "https://slack.com/api/"
4 )
5
6 const (
7 EV_MESSAGE = iota
8 EV_USER_TYPING
9 )
0 package slack
1
2 import (
3 "fmt"
4 "time"
5 )
6
7 type User struct {
8 Id string `json:"id"`
9 Name string `json:"name"`
10 }
11
12 type UserPrefs struct {
13 //{
14 // "highlight_words":"",
15 // "user_colors":"",
16 // "color_names_in_list":true,
17 // "growls_enabled":true,
18 // "tz":"Europe\/London",
19 // "push_dm_alert":true,
20 // "push_mention_alert":true,
21 // "push_everything":true,
22 // "push_idle_wait":2,
23 // "push_sound":"b2.mp3",
24 // "push_loud_channels":"",
25 // "push_mention_channels":"",
26 // "push_loud_channels_set":"",
27 // "email_alerts":"instant",
28 // "email_alerts_sleep_until":0,
29 // "email_misc":false,
30 // "email_weekly":true,
31 // "welcome_message_hidden":false,
32 // "all_channels_loud":true,
33 // "loud_channels":"",
34 // "never_channels":"",
35 // "loud_channels_set":"",
36 // "show_member_presence":true,
37 // "search_sort":"timestamp",
38 // "expand_inline_imgs":true,"expand_internal_inline_imgs":true,"expand_snippets":false,"posts_formatting_guide":true,"seen_welcome_2":true,"seen_ssb_prompt":false,"search_only_my_channels":false,"emoji_mode":"default","has_invited":true,"has_uploaded":false,"has_created_channel":true,"search_exclude_channels":"","messages_theme":"default","webapp_spellcheck":true,"no_joined_overlays":false,"no_created_overlays":true,"dropbox_enabled":false,"seen_user_menu_tip_card":true,"seen_team_menu_tip_card":true,"seen_channel_menu_tip_card":true,"seen_message_input_tip_card":true,"seen_channels_tip_card":true,"seen_domain_invite_reminder":false,"seen_member_invite_reminder":false,"seen_flexpane_tip_card":true,"seen_search_input_tip_card":true,"mute_sounds":false,"arrow_history":false,"tab_ui_return_selects":true,"obey_inline_img_limit":true,"new_msg_snd":"knock_brush.mp3","collapsible":false,"collapsible_by_click":true,"require_at":false,"mac_ssb_bounce":"","mac_ssb_bullet":true,"win_ssb_bullet":true,"expand_non_media_attachments":true,"show_typing":true,"pagekeys_handled":true,"last_snippet_type":"","display_real_names_override":0,"time24":false,"enter_is_special_in_tbt":false,"graphic_emoticons":false,"convert_emoticons":true,"autoplay_chat_sounds":true,"ss_emojis":true,"sidebar_behavior":"","mark_msgs_read_immediately":true,"start_scroll_at_oldest":true,"snippet_editor_wrap_long_lines":false,"ls_disabled":false,"sidebar_theme":"default","sidebar_theme_custom_values":"","f_key_search":false,"k_key_omnibox":true,"speak_growls":false,"mac_speak_voice":"com.apple.speech.synthesis.voice.Alex","mac_speak_speed":250,"comma_key_prefs":false,"at_channel_suppressed_channels":"","push_at_channel_suppressed_channels":"","prompted_for_email_disabling":false,"full_text_extracts":false,"no_text_in_notifications":false,"muted_channels":"","no_macssb1_banner":false,"privacy_policy_seen":true,"search_exclude_bots":false,"fuzzy_matching":false}
39 }
40
41 type UserDetails struct {
42 Id string `json:"id"`
43 Name string `json:"name"`
44 Created JSONTime `json:"created"`
45 ManualPresence string `json:"manual_presence"`
46 Prefs UserPrefs `json:"prefs"`
47 }
48
49 type JSONTime int64
50
51 func (t JSONTime) String() string {
52 tm := time.Unix(int64(t), 0)
53 return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2"))
54 }
55
56 type Team struct {
57 Id string `json:"id"`
58 Name string `json:"name"`
59 Domain string `json:"name"`
60 }
61
62 type Info struct {
63 Ok bool `json:"ok"`
64 Error string `json:"error,omitempty"`
65 Url string `json:"url,omitempty"`
66 User UserDetails `json:"self,omitempty"`
67 Team Team `json:"team,omitempty"`
68 Users []User `json:"users,omitempty"`
69 Channels []Channel `json:"channels,omitempty"`
70 }
0 package slack
1
2 type OutgoingMessage struct {
3 Id int `json:"id"`
4 ChannelID string `json:"channel,omitempty"`
5 Text string `json:"text,omitempty"`
6 Type string `json:"type,omitempty"`
7 }
8
9 type Message struct {
10 Msg
11 SubMessage Msg `json:"message,omitempty"`
12 }
13
14 type Msg struct {
15 Id string `json:"id"`
16 UserID string `json:"user,omitempty"`
17 ChannelID string `json:"channel,omitempty"`
18 Timestamp string `json:"ts,omitempty"`
19 Text string `json:"text,omitempty"`
20 // Type may come if it's part of a message list
21 // e.g.: channel.history
22 Type string `json:"type,omitempty"`
23 IsStarred bool `json:"is_starred,omitempty"`
24 // Submessage
25 SubType string `json:"subtype,omitempty"`
26 Hidden bool `json:"bool,omitempty"`
27 DeletedTimestamp string `json:"deleted_ts,omitempty"`
28 }
29
30 type Presence struct {
31 Presence string `json:"presence"`
32 UserID string `json:"user"`
33 }
34
35 type Event struct {
36 Type string `json:"type,omitempty"`
37 }
38
39 type Ping struct {
40 Id int `json:"id"`
41 Type string `json:"type"`
42 }
43
44 type AckMessage struct {
45 Ok bool `json:"ok"`
46 ReplyTo int `json:"reply_to"`
47 Timestamp string `json:"ts"`
48 Text string `json:"text"`
49 }
50
51 var message_id int
52
53 func NewOutgoingMessage(text string, channel string) *OutgoingMessage {
54 message_id++
55 return &OutgoingMessage{
56 Id: message_id,
57 Type: "message",
58 ChannelID: channel,
59 Text: text,
60 }
61 }
62
63 // XXX: maybe support variable arguments so that people
64 // can send stuff through their ping
65 func NewPing() *Ping {
66 message_id++
67 return &Ping{Id: message_id, Type: "ping"}
68 }
69
70 func (info Info) GetUserById(id string) *User {
71 for _, user := range info.Users {
72 if user.Id == id {
73 return &user
74 }
75 }
76 return nil
77 }
78
79 func (info Info) GetChannelById(id string) *Channel {
80 for _, channel := range info.Channels {
81 if channel.Id == id {
82 return &channel
83 }
84 }
85 return nil
86 }
0 package slack
1
2 import (
3 "encoding/json"
4 "errors"
5 "io"
6 "log"
7 "net/http"
8 "net/url"
9 "time"
10
11 "golang.org/x/net/websocket"
12 )
13
14 type UserTyping struct {
15 Type string `json:"type"`
16 UserID string `json:"user"`
17 ChannelID string `json:"channel"`
18 }
19
20 type SlackEvent struct {
21 Type int
22 Data interface{}
23 }
24
25 type SlackAPI struct {
26 config Config
27 conn *websocket.Conn
28
29 info Info
30 }
31
32 func New(token string) *SlackAPI {
33 return &SlackAPI{
34 config: Config{token: token},
35 }
36 }
37
38 func (api *SlackAPI) GetInfo() Info {
39 return api.info
40 }
41
42 func (api *SlackAPI) StartRTM(protocol string, origin string) error {
43 resp, err := http.PostForm(SLACK_API+"rtm.start", url.Values{"token": {api.config.token}})
44 if err != nil {
45 return err
46 }
47 defer resp.Body.Close()
48 decoder := json.NewDecoder(resp.Body)
49 if err = decoder.Decode(&api.info); err != nil {
50 return err
51 }
52 if !api.info.Ok {
53 return errors.New(api.info.Error)
54 }
55 api.config.protocol, api.config.origin = protocol, origin
56 api.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
57 if err != nil {
58 return err
59 }
60 return nil
61 }
62
63 func (api *SlackAPI) Ping() error {
64 if err := websocket.JSON.Send(api.conn, NewPing()); err != nil {
65 return err
66 }
67 return nil
68 }
69
70 func (api *SlackAPI) SendMessage(msg OutgoingMessage) error {
71 if err := websocket.JSON.Send(api.conn, msg); err != nil {
72 return err
73 }
74 return nil
75 }
76
77 func (api *SlackAPI) HandleIncomingEvents(ch *chan SlackEvent) {
78 event := json.RawMessage{}
79 for {
80 if err := websocket.JSON.Receive(api.conn, &event); err == io.EOF {
81 // should we reconnect here?
82 if !api.conn.IsClientConn() {
83 api.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin)
84 if err != nil {
85 log.Panic(err)
86 }
87 }
88 // XXX: check for timeout and implement exponential backoff
89 } else if err != nil {
90 log.Panic(err)
91 }
92 if len(event) == 0 {
93 log.Println("Event Empty. WTF?")
94 } else {
95 handle_event(ch, event)
96 }
97 time.Sleep(time.Millisecond * 500)
98 }
99 }
100
101 func handle_event(ch *chan SlackEvent, event json.RawMessage) {
102 em := Event{}
103 err := json.Unmarshal(event, &em)
104 if err != nil {
105 log.Fatal(err)
106 }
107 switch em.Type {
108 case "":
109 // try ok
110 ack := AckMessage{}
111 if err = json.Unmarshal(event, &ack); err != nil {
112 log.Fatal(err)
113 }
114 if ack.Ok {
115 log.Printf("Received an ok for: %d", ack.ReplyTo)
116 } else {
117 log.Println(event)
118 log.Println("XXX: ?")
119 }
120 case "hello":
121 return
122 case "pong":
123 // XXX: Eventually check to which ping this matched with
124 // Allows us to have stats about latency and what not
125 return
126 case "presence_change":
127 //log.Printf("`%s is %s`\n", info.GetUserById(event.PUserID).Name, event.Presence)
128 case "message":
129 handle_message(ch, event)
130 case "channel_marked":
131 log.Printf("XXX: To implement %s", em)
132 case "user_typing":
133 handle_user_typing(ch, event)
134 default:
135 log.Println("XXX: " + string(event))
136 }
137 }
138
139 func handle_user_typing(ch *chan SlackEvent, event json.RawMessage) {
140 msg := UserTyping{}
141 if err := json.Unmarshal(event, &msg); err != nil {
142 log.Fatal(err)
143 }
144 *ch <- SlackEvent{Type: EV_USER_TYPING, Data: msg}
145 }
146
147 func handle_message(ch *chan SlackEvent, event json.RawMessage) {
148 msg := Message{}
149 err := json.Unmarshal(event, &msg)
150 if err != nil {
151 log.Fatal(err)
152 }
153 *ch <- SlackEvent{Type: EV_MESSAGE, Data: msg}
154 }