自主的20%るぅる

各々が自主的に好き勝手書くゆるふわ会社ブログ

今更ChatOpsをしたくてSlack Boltに触れてみた【Agent Grow Advent Calendar 2019:12日目】

はじめに

この記事は Agent Grow Advent Calendar 2019 : 12日目 の記事です。

こんにちは、はじめまして。
福岡の隅っこの方でインフラメインに活動しているMarimon(まりもん)と申します。
弊社としてはたぶん珍しく、開発らしい開発はしていない人間です。

今回はSlack BoltというSlackがリリースしているAPIフレームワークの紹介です。
実践として簡単なキーワード反応するBotが使えるようになるまで、が今回のゴールです。
今更なChatOpsかもしれませんが、どうぞお付き合いください。

いきなり逸れますが、

2019年11月頃から順次Slackのエディタが改修(個人的には改悪)され
WYSIWYGというリッチテキスト形式になりましたね。
バックスラッシュ3つで囲うと勝手にコードブロックされるんですが、
最初の文字が1バイトしか認識せず日本語がおかしくなったり、コードブロックから抜けるのにEnter3回押したりと、
全然慣れないし、改悪にしか見えないのでなんとかしてくださいSlackさん!
adc_12_code
※Issueは行っているようです

Slack Boltについて

さて改めて、Slack Boltとは、SlackさんがリリースしたSlackアプリ開発用のフレームワークです。
日本語のドキュメント もあって良きな感じがします。
Node.js + TypeScriptで実装されていて、Nodeのバージョンは10.0以上が必要になります。

