Why not login to Qiita and try out its useful features?

We'll deliver articles that match you.

You can read useful information later.

0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【チャットボット風】Interactiveな対話形式Slack Botを作ってみる

Last updated at Posted at 2021-08-08

Untitled Diagram(20).png

背景

業務効率化のため、何か知りたい事があった場合にSlackのスラッシュコマンドで呼び出せるチャットボット的な存在が欲しいと思いました。

たとえば、組織の仕組みや開発・運用ノウハウといった情報をそこにまとめておき、質問に対して適宜レスポンスをしてくれるようにしておけば、新入社員さんなどに「もし困ったら一旦このSlack Botに聞いてみてくださいね」といった感じである程度の部分は任せてしまう事ができるため、教育コストといった面でも相当楽になるんじゃないかなと。

世は大リモートワーク時代。業務のほとんどがオンラインで行われるようになった事で、「隣に座る同僚に対してラフに質問を投げかける」といったオフラインならではの行為が難しくなってしまいました。

どんな些細な疑問であってもわざわざかしこまった質問メッセージを作成しなければいけないとなると、精神的な負担も大きくなってしまいますよね。

そこで、「テンプレ的な回答が可能なものに関しては全てSlack Botにやらせてしまおう!」と考えたのがそもそもの始まりです。

完成イメージ

interactive_bot.gif

今回はサンプルとして「/animals」というスラッシュコマンドを作成してみました。

コマンドを実行すると「どの動物について知りたいですか?」とSlack Botから聞かれるので、いずれかのボタンを選択すると回答に合わせた結果(今回はWikipediaの記事)を返してくれます。

とてもシンプルですが、このロジックを上手く活用すれば簡易的なチャットボットのようなものが作れるはず。

仕様

  • 言語: Ruby 2.7
  • フレームワーク: Sinatra

今回はRubyとその軽量フレームワークであるSinatraを採用しました。

実装

前置きはほどほどに、早速実装していきましょう。

Slackアプリの作成

まず、Slack Botを動かすための認証トークンなどを取得していきます。

スクリーンショット 2021-08-08 7.15.46.png

https://api.slack.com/apps

↑のページからSlackアプリを作成しましょう。

スクリーンショット 2021-08-08 7.18.51_censored.jpg

  • App Name
    • 適当なアプリ名を入力
  • Pick a workspace to develop your app in
    • 追加したいワークスペースを選択

スクリーンショット 2021-08-08 7.24.59.png

Slackアプリの作成が終わったら、左サイドバーの「OAuth & Permissions」をクリックし、Scopesの設定を行います。今回はスラッシュコマンドの実行とメッセージの送信ができれば良いので、

  • commands
  • chat:write

を追加してください。

スクリーンショット 2021-08-08 7.26.28.png

Scopesの設定が終わったら、「Install to WorkSpace」をクリック。

スクリーンショット 2021-08-08 7.29.11_censored.jpg

すると認証トークンが発行されるので、一旦メモなどに控えておきましょう。

スクリーンショット 2021-08-08 7.35.16_censored.jpg

あとはチャンネル設定の「インテグレーション」から「アプリを追加する」をクリックし、先ほど作ったSlackアプリを追加すればOKです。

スクリーンショット 2021-08-08 7.37.45_censored.jpg

サーバーの作成

次は、スラッシュコマンドが実行された後に結果を返すためのサーバーを作成していきましょう。

作業ディレクトリ & 各種ファイルの作成

$ mkdir interactive-slack-bot && cd interactive-slack-bot
$ mkdir data
$ touch data/dog.json data/cat.json data/rabbit.json data/questions.json app.rb Gemfile Gemfile.lock .env

最終的に次のようなディレクトリ構成になっていればOKです。

interactive-slack-bot
└── data
    ├── cat.json
    ├── dog.json
    ├── questions.json
    └── rabbit.json
├── Gemfile
├── Gemfile.lock
├── app.rb

Rubyのバージョンを指定

$ rbenv local 2.7.1

※バージョン管理にrbenvを使っている事を想定。

お好みのバージョンで構いませんが、今回は2.7.1で動作確認済みなのでなるべく近いものにすると正常な動作が保証されると思います。

各種gemのインストール

./Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "sinatra"
gem "rack"
gem "rack-contrib"
gem "slack-ruby-bot"
gem "dotenv"

これから必要になるgemをインストールしましょう。

$ bundle install --path vendor/bundle

コード

./env
SLACK_BOT_TOKEN=xoxb-**************-**************-**************
./app.rb
require "sinatra"
require "slack-ruby-client"
require "json"
require "dotenv"

Dotenv.load

client = Slack::Web::Client.new(token: ENV["SLACK_BOT_TOKEN"])

