Slack
Webhook
slackbot

SlackのIncoming Webhooksを使い倒す

More than 1 year has passed since last update.

この記事はピクシブ株式会社 AdventCalendar 2017の14日目の記事です。
昨日は @Ragg による CSSをRailsとゆるふわにお付き合いさせる話でした。

こんにちは、普段はpixivの決済基盤のシステムを担当しているエンジニアのikです。
(※今回は決済システムの話はしません)


Slack Incoming Webhooksとは

Slackには、様々なAPIや外部サービスとの連携機構があります。Incoming Webhooksはその中の1つで「外部サービスからSlackにメッセージを送信する」ための機能です。

この機能を使うと、外部からHTTPリクエストでメッセージを送ることができます。本質はHTTPリクエストを送るだけなのでライブラリ等を導入する必要もなく、HTTP通信ができる言語・環境であればどこからでも送ることができます。

Incoming Webhooksのエンドポイントを取得する

この機能を使うには、まず送信先のエンドポイントをSlackで作成する必要があります。

Slackのアプリケーション追加ページより incoming-webhooks を検索・選択します。
image.png

Incoming Webhooksの設定画面が開かれるので、 Add Configuration から設定を追加します。
image.png

メッセージを送信するチャンネルを選ぶ画面が表示されるので、選択して Add incoming WebHooks integration をクリックすると、webhookのエンドポイントが発行されます。
image.png

image.png
(注: このURLを知っていればだれでも今追加したチャンネルにメッセージをPOSTできてしまうので、関係ない第三者が見れないように注意してください。)

また、このページでデフォルトで表示されるbot名・アイコン、Descriptionを設定することができます。
image.png
設定した値は、このURLに対してPOSTしたメッセージを表示するときに使用されます。

image.png

テキストをPOSTする

単純なメッセージを送信するには、以下のようなJSONを payloadパラメータとして、エンドポイントに対してPOSTします。

{"text": "TEST"}

たとえばこのメッセージをシェル上でcurlを使って送信する場合は以下のようにします。

$ curl -X POST --data-urlencode "payload={\"text\": \"TEST\" }" https://hooks.slack.com/services/TAAAAAAAA/BBBBBBBBBB/ZZZZZZZZZZZZZZZZZZZZZ

image.png

装飾をしたメッセージ群を送る

先程の例では、シンプルなテキストのみを送りましたが、より複雑な装飾をしたメッセージを送ることもできます。

例えば以下のJSONをPOSTすると以下の画像のようなメッセージとして表示されます。

{
   "attachments":[
      {
         "fallback":"fallback Test",
         "pretext":"attachments Test",
         "color":"#D00000",
         "fields":[
            {
               "title":"attachment01",
               "value":"This is attachment"
            }
         ]
      }
   ]
}   

image.png

この時、表示されていない fallback はattachmentsが表示できない環境(プッシュ通知の文言等)の表示に使用されます。

また、attachmentsは複数同時に渡すことも可能です。

{
   "attachments":[
      {
         "fallback":"fallback Test",
         "pretext":"attachments Test",
         "color":"#D00000",
         "fields":[
            {
               "title":"attachment01",
               "value":"This is attachment"
            }
         ]
      },
      {
         "fallback":"fallback Test",
         "pretext":"attachments Test02",
         "color":"#00FF00",
         "fields":[
            {
               "title":"attachment02",
               "value":"This is attachment02"
            }
         ]
      }
   ]
}   

image.png

attachmentsには、ここで書いた以外にも様々な表示オプションがあり、それらを使用すれば以下のようなメッセージも作成できます。

{
   "attachments":[
      {
         "author_name": "ik-fib",
         "author_link": "https://twitter.com/ik11235",
         "author_icon": "https://s.gravatar.com/avatar/ce68aabafe314bb9524700960fe7b6dc?s=80",
         "fallback":"ik-fibがツイートしました",
         "pretext":"ik-fibがツイートしました",
         "color":"#D00000",
         "fields":[
            {
               "title":"Twitter",
               "value":"なう"
            }
         ],
         "footer": "Slack API",
         "footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
         "ts": 123456789
      }
   ]
}

