statbot/slack.go

208 lines
4.4 KiB
Go

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"`
}