Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
37
Help us understand the problem. What is going on with this article?
@seratch

Bolt for JavaScript を使った Slack アプリ開発で知っておくと捗る 7 つのこと

Bolt とは?

Slack でユーザとのインタラクション(メッセージ内の Block Kit のボタンのクリック、プルダウンでの選択、モーダルやダイアログからのデータ送信、Home タブの制御 etc)を処理したり、Events API を subscribe したりする Slack アプリを作る場合、Slack からリクエストを受け付ける Web サービスを立ててあげる必要があります。

この Web サービスを Node.js で簡単に、かつ迷わず作るためのフレームワークとして、2019 年に Slack から公開されたのが Bolt for JavaScript です。この辺の記事もみてみてください。

知っておくとちょっと便利な 7 つのこと

Bolt は比較的簡単に扱えるフレームワークだと思いますが、知っておくと便利かなということがいくつかあるので、それを共有したいと思います。

  • 再起動なしで変更を反映
  • .env の活用 & 強制上書き
  • ログレベルの変更
  • リクエスト情報を丸ごとログ出力
  • FaaS で動かすなら processBeforeResponse: true を設定
  • サンプル・テンプレート
  • コミュニティ

なお、これから説明することの多くは以下のリポジトリに盛り込んであります。コードを見る方が早いという方はこちらを見てください。もしもこのテンプレートが何かの役に立ったなら :star: を増やしておいてください :smiley:

プロジェクトを始めるのに必要な手順を示した 日本語の README もありますので、そちらも参考にしてみてください。

再起動なしで変更を反映

まず、基本的なところから。コードを書きながら動作を確認するときにいちいち再起動していては効率が悪すぎます。

シンプルな Bolt アプリであれば、以下のように npm run local (名前は違うものでもよいでしょう)のような感じで nodemon を使うのが簡単でわかりやすいですね。実際に運用するときは npm start で起動させます。

{
  "scripts": {
    "local": "node_modules/.bin/nodemon index.js",
    "start": "node index.js"
  },
  "dependencies": {
    "@slack/bolt": "^2.1.1",
    "dotenv": "^8.2.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.1"
  }
}

TypeScript の場合、webpack を使われているケースも多いですが、シンプルにやるなら nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts のようにできるかと思います。

TypeScript で使う場合は、こちらの記事も参考にしてみてください。

.env の活用 & 強制上書き

これは Bolt 固有ではないですが、Node.js のアプリで、環境変数の参照だけでなく .env というファイルからそれらの情報を読み込むために dotenv という npm パッケージが広く使われています(使わないといけないわけではないです)。

ただ、この dotenv は同じキー名が環境変数にもある場合、.env での設定は無視されます。ローカルでの開発の場合だと上書きしたいユースケースも多いかと思います。その場合は以下のようなコードをそのまま貼り付けておくと .env を正として上書きしてくれるので便利かもしれません。

const config = require("dotenv").config().parsed;
for (const k in config) {
  process.env[k] = config[k];
}

また .gitignore.env も追加することを忘れずに。.env.sample とか _env のような名前で雛形を置いておくのもよいですね。

ログレベルの変更

デフォルトだと Bolt は INFO レベルでログを出力します。デバッグしているときに Bolt の内部で出力されているログをもう少し見たいというときは、以下のような感じで、logLevel を渡します。

const { App } = require("@slack/bolt");
const { LogLevel } = require("@slack/logger");

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

app.command("/echo", fn)fn にあたるリスナーの関数内でも、同じ設定の Logger を使ってログを出したいというユースケースは多いでしょう。その場合はリスナー関数の引数で Logger を受け取ることができます。

const { App } = require("@slack/bolt");
const { LogLevel } = require("@slack/logger");

const logLevel = process.env.SLACK_LOG_LEVEL || LogLevel.INFO;

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

app.event("app_mention", async ({ event, logger }) => {
  logger.debug(`${event.user} mentioned me!`);
});

リクエスト情報を丸ごとログ出力

Slack からペイロードを処理していて「あの項目はリクエストのどの階層にどんなキー名で存在しているのかな? :thinking: 」と少し困ったことがあるかもしれません。そもそも API ドキュメントがもっと網羅的になるべき・・ということは前提として、利用側で今すぐに簡単にできる工夫を一つご紹介します。

ローカルでの開発中限定で、以下のような Middleware を設定してみましょう。