image.png

attachmentsのオプションはこれ以外にもいくつもあり、以下の公式ドキュメントでも確認できます。

Tips

Slackでの表示を簡単に確認する

webhookを使ってメッセージ送信botを作る際に、思っていたフォーマットになるかどうかを毎回JSONをPOSTして確認するのはなかなかに面倒な作業です。
しかし、JSONを書くとそれがどのようなSlackメッセージになるかをすぐにプレビューできる機能が公式から提供されています

Message Formatting | Slack

こちらではテキストエリアに記載したJSONをすぐにslackのメッセージ形式でプレビューできます。
image.png

ただし、後述するアイコンの絵文字指定機能でカスタム絵文字を指定した場合は解釈されずに文字のまま処理されたり、一部は完全には動きません。(カスタム絵文字まで処理を期待するのは流石に厳しいですが、昔は一部正常オプションも解釈してくれずエラー扱いになった記憶があるので…)

ポストごとに送信するチャンネルを変える

基本的にwebhookでメッセージが送信されるチャンネルは最初の設定で行ったチャンネルですが、channel にチャンネル名を指定することでポストごとに送信するチャンネルを変えることも可能です。

{
    "channel": "#new_channel",
    "text": "I am a test message http://slack.com"
}

ただし、この方法でチャンネル指定をしている場合、その送信先のチャンネル名を変更した場合、その名前をもつチャンネルがなくなるのでエラーになります。(webhookの設定で指定している場合は自動で対応される)
コード上も変更が必要になるため、同一botで可変にする必要がある場合や、テスト送信をsandboxのチャンネルに向けるなど一時的な使い方以外にはあまりおすすめしません。

ポストごとにbotの表示名を変える

基本的にbotメッセージの表示名は設定で指定したものになりますが、 username で可変的に指定することが可能です。

{
    "username": "スーパーwebhookおじさん",
    "text": "test message"
}

image.png

ポストごとにbotのアイコンを変える

基本的にbotメッセージのアイコンは設定で指定したものになりますが、 icon_emoji で可変的に指定することが可能です。

{
    "icon_emoji": ":bow:",
    "text": "Test message"
}

image.png

また、これはslackに最初から設定されている絵文字以外にもカスタム絵文字機能を使って追加した絵文字も指定可能です。

image.png

{
    "icon_emoji": ":cool_emoji:",
    "text": "カスタム絵文字も指定できます"
}

image.png

ユーザーにメンションを送る

webhookに@つきでテキストを書いてもそのままではユーザーへのメンションになりません。

{
    "text": "Hello @pixiv",
}

image.png

これをメンションにするには、 link_names を指定します。

{
    "text": "Hello @pixiv",
    "link_names": 1
}

image.png

他の方法としては、<>で囲むことでも有効になります。

{
    "text": "good night <@pixiv>"
}

image.png

ただし、この書式で@here@channel指定をする場合は、@ではなく!で指定する必要があります。

{
    "text": "come on <@here> <@channel>"
}

image.png

{
    "text": "come on <!here> <!channel>"
}

image.png

ただし有料プランで使えるユーザーグループに対してのメンションはこれらの方式ではできず、グループのID(slack側で自動で割り振られている固有の英数字)を調べて指定する必要があります。

URLリンクを特定の文字列につける

メッセージ内にURLを含めたら、自動的にリンクとして処理してくれますが、長いURLの場合や文章内に入れる場合には、特定のテキストに対して、リンクを付けたい場合もあります、
その場合、 <URL|特定の文字列> として表記することでリンクとしてメッセージになります。

{
    "text": "<https://pixiv.net/|This is pixiv link>"
}

image.png

attachments内にslackのメッセージ書式設定を適用する

slackには、Markdownに似たメッセージ書式設定があり、webhookによるメッセージでも使えますが、attachmentsの中ではそのままでは有効になりません。

{
    "text": "ここでは \`何も指定しなくても\` *メッセージ* _書式指定_ が反映されるけど、",
    "attachments": [
        {
            "text": "ここではそのままでは *使えない*"
        }
    ]
}

image.png

