package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "strconv" "strings" "sync/atomic" "time" "golang.org/x/net/websocket" ) // Slack manages the slack connection and implements API calls type Slack struct { apiToken string id string socket *websocket.Conn alive bool } // CreateSlack creates the slack object, opening a websocket func CreateSlack(token string) (*Slack, error) { s := Slack{apiToken: token} url := fmt.Sprintf("https://slack.com/api/rtm.start?token=%s", token) resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != 200 { err = fmt.Errorf("API request failed with code %d", resp.StatusCode) return nil, err } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { return nil, err } var respObj responseRtmStart err = json.Unmarshal(body, &respObj) if err != nil { return nil, err } if !respObj.Ok { err = fmt.Errorf("Slack error: %s", respObj.Error) return nil, err } wsurl := respObj.URL s.id = respObj.Self.ID ws, err := websocket.Dial(wsurl, "", "https://api.slack.com/") s.socket = ws s.keepAlive() return &s, nil } func (s *Slack) keepAlive() { s.alive = true go func() { for { if s.alive { s.ping() } time.Sleep(time.Second * 30) } }() } var counter uint64 func (s *Slack) ping() error { return s.postMessage(Message{Type: "ping"}) } func (s *Slack) postMessage(m Message) error { m.ID = atomic.AddUint64(&counter, 1) return websocket.JSON.Send(s.socket, m) } func (s *Slack) getMessage() (Message, error) { var m Message err := websocket.JSON.Receive(s.socket, &m) m.Time = convertSlackTimestamp(m.Ts) return m, err } func (s *Slack) sendMessageToUser(uid string, msg string) error { // TODO: Actually send a direct message to uid m := new(Message) m.ID = atomic.AddUint64(&counter, 1) m.Type = "message" m.Channel = "" m.User = uid m.Name = "" m.Text = msg return websocket.JSON.Send(s.socket, m) } func (s *Slack) getUserInfo(uid string) (*User, error) { url := fmt.Sprintf("https://slack.com/api/users.info?token=%s&user=%s", s.apiToken, uid) resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != 200 { err = fmt.Errorf("API request failed with code %d", resp.StatusCode) return nil, err } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { return nil, err } var respObj responseUserLookup err = json.Unmarshal(body, &respObj) if err != nil { return nil, err } if !respObj.Ok { err = fmt.Errorf("Slack error: %s", respObj.Error) return nil, err } return respObj.User, nil } func (s *Slack) getChannelList() ([]Channel, error) { url := fmt.Sprintf("https://slack.com/api/channels.list?token=%s", s.apiToken) resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != 200 { err = fmt.Errorf("API request failed with code %d", resp.StatusCode) return nil, err } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { return nil, err } fmt.Println(string(body)) var respObj responseChannelList err = json.Unmarshal(body, &respObj) if err != nil { return nil, err } if !respObj.Ok { err = fmt.Errorf("Slack error: %s", respObj.Error) return nil, err } for i := range respObj.Channels { respObj.Channels[i].LastRead = convertSlackTimestamp(respObj.Channels[i].LastReadRaw) } return respObj.Channels, nil } func (s *Slack) getChannelInfo(cid string) (*Channel, error) { url := fmt.Sprintf("https://slack.com/api/channels.info?token=%s&channel=%s", s.apiToken, cid) resp, err := http.Get(url) if err != nil { return nil, err } if resp.StatusCode != 200 { err = fmt.Errorf("API request failed with code %d", resp.StatusCode) return nil, err } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { return nil, err } var respObj responseChannelLookup err = json.Unmarshal(body, &respObj) if err != nil { return nil, err } if !respObj.Ok { err = fmt.Errorf("Slack error: %s", respObj.Error) return nil, err } respObj.Channel.LastRead = convertSlackTimestamp(respObj.Channel.LastReadRaw) return respObj.Channel, nil } // These structures represent the response of the Slack API events // Only some fields are included. The rest are ignored by json.Unmarshal. type responseRtmStart struct { Ok bool `json:"ok"` Error string `json:"error"` URL string `json:"url"` Self responseSelf `json:"self"` } type responseSelf struct { ID string `json:"id"` } type responseUserLookup struct { Ok bool `json:"ok"` Error string `json:"error"` User *User `json:"user"` } type responseChannelLookup struct { Ok bool `json:"ok"` Error string `json:"error"` Channel *Channel `json:"channel"` } type responseChannelList struct { Ok bool `json:"ok"` Error string `json:"error"` Channels []Channel `json:"channels"` } func convertSlackTimestamp(ts string) time.Time { var ret time.Time txtArr := strings.Split(ts, ".") if t, err := strconv.Atoi(txtArr[0]); err == nil { rawts := int64(t) ret = time.Unix(0, rawts*1000000000) } return ret }