if (process.env.SLACK_REQUEST_LOG_ENABLED === "1") {
  app.use(async (args) => {
    const copiedArgs = JSON.parse(JSON.stringify(args));
    copiedArgs.context.botToken = 'xoxb-***';
    if (copiedArgs.context.userToken) {
      copiedArgs.context.userToken = 'xoxp-***';
    }
    copiedArgs.client = {};
    copiedArgs.logger = {};
    args.logger.debug(
      "Dumping request data for debugging...\n\n" +
      JSON.stringify(copiedArgs, null, 2) +
      "\n"
    );
    const result = await args.next();
    args.logger.debug("next() call completed");
    return result;
  });
}

すると、以下のような出力を見ることができるようになります。これによって、リスナー関数で使える値の構造を簡単に把握することができますし、うまくルーティングされないときのデバッグも楽になるかと思います。

{
  "context": {
    "botToken": "xoxb-***",
    "botUserId": "U1234567",
    "botId": "B1234567"
  },
  "body": {
    "type": "block_actions",
    "team": {
      "id": "T1234567",
      "domain": "your-subdomain"
    },
    "user": {
      "id": "U1234567",
      "username": "seratch",
      "name": "seratch",
      "team_id": "T1234567"
    },
    "api_app_id": "A1234567",
    "token": "random",
    "container": {
      "type": "message",
      "message_ts": "12345.12345",
      "channel_id": "C1234567",
      "is_ephemeral": true
    },
    "trigger_id": "12345.12345.random",
    "channel": {
      "id": "C1234567",
      "name": "general"
    },
    "response_url": "https://hooks.slack.com/actions/T1234567/12345/random",
    "actions": [
      {
        "action_id": "action-id",
        "block_id": "block-id",
        "text": {
          "type": "plain_text",
          "text": "Button",
          "emoji": true
        },
        "value": "click_me_123",
        "type": "button",
        "action_ts": "12345.12345"
      }
    ]
  },
  "payload": {
    "action_id": "action-id",
    "block_id": "block-id",
    "text": {
      "type": "plain_text",
      "text": "Button",
      "emoji": true
    },
    "value": "click_me_123",
    "type": "button",
    "action_ts": "12345.12345"
  },
  "action": {
    "action_id": "action-id",
    "block_id": "block-id",
    "text": {
      "type": "plain_text",
      "text": "Button",
      "emoji": true
    },
    "value": "click_me_123",
    "type": "button",
    "action_ts": "12345.12345"
  },
  "client": {},
  "logger": {}
}

あくまでローカルでのデバッグのときのみ有効となるように switch できるフラグを上記のように環境変数などで渡すとよいでしょう。

FaaS で動かすなら processBeforeResponse: true を設定

AWS Lambda や Google Cloud Functions のような FaaS (Function as a Service) 環境で動かすなら v2.0 から導入された processBeforeResponse というフラグを true に設定してください。これを設定することでリスナー関数が 3 秒以内に処理を完了できる前提で FaaS 環境で正常に動作させることができるようになります。

より詳細な内容を知りたい方は、英語のみとなりますが、私が書いている GitHub issue とそこからリンクされている議論や pull request を参考にしてください。 https://github.com/slackapi/bolt/issues/361

const { App } = require("@slack/bolt");

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

app.command("/hello", async ({ ack, respond }) => {
  await respond("What's up?");
  await ack(); // processBeforeResponse: true の場合、これより前の await つきの処理も全てランタイムが破棄される前に実行されることが保証される
});

サンプル・テンプレート

いくつか参考になりそうなサンプルやテンプレートを紹介しておきます。

以下はプロジェクトの雛形です。

コミュニティ

Slack Platform Community という Slack のプラットフォーム機能を活用する人たちが集まるコミュニティがあります。slackcommunity.com に参加すると、新しいイベントが公開されたときに通知が来ます。

日本では meetup.com より connpass が広く使われているので、日本のチャプターでは connpass でもミラーのイベント情報が流れるようになっています。

そして、普段のオンラインのコミュニケーションには Slack ワークスペースがあります。こちらから参加することができます。今年始まったばかりですが、すでに活気のあるコミュニティになってきています!

年末年始は Bolt 入門で決まり(?)

以上、Bolt に関するいくつかの tips を紹介しました。

「年末年始に新しいことを始めたい」という人は、Bolt を使った Slack アプリ開発に入門してみてはどうでしょうか?

日々をちょっと楽しくしたり、作業の効率を良くするためのアプリをつくってみてください!

37
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
seratch
Slack で Bolt などの Slack 公式 SDK の開発、日本での API に関する啓蒙活動を担当しています。
slack
Slack は必要なメンバーから情報、ツールまで一元化するメッセージプラットフォームと、そのアプリ開発用プラットフォームを提供しています。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
37
Help us understand the problem. What is going on with this article?