attachments内部の要素にこの記法を適用するには、mrkdwn_in で適用したい要素を指定する必要があります。

{
    "text": "ここでは \`何も指定しなくても\` *メッセージ* _書式指定_ が反映されるけど、",
    "attachments": [
        {
            "text": "ここで使うには _以下の_ *指定が必要* ",
            "mrkdwn_in": [
                "text"
            ]
        }
    ]
}

image.png

利用例

ここからは実際にピクシブの業務でも使っているSlack webhookの利用例を紹介します。

Jenkinsのビルドの成否を通知する

ピクシブでは一部のプロジェクトでJenkinsを使用しています。

その中で、Jenkinsのビルド結果によって異なる内容をSlackに通知するのを、シェルスクリプトで実行しています。
(「Slack Notification Plugin」 というJenkinsプラグインもありますが、プロジェクトごとやビルドごとに異なる内容を柔軟に変えたいため、シェルスクリプトで実行しています。(プラグインを使っているプロジェクトもあります))

WEBHOOK_URL="https://hooks.slack.com/services/TAAAAAAAA/BBBBBBBBBB/ZZZZZZZZZZZZZZZZZZZZZ"

POST_DATA=`cat << EOF
    payload={
    "text": "ジョブ開始 <${BUILD_URL}|${BUILD_TAG}>"
  }
EOF`
curl --data-urlencode "${POST_DATA}" ${WEBHOOK_URL}

# jenkinsで実行したい処理本体
php batch/job.php &&:
RET=$?

if [ ${RET} -eq 0 ]; then
  POST_TEXT="ジョブ成功"
else
  POST_TEXT="@channel ジョブ失敗! <${BUILD_URL}|失敗したジョブ>"
fi

POST_DATA=`cat << EOF
    payload={
    "text": "${POST_TEXT}",
    "link_names": 1
  }
EOF`
curl --data-urlencode "${POST_DATA}" ${WEBHOOK_URL}

exit $RET

バッチ処理本体の本体部分を &&: しているのは、jenkinsが返り値が0以外の場合、即時エラーとして処理を停止 & ビルド失敗とするため、後続の失敗通知を処理してもらうために使用しています。

これ以外にも該当箇所のみ set +e を指定して0以外の返り値でも処理を続行するように指定する方法もありますが、このようにしないとビルド本体の終了コードを元に処理を書くのが困難です。

rubyの集計スクリプト結果をPOSTする

webhookは単純なPOSTリクエストでしかないので、インターネットにPOSTできる環境であれば、どこからでもPOSTできます。
例えば、Slackのサーバからアクセスして通知するような連携だと、外部からアクセス不可能なネットワークにあるデータを取得することはできませんが、
webhookで通知するスクリプトと内部のネットワーク内部で動かしてSlackにpostすることでそのようなデータでも通知可能です。(外部サーバにPOSTするので、扱うデータのポリシーは所属する組織・団体のルールに合わせて注意してください。)

require 'net/https'
require 'json'

# 通知したいデータの集計や整形処理

result = #通知したいデータを処理した結果

uri = URI.parse("https://hooks.slack.com/services/TAAAAAAAA/BBBBBBBBBB/ZZZZZZZZZZZZZZZZZZZZZ")

request = Net::HTTP::Post.new(uri.request_uri)
request.body = {text: "処理結果: #{result}"}.to_json

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

http.start do |h|
  puts h.request(request).body
end

rubyには、SlackAPIを扱えるgemもありますが、単純な通知だけなら上記のように、gemを使わずに単純なPOSTだけでも可能です。

まとめ

Slackには、様々なアプリ連携機能がありますが、Webhookを使えば対応していないサービスや自社サービス、自前のPCからもメッセージが多種多様・好きな形式で送れるので、用途に合わせた通知をすることができます。

ここで紹介した以外にも、webhookでインタラクティブなボタン付きのメッセージを送るなどもできるので、ぜひ試してみてください。


ピクシブ株式会社では、botを駆使して業務を効率化できるエンジニアを募集しています。

明日は @orekyuu がなんとかJの話をしてくれるかもしれないので、乞うご期待