SlackのAPIの使い方の一例として、「echo hogehoge」と発言したら「hogehoge」と返す簡単なbotを作るまでの流れです。

説明中に出てくるコードは基本的にGAE + Python 2.7 (無料枠で使えるバージョンがこれしかない)を想定しています。

アプリケーションの種類

Slackでアプリケーションを作成する場合、最初にアプリケーションの種類を選ぶ必要がある。

Slackのアプリケーションが使える機能はいくつかあるが、基本的には、用途に合わせて下の三種類のいずれかを使用すればよい。

Incoming Webhooks

一番シンプルなアプリケーションの種類。単純にAPIを叩いてメッセージの投稿などを行う。投稿を行ったタイミングでアプリケーション側に通知を投げたりすることはできない。

Slash Commands

/invite/remind などのように、スラッシュで始まるコマンドを作成することができる。コマンドが入力されたタイミングで、アプリケーション側のサーバーにPOSTリクエストが届く。

Bot User

基本的なAPIの使用に加え、メッセージ等のイベントの受信を行うことができる。イベントの受信方法は、特定の条件下でサーバーにPOSTリクエストが届くEvent APIの他、WebSocketベースのReal Time Messaging APIがある。

Botを作る

まずは、 https://api.slack.com/ からアプリケーションを作る。必要な手順は以下の通り:

  1. 「Add Bot User」からbotを追加。botの名前とかは適当に設定。
  2. サイドバーの「Install App」から、botをワークスペースに追加。
  3. botを追加するとBot User OAuth Access Token が手に入るので、これをメモ。
  4. サイドバーの「Basic Information」から、Verification Token をメモ。

トークンにはOAuth Access TokenとBot User OAuth Access Tokenの2種類がある。これらの間に大きな違いはないが、Real Time Messaging APIを使う場合や、Botとしてアクセスする場合はBot User OAuth Access Tokenを使う。

詳細は以下を参照:

https://api.slack.com/docs/oauth#bots

最低限のBotのコードを書く

Botはまず最低限SlackのURL verificationに反応する必要がある。

Slackはbotの存在確認のために、以下のようなbodyを持つPOSTリクエストを投げる:

{
    "token": "(先程取得したverification token)",
    "challenge": "(ランダムな文字列)",
    "type": "url_verification"
}

このうち、 token はSlackからのリクエストに必ず付与されるもので、リクエストの送信元がSlackであることを証明するために用いられる。

これに対して、botは以下のようなレスポンスを返す必要がある:

HTTP/1.1 200 Ok
...
Content-Type: text/plain

(先程受け取ったchallengeの文字列)

最低限この機能を実装しないと、Slack側はbotが存在しないか、正常に動いていないものとみなしてしまう。そのため、まずは上述した機能のみを持つ最小限のサーバーを作る。

# -*- coding: utf-8 -*-
import json
import webapp2

class EchoHandler(webapp2.RequestHandler):
    VERIFICATION_TOKEN = 'YOUR_VERIFICATION_TOKEN'

    def post(self):
        body = json.loads(self.request.body)
        if body['token'] != self.VERIFICATION_TOKEN:
            self.response.headers['Content-Type'] = 'text/plain'
            self.status = 403
            self.response.write('403 Forbidden')
            return
        if body['type'] == 'url_verification':
            self.verify_url(body)

    def verify_url(self, params):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write(params['challenge'])

app = webapp2.WSGIApplication([
    ('/echo-bot-hook', EchoHandler)
], debug=True)

※ Verification token はコードに直接書くべきではない。今回は動作説明のため、この部分については簡略化している。

Events API の有効化

上のコードをGAEにデプロイしたら、以下の手順を実行してSlack側からのイベントの通知を受け取れるように設定する:

  1. Slackのアプリケーション管理画面に戻り、サイドバーの「Event Subscription」から、Enable EventsをOnに設定。
  2. Request URLに、 https://YOUR-APP-NAME.appspot.com/echo-bot-hook を入力。
  3. botが正常に動いていれば、先程説明したURL verificationが行われ、Verifiedと表示される。
  4. Subscribe to Workspace Eventsから、botに通知させたいイベントを追加する。今回はbotの参加しているチャンネルに対する発言を受け取ることができればいいので、 message.channels を選択し、「Save Changes」で保存。
  5. 変更後、「Click here to reinstall …」というメッセージが出て来るので、クリックしアプリケーションをワークスペースにインストールし直す。

イベントを受け取る

Events APIを有効化すると、指定したイベントが起きるたびに、登録されたURLにPOSTリクエストが飛ぶようになる。今回はイベントのうち、先程指定した message.channels のみについての説明を行う。全てのイベントの仕様については以下を参照:

https://api.slack.com/events

message.channels イベントは以下のような形式でサーバーに届く

POST /echo-bot-hook HTTP/1.1
...
Content-Type: application/json

