読者です 読者をやめる 読者になる 読者になる

手元のPCで動かせる slackbot を作って遊ぶ (1)

golang Go言語 slack

今回の元ネタはこちらです: www.opsdash.com

前々から slack bot を立ち上げて遊んでみたいとは思ってたんですが, どっかにサーバ借りて・・・とか考えるとなかなか面倒で遠ざかってました.

実は,slack には Real Time Messaging API という WebSocket ベースの API が用意されていて, こいつを叩くと,簡単に手元のPCで bot を立ち上げることが出来るのです.

仕組みはこんな感じです.手元のPCからアクセスを確立するための url に折り返すので,手元のマシンで bot を立ち上げられます.

f:id:ikawaha:20151005165113p:plain:w250

コード的にはこんな感じ.

(1) token を送って折り返しの url をもらうところ.

func (b Bot) connect(token string) (*connectResponse, error) {
    q := url.Values{}
    q.Set("token", token)
    u := &url.URL{
        Scheme:   "https",
        Host:     "slack.com",
        Path:     "/api/rtm.start",
        RawQuery: q.Encode(),
    }
    resp, err := http.Get(u.String())
    if err != nil {
        return nil, err
    }
    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("request failed with code %d", resp.StatusCode)
    }
    var body connectResponse
    dec := json.NewDecoder(resp.Body)
    if e := dec.Decode(&body); e != nil {
        return nil, fmt.Errorf("response decode error, %v", err)
    }
    return &body, nil
}

(2) もらった url に折り返すところ

func (b *Bot) dial(url string) error {
    ws, err := websocket.Dial(url, "", "https://api.slack.com/")
    if err != nil {
        return fmt.Errorf("dial error, %v", err)
    }
    b.socket = ws
    return nil
}

これで接続が確立されました.

あとは,メッセージの受け取りと,送信をしてやればいいです.

送受信は,websocket から JSON 形式で読み出します. 送信するときには,メッセージ ID がいるようなので, こいつは複数の go routine から呼ばれても大丈夫なように atomic に処理してインクリメントします.

// GetMessage receives a message from the slack channel.
func (b Bot) GetMessage() (Message, error) {
    var msg Message
    err := websocket.JSON.Receive(b.socket, &msg)
    return msg, err
}

// PostMessage sends a message to the slack channel.
func (b *Bot) PostMessage(m Message) error {
    m.ID = atomic.AddUint64(&b.counter, 1)
    return websocket.JSON.Send(b.socket, m)
}

こうすると,受け取ったメッセージをひたすら echo する bot になります. websocket は,読み込めるまでそこで止まるので,for で囲むだけで大丈夫です. 送信の方は go ruoutine 作ってまかせてしまいます.

for {
    msg, _ := bot.GetMessage()
    go func(m slackbot.Message) {
        m.Text = m.TextBody()
        bot.PostMessage(m)
    }(msg)
}

基本的にはこれで終わりです.ただし,メッセージは,チャンネル内のすべてのメッセージを受け取ってしまうので, このままだとカオスになります.

次回は,bot 用の token の取得方法,チャンネル情報の取得,受け取るメッセージの構造なんかを説明したいと思います.