例えばBotの作成であれば今までもHubotなどを利用してお手軽に(でもHubot入れるのはだるい)
色々やろうとするとそこそこ手間な感じで出来ていたのですが、
SlackさんもHubot のアプリを Bolt に移行する方法
というページを公式に用意する、という程度には本気度が伺えます。
Slack 「Hubotをぶっ壊す!(´ω`)」

ということで簡単なBotアプリを作ってみよう

まずは Slackアプリ を作成

  • SlackのAPIページ からアプリを作ります。
    今回は全然すごくない「sugoi-app」という名前を作ります。
    ワークスペースはプライベートのものなので伏せています。
    adc_12_01

Botユーザ を作成

  • 左メニューの「Bot Users」からBotユーザを作ります。
    「Add a Bot User」をクリックします。
    adc_12_02

  • Botの設定は常時オンラインのところだけOnにしました。
    adc_12_03

Slackのワークスペースにアプリ(Bot)を登録

  • 左メニューの「OAuth & Permissions」(もしくは「Install App」)からワークスペースにBotさんを登録します。
    「Install App to Workspace」をクリックします。
    adc_12_04

  • こんな情報にアクセスしますよーという情報が表示されるので、「許可する」をクリックします。
    adc_12_05

  • 登録が完了したらアクセストークンが表示されます。
    adc_12_06

認証情報を取得

  1. 前の画面でアクセストークンが表示されているので、「Bot User」のアクセストークンを控えます。
    xoxb から始まるものを利用します。

  2. Signing Secretを取得します。
    左メニューの「Basic Information」-> 「App Credentials」にある 「Signing Secret」を控えます。
    adc_12_07

ひとまずSlackアプリ側はこれで一旦完了です。

Bot側の準備

適当なサーバの準備

Botを動かすにはなんらかサーバが必要になります。
詳しくないのですが「ngrok」や「Glitch」を使えばローカルでも簡単に公開できるようです。
ふつーに便利そうなので、今度使ってみたいです。特にGlitchイイネ!
今回は適当なサーバ(GCP@CentOS7)を利用しています。
AWSはEC2やLambdaでもOK、Herokuもいけるみたいです。
GCPのCloudFunctionは自分の設定が悪いのかうまくいきませんでした。

nodeの導入

最初からnode10以上が入っていれば特に作業は不要です。
今回は13.2.0を導入しました。
導入と言っても、パッケージをダウンロード、展開、パスを通す、だけです。
※すべて一般ユーザ権限で行っています、root権限は今回の作業では不要です

$ wget https://nodejs.org/dist/v13.2.0/node-v13.2.0-linux-x64.tar.xz
$ tar Jxfv node-v13.2.0-linux-x64.tar.xz
$ echo "export PATH=$PATH:~/node-v13.2.0-linux-x64/bin/" >> .bashrc
$ source .bashrc

Boltプロジェクトの設定

  • npm init で初期化します。
    • author以外はEnter叩いたのみです。
$ mkdir sugoi-app
$ cd sugoi-app/
$ npm init
package name: (sugoi-app)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author: Marimon
license: (ISC)
  • 認証情報を環境変数として設定します。
export SLACK_SIGNING_SECRET=e73cXXXX6a7
export SLACK_BOT_TOKEN=xoxb-951831XXXfcvS
  • Boltをインストールします。
npm install @slack/bolt

イベントの設定

このあたりからがポイントですが、BotアプリとSlackを連携する設定です。
ここでのイベントとは、Slackにログインしたり、発言したり、といったことを指します。

サーバ側にSlackと連携するBotスクリプトを設置

  • app.jsを作成します。
    • トークン情報を読み込んで、3000番ポートでListenするだけのものです。
$ vim app.js
const { App } = require('@slack/bolt');

// Initializes your app with your bot token and signing secret
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET
});

(async () => {
// Start your app
await app.start(process.env.PORT || 3000);

console.log('⚡️ Bolt app is running!');
})();
  • アプリを起動します。
$ node app.js
⚡️ Bolt app is running!

SlackのAPP管理画面でイベント機能を有効化

  • APP管理画面
    • 左メニューの「Event Subscriptions」をクリックし、「Enable Events」を有効化します。
      adc_12_08
  • 先程起動したアプリのURLを「Request URL」に指定します。
    • サーバのアドレスに3000ポートを指定します
    • Boltアプリは/slack/events をリスニングするため、アドレスは以下のようになります
    • http://example.com:3000/slack/events
      adc_12_09
  • Verifiedとでなければ残念ながらどこか設定に誤りがあります。
    • アプリが起動していない、起動に失敗している
    • サーバのファイアウォールがブロックしている
    • etcetc

反応するイベントを設定

ここの設定をしておかないと、どれだけ頑張ってBotを作ってもうまく動かないので注意です。
※先程と同じページ(左メニューの「Event Subscriptions」)に「Subscribe to bot events」という項目があります。
例えば
* DMでキーワードに反応する
* メンションの場合にキーワードに反応する
* 普通のチャンネルでキーワードに反応する
といったように、どのタイミング(イベント)で反応するかの設定が必要になります。
adc_12_10

キーワードに反応するBotを作成

Slackのチャンネル、もしくはDMで「hello」とつぶやくと、返事をしてくれるBotを目指します。

アプリ側のコードを修正

  • マニュアルページからまるっといただきました
const { App } = require('@slack/bolt');

const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET
});

// Listens to incoming messages that contain "hello"
app.message('hello', ({ message, say }) => {
// say() sends a message to the channel where the event was triggered
say(`Hey there <@${message.user}>!`);
});

(async () => {
// Start your app
await app.start(process.env.PORT || 3000);

console.log('⚡️ Bolt app is running!');
})();
  • アプリを起動します。
$ node app.js
⚡️ Bolt app is running!

起動しました。

Slackでテスト

ようやくテストです。テストするまでが長かった・・・。
最初はBotユーザがSlackに表示されていないかもしれないので、アプリから追加します。

  • Slackの左のメニューからAppを追加します。
    adc_12_11

  • sugoi-appを「表示」します。※チャンネルに追加する場合だと「追加」でした。
    adc_12_12

  • 設定をクリックするとブラウザが開きます。
    adc_12_13

  • ブラウザに飛ぶので、再度Slackで開きます。
    adc_12_14

  • sugoi-appとDMができるようになりました。
    adc_12_15

  • テストしてみます。
    adc_12_16

とりあえず反応してくれたー!
あとは如何様にでもしてくれるな・・・。
ということで今回のテーマ的な最低ラインはこの辺まで。後はオマケです。ざっくりです。

おまけ1:ボタンを表示

Botの応答にボタンを表示してみます。
ボタンの表示やセレクトアイテム、メニューボタンを作ったりするのに
Slackブロックビルダー というのが用意されていて、
これが普段コードをあまり書かない人間からすると便利でした。

Slack 設定画面

  • sugoi-appの設定画面「Interactive Components」を有効にします。
    • Actionsを設定、「Callback ID」を設定します。
      adc_12_17

Bot側のコード修正

  • コード側はマニュアルを参照しつつブロックビルダーから適当にポチポチと・・・
const {
    App
} = require('@slack/bolt');

const app = new App({
    token: process.env.SLACK_BOT_TOKEN,
    signingSecret: process.env.SLACK_SIGNING_SECRET
});

// Listens to incoming messages that contain "hello"
app.message('hello', ({
    message,
    say
}) => {
    // say() sends a message to the channel where the event was triggered
    say({
        blocks: [{
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": `Hey there !`
                },
                "accessory": {
                    "type": "button",
                    "text": {
                        "type": "plain_text",
                        "text": "Click Me"
                    },
                    "action_id": "button_click"
                }
            },
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": "Pick one or more items from the list"
                },
                "accessory": {
                    "type": "multi_static_select",
                    "placeholder": {
                        "type": "plain_text",
                        "text": "Select items",
                        "emoji": true
                    },
                    "options": [{
                            "text": {
                                "type": "plain_text",
                                "text": "Choice 1",
                                "emoji": true
                            },
                            "value": "value-0"
                        },
                        {
                            "text": {
                                "type": "plain_text",
                                "text": "Choice 2",
                                "emoji": true
                            },
                            "value": "value-1"
                        },
                        {
                            "text": {
                                "type": "plain_text",
                                "text": "Choice 3",
                                "emoji": true
                            },
                            "value": "value-2"
                        }
                    ]
                }
            }
        ]
    });
});

(async () => {
    // Start your app
    await app.start(process.env.PORT || 3000);

    console.log('⚡️ Bolt app is running!');
})();

いざテスト!

  • ハロー!
    adc_12_18

hello -> Click Me ボタンをクリックすると、自分宛てにMentionが飛んできました!

  • セレクトボタンはどうかな?
    adc_12_19

なるほどねー。はー簡単。

おまけ2:/slashコマンドを作る

こっちも割と使うslashコマンドです。
Slackのメッセージ入力のところでスラッシュを入れるとたくさんコマンドが出てくる、アレです。
/ticket と入力するといい感じの画面が出てこないかなぁ?

Slack 側設定画面

  • 設定画面
    「Slash Commands」 というメニューがあるのでそこで作れます。
    adc_12_20

  • Commandを指定して、URLは同じものを指定するぐらいです
    adc_12_21

Bot側のコード修正

  • 以下を追加しました。
app.action('button_click', ({ body, ack, say }) => {
  // Acknowledge the action
  ack();
  say(` clicked the button`);
});

// コマンド起動をリッスン
app.command('/ticket', ({ ack, payload, context }) => {
  // コマンドのリクエストを確認
  ack();

  try {
    const result = app.client.views.open({
      token: context.botToken,
      // 適切な trigger_id を受け取ってから 3 秒以内に渡す
      trigger_id: payload.trigger_id,
      // view の値をペイロードに含む
      view: {
        type: 'modal',
        // callback_id が view を特定するための識別子
        callback_id: 'view_1',
        title: {
          type: 'plain_text',
          text: 'Modal title'
        },
        blocks: [
          {
            type: 'section',
            text: {
              type: 'mrkdwn',
              text: 'Welcome to a modal with _blocks_'
            },
            accessory: {
              type: 'button',
              text: {
                type: 'plain_text',
                text: 'Click me!'
              },
              action_id: 'button_abc'
            }
          },
          {
            type: 'input',
            block_id: 'input_c',
            label: {
              type: 'plain_text',
              text: 'What are your hopes and dreams?'
            },
            element: {
              type: 'plain_text_input',
              action_id: 'dreamy_input',
              multiline: true
            }
          }
        ],
        submit: {
          type: 'plain_text',
          text: 'Submit'
        }
      }
    });
    console.log(result);
  }
  catch (error) {
    console.error(error);
  }
});

いざテスト!

  • /ticket とタイプしてエンター!
    adc_12_22

  • Modalが表示されました
    adc_12_23

最後に

意外に簡単、意外に面倒、そんな感じでした。そこまで楽さは感じてません(笑
ただ、今回利用したコード類はほとんどマニュアルから頂いたものなのでコードも殆ど書いてないですし、ブロックビルダー もあって、簡単やりたいことができる気がしてます。

今回のものを応用して、
1. とあるサイトのRSSをSlackで読み込む
2. とあるキーワードがあったらMention通知&ボタン表示する
3. ボタンが押されるととあるアクションを起
ということを行ってます!プログラム初級者でも意外に楽にいけました。

そんなわけなので、誰でも簡単(とはいかないまでも)にChatopsの構築ができそうな感じですね!

Let’s share this article!

{ 関連記事 }

{ この記事を書いた人 }

Marimon
記事一覧