{
  "token": "(verification token)",
  "team_id": "(team id)",
  "api_app_id": "(app id)",
  "event": {
    "type": "message",
    "channel_type": "channel",
    "user": "(user id)",
    "text": "(message body)",
    ...
  },
  ...
}

なお、今回は簡単なbotを作るために最低限必要になるパラメータについてのみ触れる。詳細な仕様に関しては、以下を参照:

https://api.slack.com/events-api

先程のコードを変更し、 message.channels イベントを受け取るだけの最小限の機能を実装する:

...
import logging
...

class EchoHandler(webapp2.RequestHandler):
    ...

    def post(self):
        body = json.loads(self.request.body)
        if body['token'] != self.VERIFICATION_TOKEN:
            self.response.headers['Content-Type'] = 'text/plain'
            self.status = 403
            self.response.write('403 Forbidden')
            return
        if body['type'] == 'url_verification':
            self.verify_url(body)
        elif body['type'] == 'event_callback':
            self.handle_event(body)
        else:
            self.unknown_request()

    def verify_url(self, params):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write(params['challenge'])

    def handle_event(self, params):
        if params['event']['type'] == 'message':
            self.handle_message(params)
        else:
            self.unknown_request()

    def handle_message(self, params):
        logging.info('Slack: team_id={team_id} channel={channel} user={user} text={text}'.format(
            team_id=params['team_id'], channel=params['event']['channel'],
            user=params['event']['user'], text=params['event']['text']))

    def unknown_request(self):
        self.status = 400
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('400 Bad Request')

このコードをGAE上にデプロイ後、実際にSlackで適当なチャンネルに作成したbotを招待した後発言を行えば、GCPのコンソールから以下のようなログが出力されていることを確認できる。

Slack: team_id=ABCDEFG channel=EFGHIJK user=LMNOPQR text=hogehoge

※ 注意: ここで手に入る channel, user は、内部のIDであって通常見ることのできる #channel-name@user.name とは別物である

APIを叩いてメッセージの送信を行う

APIの使い方の詳細や、エンドポイント一覧については以下を参照:

今回は、チャンネルにメッセージを投稿するための chat.postMessage エンドポイントのみを使用する。

chat.postMessage を呼び出すのに必要な最低限のリクエストは、以下のようなものになる:

POST /api/chat.postMessage HTTP/1.1
Content-Type: application/json
Authorization: Bearer MY_BOT_ACCESS_TOKEN

{
  "channel": "CHANNEL_ID",
  "text": "送信したいメッセージ"
}

このうち、MY_BOT_ACCESS_TOKENは先程取得したBot User OAuth Tokenで、channelはチャンネルの内部ID、もしくは # を頭につけたチャンネル名での指定を行う。textは送信したいメッセージの本文である。

先程のコードを少し改良して、上のようなリクエストを投げるだけの最低限の実装を行う。以下のコードは、メッセージを送られてくるたびに「hello」と返信するbotの実装である:

...
from google.appengine.api import urlfetch
...

class EchoHandler(webapp2.RequestHandler):
    ...
    BOT_OAUTH_TOKEN = 'xoxb-XXXXXXXXXXXXXXXXXXXXXXXXXX'

    def handle_message(self, params):
        logging.info('Slack: team_id={team_id} channel={channel} user={user} text={text}'.format(
            team_id=params['team_id'], channel=params['event']['channel'],
            user=params['event']['user'], text=params['event']['text']))
        self.post_message(params['event']['channel'], 'hello')

    def post_message(self, channel, text):
        urlfetch.fetch(
            'https://slack.com/api/chat.postMessage',
            method=urlfetch.POST,
            headers={
                'Content-Type': 'application/json',
                'Authorization': 'Bearer {}'.format(self.BOT_OAUTH_TOKEN)
            },
            payload=json.dumps({
                'channel': channel,
                'text': text
            })
        )

    ...

※ OAuthトークンはVerification Tokenと同様、コード内に含めるべきではない。今回は動作説明のために簡略化している。

なお、今回はHTTPリクエストの送信に urlfetch を使った。

このコードが正常に動けば、以下のように何か発言するたびにbotが返答してくれるのが確認できる。

Botが喋っている図

Botの完成

ここまでくれば残りは簡単。ユーザーが「echo hogehoge」と発言したときのみに反応し、「hogehoge」とメッセージを投稿するように書き換える。

...
import re
...

class EchoHandler(webapp2.RequestHandler):
    ...
    def handle_message(self, params):
        logging.info('Slack: team_id={team_id} channel={channel} user={user} text={text}'.format(
            team_id=params['team_id'], channel=params['event']['channel'],
            user=params['event']['user'], text=params['event']['text']))
        match = re.match(r'echo\s+(.+)', params['event']['text'])
        if match:
            text = match.group(1)
            self.post_message(params['event']['channel'], text)
    ...

コードが正常に動作すれば、以下のように「echo hogehoge」と発言した場合のみbotが返答してくれるようになる。 Botが喋っている図