# POSTメソッドで「/slack/command」にリクエストが来た場合の処理
post "/slack/command" do
  blocks = File.open("./data/questions.json"){ |j| JSON.load(j) }
  
  channel = params["channel_id"] # どのチャンネルから送信されたのかを取得
  user = params["user_id"] # どのユーザーから送信されたのかを取得

  # スラッシュコマンド実行者に対してのみ表示されるメッセージ(ephemeral)を作成
  client.chat_postEphemeral(
    channel: channel,
    user: user,
    blocks: blocks,
    as_user: true
  )

  return
end

# POSTメソッドで「/slack/command/answer」にリクエストが来た場合の処理
post "/slack/command/answer" do
  # Interactiveボタン押下の結果はpayloadという値で渡ってくる
  payload = JSON.parse(params[:payload])

  # case文でどのJSONを返すか分岐
  blocks = case payload["actions"][0]["value"]
    when "dog"
      File.open("./data/dog.json"){ |j| JSON.load(j) }
    when "cat"
      File.open("./data/cat.json"){ |j| JSON.load(j) }
    when "rabbit"
      File.open("./data/rabbit.json"){ |j| JSON.load(j) }
  end
  
  channel = payload["channel"]["id"] # どのチャンネルから送信されたのかを取得
  user = payload["user"]["id"] # どのユーザーから送信されたのかを取得

  # スラッシュコマンド実行者に対してのみ表示されるメッセージ(ephemeral)を作成
  client.chat_postEphemeral(
    channel: channel,
    user: user,
    blocks: blocks,
    as_user: true
  )

  return
end
./data/questions.json
[
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "どの動物について知りたいですか?"
    }
  },
  {
    "type": "actions",
    "elements": [
      {
        "type": "button",
        "text": {
          "type": "plain_text",
          "text": "犬について",
          "emoji": true
        },
        "value": "dog"
      },
      {
        "type": "button",
        "text": {
          "type": "plain_text",
          "text": "猫について",
          "emoji": true
        },
        "value": "cat"
      },
      {
        "type": "button",
        "text": {
          "type": "plain_text",
          "text": "兎について",
          "emoji": true
        },
        "value": "rabbit"
      }
    ]
  }
]
./data/dog.json
[
  {
    "type": "image",
    "image_url": "https://www.pakutaso.com/shared/img/thumb/PPW_uturogenashibaken_TP_V.jpg",
    "alt_text": "image"
  },
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "犬については次の記事を読んでください。"
    },
    "accessory": {
      "type": "button",
      "text": {
        "type": "plain_text",
        "text": "Click Me",
        "emoji": true
      },
      "url": "https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%8C"
    }
  }
]
./data/cat.json
[
  {
    "type": "image",
    "image_url": "https://www.pakutaso.com/shared/img/thumb/AME19716064_TP_V.jpg",
    "alt_text": "image"
  },
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "猫については次の記事を読んでください。"
    },
    "accessory": {
      "type": "button",
      "text": {
        "type": "plain_text",
        "text": "Click Me",
        "emoji": true
      },
      "url": "https://ja.wikipedia.org/wiki/%E3%83%8D%E3%82%B3"
    }
  }
]
./data/rabbit.json
[
  {
    "type": "image",
    "image_url": "https://www.pakutaso.com/shared/img/thumb/USAGI0I9A6075_TP_V.jpg",
    "alt_text": "image"
  },
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "兎については次の記事を読んでください。"
    },
    "accessory": {
      "type": "button",
      "text": {
        "type": "plain_text",
        "text": "Click Me",
        "emoji": true
      },
      "url": "https://ja.wikipedia.org/wiki/%E3%82%A6%E3%82%B5%E3%82%AE"
    }
  }
]

各JSONについては、 Block Kit Builder を使って作成したものを使っています。簡単に言うと、Slackのメッセージをよりリッチな感じで作れるツールですね。

スクリーンショット 2021-08-08 8.05.49_censored.jpg

たとえば、「./data/questions.json」の内容だと↑こんな風なメッセージが送信できます。

ボタンだけでなくテキストフォームやチェックボックスなどたくさんのパーツがあるので、時間のある時にでも色々試してみてください。

スラッシュコマンドの作成

スクリーンショット 2021-08-08 8.13.53.png

Slackアプリの設定画面を開き、左サイドバーの「Slash Commands」から「Create New Command」をクリック。

