package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "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 return nil } 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) 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 } func (s *Slack) joinChannel(c *Channel) error { url := fmt.Sprintf("https://slack.com/api/channels.join?token=%s&name=%s", s.apiToken, c.Name) resp, err := http.Get(url) if err != nil { return err } if resp.StatusCode != 200 { err = fmt.Errorf("API request failed with code %d", resp.StatusCode) return err } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { return err } var respObj responseChannelLookup err = json.Unmarshal(body, &respObj) if err != nil { return err } if !respObj.Ok { err = fmt.Errorf("Slack error: %s", respObj.Error) return err } return err } // 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"` }