Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nlopes committed Jan 9, 2015
0 parents commit 8258160
Show file tree
Hide file tree
Showing 6 changed files with 391 additions and 0 deletions.
61 changes: 61 additions & 0 deletions channel.go
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
}
7 changes: 7 additions & 0 deletions config.go
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
}
10 changes: 10 additions & 0 deletions const.go
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
)
71 changes: 71 additions & 0 deletions info.go
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"`
}
87 changes: 87 additions & 0 deletions messages.go
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
}
155 changes: 155 additions & 0 deletions slack.go
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}
}

0 comments on commit 8258160

Please sign in to comment.