スクリーンショット 2021-08-08 8.18.13.png

  • Command: スラッシュコマンド
    • /animals
  • Request URL: スラッシュコマンドを実行した際に飛ぶリクエスト先URL
    • https://***********.ngrok.io/slack/command
    • localhostでは動かないため、ngrokを使って独自のドメインを割り当てる
    • 参照: ngrokの利用方法
    • Sinatraのポート番号は4567なので「$ ngrok http 4567」
  • Short Description: コマンドの説明
    • 動物のWikipedia記事を返すコマンド
  • Usage Hint: 使い方のヒント
    • 空欄でOK。

各項目を入力し、保存してください。

これで今回の場合、「/animals」というスラッシュコマンドを実行すると「https://***********.ngrok.io/slack/command」にPOSTリクエストが飛ぶようになり、先ほど「./app.rb」ファイル内で定義した

./app.rb
# POSTメソッドで「/slack/command」にリクエストが来た場合の処理
post "/slack/command" do
  blocks = File.open("./data/questions.json"){ |j| JSON.load(j) }
  
  channel = params["channel_id"] # どのチャンネルから送信されたのかを取得
  user = params["user_id"] # どのユーザーから送信されたのかを取得

  # スラッシュコマンド実行者に対してのみ表示されるメッセージ(ephemeral)を作成
  client.chat_postEphemeral(
    channel: channel,
    user: user,
    blocks: blocks,
    as_user: true
  )

  return
end

この部分の処理が走るというわけですね。

Interactivityの有効化

最後に、Interactivityの有効化を行います。

スクリーンショット 2021-08-08 8.29.21.png

今のままだと、送信されてきたメッセージ内のボタンを押した際にその内容をサーバーに伝える術がありません。つまり、ボタンを押しても何も起こらずエラーになってしまいます。

スクリーンショット 2021-08-08 8.32.25.png

これを解決するためには、Slackアプリ管理画面の左サイドバーの「Interactivity & Shortcuts」からInteractivityをONにする必要があります。

スクリーンショット 2021-08-08 8.34.39.png

  • Request URL
    • https://***********.ngrok.io/slack/command/answer

ONに変更後、Request URLに上記のようなURLを入力しましょう。

すると、今後Interactiveなコンポーネント(先ほど Block Kit Builder で作成したようなボタン、テキストフォーム、チェックボックスなど)に対してリアクションを起こした際には「https://***********.ngrok.io/slack/command/answer」にPOSTリクエストが飛ぶようになり、先ほど「./app.rb」内で定義した

./app.rb
# POSTメソッドで「/slack/command/answer」にリクエストが来た場合の処理
post "/slack/command/answer" do
  # ボタン押下の結果はpayloadという値で渡ってくる
  payload = JSON.parse(params[:payload])

  # case文でどのJSONを返すか分岐
  blocks = case payload["actions"][0]["value"]
    when "dog"
      File.open("./data/dog.json"){ |j| JSON.load(j) }
    when "cat"
      File.open("./data/cat.json"){ |j| JSON.load(j) }
    when "rabbit"
      File.open("./data/rabbit.json"){ |j| JSON.load(j) }
  end
  
  channel = payload["channel"]["id"] # どのチャンネルから送信されたのかを取得
  user = payload["user"]["id"] # どのユーザーから送信されたのかを取得

  # スラッシュコマンド実行者に対してのみ表示されるメッセージ(ephemeral)を作成
  client.chat_postEphemeral(
    channel: channel,
    user: user,
    blocks: blocks,
    as_user: true
  )

  return
end

この部分の処理が走るという仕組みです。

動作確認

全ての実装が完了したので、いよいよ動作確認です。

$ bundle exec ruby app.rb

[2021-08-08 08:44:35] INFO  WEBrick 1.6.0
[2021-08-08 08:44:35] INFO  ruby 2.7.1 (2020-03-31) [x86_64-darwin19]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2021-08-08 08:44:35] INFO  WEBrick::HTTPServer#start: pid=54712 port=4567

スクリーンショット 2021-08-08 8.49.24_censored.jpg

Slack Botを追加したチャンネル内で「/animals」と入力し、

スクリーンショット 2021-08-08 8.50.56.png

こんな感じで対話できるようになっていれば成功です。

あとがき

以上、簡易チャットボット風の対話形式Slack Botを作ってみました。リモートワーク中心の社会となった事で、Slackの活用は業務効率化のために欠かせないものになっていると感じます。

その気になればもっと便利な機能も実装できそうなので、この記事をベースにぜひとも色々と試してみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Qiita Conference 2025 will be held!: 4/23(wed) - 4/25(Fri)

Qiita Conference is the largest tech conference in Qiita!

Keynote Speaker

ymrl、Masanobu Naruse, Takeshi Kano, Junichi Ito, uhyo, Hiroshi Tokumaru, MinoDriven, Minorun, Hiroyuki Sakuraba, tenntenn, drken, konifar

View event details
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?