forked from slack-go/slack
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8258160
Showing
6 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package slack | ||
|
||
import ( | ||
"encoding/json" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
) | ||
|
||
type ChannelHistory struct { | ||
Ok bool `json:"ok"` | ||
Latest string `json:"latest"` | ||
Messages []Message `json:"messages"` | ||
HasMore bool `json:"has_more"` | ||
|
||
Error string `json:"error"` | ||
} | ||
|
||
type ChannelTopic struct { | ||
Value string `json:"value"` | ||
Creator string `json:"creator"` | ||
LastSet JSONTime `json:"last_set"` | ||
} | ||
|
||
type Channel struct { | ||
Id string `json:"id"` | ||
Name string `json:"name"` | ||
IsChannel bool `json:"is_channel"` | ||
Creator string `json:"creator"` | ||
IsArchived bool `json:"is_archived"` | ||
IsGeneral bool `json:"is_general"` | ||
Members []string `json:"members"` | ||
Topic ChannelTopic `json:"topic"` | ||
Created JSONTime `json:"created"` | ||
IsMember bool `json:"is_member"` | ||
LastRead string `json:"last_read"` | ||
Latest Message `json:"latest"` | ||
UnreadCount int `json:"unread_count"` | ||
} | ||
|
||
func (api *SlackAPI) GetChannelHistory(channel_id string, latest string) ChannelHistory { | ||
channel_history := ChannelHistory{} | ||
resp, err := http.PostForm(SLACK_API+"channels.history", | ||
url.Values{ | ||
"token": {api.config.token}, | ||
"channel": {channel_id}, | ||
"latest": {latest}, | ||
}) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer resp.Body.Close() | ||
decoder := json.NewDecoder(resp.Body) | ||
if err = decoder.Decode(&channel_history); err != nil { | ||
log.Fatal(err) | ||
} | ||
if !channel_history.Ok { | ||
log.Fatal(channel_history.Error) | ||
} | ||
return channel_history | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package slack | ||
|
||
type Config struct { | ||
token string | ||
origin string | ||
protocol string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package slack | ||
|
||
const ( | ||
SLACK_API = "https://slack.com/api/" | ||
) | ||
|
||
const ( | ||
EV_MESSAGE = iota | ||
EV_USER_TYPING | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package slack | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
type User struct { | ||
Id string `json:"id"` | ||
Name string `json:"name"` | ||
} | ||
|
||
type UserPrefs struct { | ||
//{ | ||
// "highlight_words":"", | ||
// "user_colors":"", | ||
// "color_names_in_list":true, | ||
// "growls_enabled":true, | ||
// "tz":"Europe\/London", | ||
// "push_dm_alert":true, | ||
// "push_mention_alert":true, | ||
// "push_everything":true, | ||
// "push_idle_wait":2, | ||
// "push_sound":"b2.mp3", | ||
// "push_loud_channels":"", | ||
// "push_mention_channels":"", | ||
// "push_loud_channels_set":"", | ||
// "email_alerts":"instant", | ||
// "email_alerts_sleep_until":0, | ||
// "email_misc":false, | ||
// "email_weekly":true, | ||
// "welcome_message_hidden":false, | ||
// "all_channels_loud":true, | ||
// "loud_channels":"", | ||
// "never_channels":"", | ||
// "loud_channels_set":"", | ||
// "show_member_presence":true, | ||
// "search_sort":"timestamp", | ||
// "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} | ||
} | ||
|
||
type UserDetails struct { | ||
Id string `json:"id"` | ||
Name string `json:"name"` | ||
Created JSONTime `json:"created"` | ||
ManualPresence string `json:"manual_presence"` | ||
Prefs UserPrefs `json:"prefs"` | ||
} | ||
|
||
type JSONTime int64 | ||
|
||
func (t JSONTime) String() string { | ||
tm := time.Unix(int64(t), 0) | ||
return fmt.Sprintf("\"%s\"", tm.Format("Mon Jan _2")) | ||
} | ||
|
||
type Team struct { | ||
Id string `json:"id"` | ||
Name string `json:"name"` | ||
Domain string `json:"name"` | ||
} | ||
|
||
type Info struct { | ||
Ok bool `json:"ok"` | ||
Error string `json:"error,omitempty"` | ||
Url string `json:"url,omitempty"` | ||
User UserDetails `json:"self,omitempty"` | ||
Team Team `json:"team,omitempty"` | ||
Users []User `json:"users,omitempty"` | ||
Channels []Channel `json:"channels,omitempty"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package slack | ||
|
||
type OutgoingMessage struct { | ||
Id int `json:"id"` | ||
ChannelID string `json:"channel,omitempty"` | ||
Text string `json:"text,omitempty"` | ||
Type string `json:"type,omitempty"` | ||
} | ||
|
||
type Message struct { | ||
Msg | ||
SubMessage Msg `json:"message,omitempty"` | ||
} | ||
|
||
type Msg struct { | ||
Id string `json:"id"` | ||
UserID string `json:"user,omitempty"` | ||
ChannelID string `json:"channel,omitempty"` | ||
Timestamp string `json:"ts,omitempty"` | ||
Text string `json:"text,omitempty"` | ||
// Type may come if it's part of a message list | ||
// e.g.: channel.history | ||
Type string `json:"type,omitempty"` | ||
IsStarred bool `json:"is_starred,omitempty"` | ||
// Submessage | ||
SubType string `json:"subtype,omitempty"` | ||
Hidden bool `json:"bool,omitempty"` | ||
DeletedTimestamp string `json:"deleted_ts,omitempty"` | ||
} | ||
|
||
type Presence struct { | ||
Presence string `json:"presence"` | ||
UserID string `json:"user"` | ||
} | ||
|
||
type Event struct { | ||
Type string `json:"type,omitempty"` | ||
} | ||
|
||
type Ping struct { | ||
Id int `json:"id"` | ||
Type string `json:"type"` | ||
} | ||
|
||
type AckMessage struct { | ||
Ok bool `json:"ok"` | ||
ReplyTo int `json:"reply_to"` | ||
Timestamp string `json:"ts"` | ||
Text string `json:"text"` | ||
} | ||
|
||
var message_id int | ||
|
||
func NewOutgoingMessage(text string, channel string) *OutgoingMessage { | ||
message_id++ | ||
return &OutgoingMessage{ | ||
Id: message_id, | ||
Type: "message", | ||
ChannelID: channel, | ||
Text: text, | ||
} | ||
} | ||
|
||
// XXX: maybe support variable arguments so that people | ||
// can send stuff through their ping | ||
func NewPing() *Ping { | ||
message_id++ | ||
return &Ping{Id: message_id, Type: "ping"} | ||
} | ||
|
||
func (info Info) GetUserById(id string) *User { | ||
for _, user := range info.Users { | ||
if user.Id == id { | ||
return &user | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (info Info) GetChannelById(id string) *Channel { | ||
for _, channel := range info.Channels { | ||
if channel.Id == id { | ||
return &channel | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package slack | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"io" | ||
"log" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
"golang.org/x/net/websocket" | ||
) | ||
|
||
type UserTyping struct { | ||
Type string `json:"type"` | ||
UserID string `json:"user"` | ||
ChannelID string `json:"channel"` | ||
} | ||
|
||
type SlackEvent struct { | ||
Type int | ||
Data interface{} | ||
} | ||
|
||
type SlackAPI struct { | ||
config Config | ||
conn *websocket.Conn | ||
|
||
info Info | ||
} | ||
|
||
func New(token string) *SlackAPI { | ||
return &SlackAPI{ | ||
config: Config{token: token}, | ||
} | ||
} | ||
|
||
func (api *SlackAPI) GetInfo() Info { | ||
return api.info | ||
} | ||
|
||
func (api *SlackAPI) StartRTM(protocol string, origin string) error { | ||
resp, err := http.PostForm(SLACK_API+"rtm.start", url.Values{"token": {api.config.token}}) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
decoder := json.NewDecoder(resp.Body) | ||
if err = decoder.Decode(&api.info); err != nil { | ||
return err | ||
} | ||
if !api.info.Ok { | ||
return errors.New(api.info.Error) | ||
} | ||
api.config.protocol, api.config.origin = protocol, origin | ||
api.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (api *SlackAPI) Ping() error { | ||
if err := websocket.JSON.Send(api.conn, NewPing()); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (api *SlackAPI) SendMessage(msg OutgoingMessage) error { | ||
if err := websocket.JSON.Send(api.conn, msg); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (api *SlackAPI) HandleIncomingEvents(ch *chan SlackEvent) { | ||
event := json.RawMessage{} | ||
for { | ||
if err := websocket.JSON.Receive(api.conn, &event); err == io.EOF { | ||
// should we reconnect here? | ||
if !api.conn.IsClientConn() { | ||
api.conn, err = websocket.Dial(api.info.Url, api.config.protocol, api.config.origin) | ||
if err != nil { | ||
log.Panic(err) | ||
} | ||
} | ||
// XXX: check for timeout and implement exponential backoff | ||
} else if err != nil { | ||
log.Panic(err) | ||
} | ||
if len(event) == 0 { | ||
log.Println("Event Empty. WTF?") | ||
} else { | ||
handle_event(ch, event) | ||
} | ||
time.Sleep(time.Millisecond * 500) | ||
} | ||
} | ||
|
||
func handle_event(ch *chan SlackEvent, event json.RawMessage) { | ||
em := Event{} | ||
err := json.Unmarshal(event, &em) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
switch em.Type { | ||
case "": | ||
// try ok | ||
ack := AckMessage{} | ||
if err = json.Unmarshal(event, &ack); err != nil { | ||
log.Fatal(err) | ||
} | ||
if ack.Ok { | ||
log.Printf("Received an ok for: %d", ack.ReplyTo) | ||
} else { | ||
log.Println(event) | ||
log.Println("XXX: ?") | ||
} | ||
case "hello": | ||
return | ||
case "pong": | ||
// XXX: Eventually check to which ping this matched with | ||
// Allows us to have stats about latency and what not | ||
return | ||
case "presence_change": | ||
//log.Printf("`%s is %s`\n", info.GetUserById(event.PUserID).Name, event.Presence) | ||
case "message": | ||
handle_message(ch, event) | ||
case "channel_marked": | ||
log.Printf("XXX: To implement %s", em) | ||
case "user_typing": | ||
handle_user_typing(ch, event) | ||
default: | ||
log.Println("XXX: " + string(event)) | ||
} | ||
} | ||
|
||
func handle_user_typing(ch *chan SlackEvent, event json.RawMessage) { | ||
msg := UserTyping{} | ||
if err := json.Unmarshal(event, &msg); err != nil { | ||
log.Fatal(err) | ||
} | ||
*ch <- SlackEvent{Type: EV_USER_TYPING, Data: msg} | ||
} | ||
|
||
func handle_message(ch *chan SlackEvent, event json.RawMessage) { | ||
msg := Message{} | ||
err := json.Unmarshal(event, &msg) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
*ch <- SlackEvent{Type: EV_MESSAGE, Data: msg} | ||
} |