Botkit middleware を作ってみる作業メモ

やること

  1. modern JavaScript なプロジェクトをセットアップしてみる
  2. Botkitアプリケーションに対して外から入れるような形のモジュールを作ってみる
    • Botkit middleware
  3. ローカルでのyarnを介したモジュール開発をしてみる
  4. npmに登録してみる
  5. サンプルアプリケーションを作成してみる
  6. Deploy to Heroku で簡単に触って見られる、という感じにしてみる

きっかけ

そこそこお久しぶりな方から、Slackにハッシュタグ的な「ゆるく情報をまとめる方法」が欲しかった話 - コネヒト開発者ブログをみて、「今まさにこういうの欲しいのですがーーーーー」的なご連絡をいただきまして、自分が作ったり発信したりした何かに興味を持ってもらって & ましてや、ソレをきっかけにリアルグラフ内からのご連絡を頂戴するなど技術者冥利に尽きるなガッハッハ!という事で、テンションが上がってしまいましたとさ。

ただ、実装詳細についてはポイントポイントでスニペットをつけてはいるものの、 普段からslack apiやbotに触れている〜〜みたいな人でもない限り、これを「ゼロから自分でコードに落とす」みたいなのは確かに面倒臭そう。
自分のアイディアなので使ってもらえたら嬉しいな〜とは素直に思いつつ、どうやって手にとってもらうかな〜が悩まし、いっそコード晒すか!!ってなりました*1

序章: やり方を考える

〜botkitに組み込むポータビリティの高いパッケージ管理とかあるの?〜

せっかくなら、メンタビリティを損なわないような形をとりたい。疎結合、外から注入できる必要がある。プラグイン的な機構が必要。 f:id:o0h:20181013185417p:plain

で・・ botkit.ai

あるじゃん!

botkitのサイトにもリンクされていたGitHub - shishirsharma/botkit-mixpanel: Botkit plugin for Mixpanel analytics https://botkit.aiとかは、題材的にも自分の身近感あるし参考になりそう。

ああ、なんかここまで調べてるうちにコレは自分にとってすごい楽しいオモチャなのでは・・って気持ちが湧いてきた。compsor plugin, redashのquery runnerに並ぶような。。

第1章: まずはlocalでbotkitを立てましょう

ミドルウェアの概念を理解して実装をしたいのだけど、とにかくまずは開発環境の構築だ。

  • yarn add botkit
  • eslint周り + prettier をyarn add
    • 関連設定ファイルは、会社のJS強い人が書いているやつをそのまま参考にする
    • ぷりてぃあ〜使える安心感たるやすっごい・・・

ビルドツール

うげ〜〜JSわからんのだよ〜〜〜っていう苦手意識が強いので、「パッと入れてガッと動く」と噂に聞くparcelを使えばいいのかな?と思って試してみた。

・・・のだけど、会社のJSに強い人に聞いてみたら、「今回の目的からすればrollupでないかい!」と。マジか。設定ファイル書きたくないんだけど。調べてみる。

f:id:o0h:20181013202636p:plain

なるほどなるほど。ググって出てきて参考にした記事。

最小構成で始めるRollup.js - コンパイラかく語りき

神がいる、ありがとうございます。

rollup.js ふむふむ。

あと、ついでに「本当に必要最低限な.babelrcについて教えてください」と前出の人に問うた結果、 @babel/preset-env はあったほうが良さそうだったので入れる。
それに加え、「モジュールの読み込みがうまくいかない・・・」ってハマった。

  • npmでインストールしたもの = node_modules以下を使えるように node-resolve が必要
  • 自分で書いたものなどを使えるように commonjs が必要

ココらへんの知識の無さを痛感するなぁ。。。参考にした記事。

最終的にこんな感じ

  • yarn add --dev rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-babel @babel/core @babel/preset-env

.babelrc

{
  "presets": [
    ["@babel/preset-env", {
      "targets": { "node": "current" },
      "useBuiltIns": "usage"
    }]
  ]
}

rollup.config.js

import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve'
import babel from 'rollup-plugin-babel'

export default {
  input: 'src/index.js',
  output: {
    file: 'dist/index.js',
    format: 'cjs',
  },
  plugins: [resolve(), commonjs(), babel()],
}

これでいいのかな? ついでに、package.jsonにscriptsを追記しておく

  "scripts": {
    "watch": "rollup -cw"
  },
  • yarn run watch
  • node bot.js

f:id:o0h:20181013222223p:plain

無事に動いた。

ちなみに、動かしたスクリプトの内容は以下。

第2章: npmモジュールを作成してみる

「ミドルウェアの概念を掴んだり実際に機能実装をやろうかな?」と思ったのだけど、強い人の声。

f:id:o0h:20181013222717p:plain

なるほど・・?そういうのがあるなら、手戻りなく「最初からbotkitのappからスタンドアロンにして作ってしまおう」ができるかもしれない。
概念がよくわかっていないので調べる。

yarnpkg.com

お、なるほど〜。
このあたり、PHP(composer)だとtypeをsrcにしてレポジトリは直接ローカルを指定〜とかやるけど、それよりもシュッとしたやり方?それとも、無用な抽象化でまどろっこしい?どっちなんだろ〜、という感想。いずれにせよ思想の違いを感じて面白いな!!

ということで、
現状のディレクトリ構成がこう。

- hoghoge-ws
    - botkit-hogehoge
    - app

いつもそうなんだけど、プラグインとかモジュールとか作るときは「ws: workspace」ディレクトリを掘って、その配下に実際に公開されるgitレポと合わせた名前でモジュール本体のディレクトリ & 被検する「app」ディレクトリをおく、というやり方をしてる。

ということで、とても簡単にできそうなので、まずは「パッケージ化するためのもろもろ」からですな〜

npmモジュールとして登録するための諸々を行う

qiitaの記事で流れを掴みつつ、 qiita.com

「実際に公開されているミドルウェア」で構成だったり記述内容を学ぶ感じで行けるでしょうか。

www.npmjs.com

最低限の構成と思われるものを整えた。

$ git diff --name-only HEAD~~

.babelrc
.eslintrc
package.json
rollup.config.js
yarn.lock

で、 yarn link ・・・

$ yarn link
yarn link v1.9.4
success Registered "botkit-hashtag".
info You can now run `yarn link "botkit-hashtag"` in the projects where you want to use this package and it will be used instead.
✨  Done in 0.20s.

お〜!なるほどね。 これを食うようにしてみる。

$ cd ../app
 $ yarn link botkit-hashtag
yarn link v1.9.4
success Using linked package for "botkit-hashtag".
✨  Done in 0.15s.

$ ls -l node_modules | grep hashtag
lrwxr-xr-x    1 hkinjyo  staff     20 10 14 00:29 botkit-hashtag -> ../../botkit-hashtag

ほうほう。とりあえず、「簡単なオウム返しなミドルウェア」を作ってみよう。
botkit-mixpanelを見ると、

  1. module.exports = function(controller, options) {} のクロージャを作って、その中でcontrollerをゴニョゴニョしている
  2. readmeをみると require('botkit-mixpanel')(controller, {mixpanel_api_key: process.env.MIXPANEL_API_KEY, debug: true, always_update: true}); 的な感じで食わせている

なるほど〜。
これでやっと、「ESどうやって書くの・・・」な世界から抜け出してbotkitな世界に進める。

Middleware Endpoints。

botkit.ai

公式のスニペット

// this example does a simple string match instead of using regular expressions
function custom_hear_middleware(patterns, message) {

    for (var p = 0; p < patterns.length; p++) {
        if (patterns[p] == message.text) {
            return true;
        }
    }
    return false;
}


controller.hears(['hello'],'direct_message',custom_hear_middleware,function(bot, message) {

    bot.reply(message, 'I heard the EXACT string match for "hello"');

});

あ、ちょっとAPIの形が違うのね。って思ったけど、 controller には変わりないよね。一旦試してみよう、さっき雑にapp/src/index.jsに書いていたreplyをこっちに移してみる

// botkit-hashtag/src/index.js
module.exports = (controller, options) => {
  controller.hears('hello', ['direct_mention'], (bot, message) => {
    bot.reply(message, 'Hello my friend!');
  });
};

// app/src/index.js
const botkit = require('botkit')
const controller = botkit.slackbot({ debug: true })

require('botkit-hashtag')(controller, {})

controller
  .spawn({
    token: process.env.SLACK_TOKEN
  })
  .startRTM(function(err) {
    if (err) {
      throw new Error(err)
    }
  })

f:id:o0h:20181014005003p:plain

おお〜〜、ちゃんと動いた〜〜〜!

  • When hearing a message
  • When matching patterns with hears(), after the pattern has been matched but before the handler function is called

ってあるから、ミドルウェアに関しては特別なのかもしれない?


あとは、実装は愚直に。しかしprettier最高すぎるな・・
このエントリーは一旦ここまで!折角だし、もっとミドルウェアっぽい機能を作ってみたいな。楽しそう

*1:いっそコーヒーでも奢ってもらいながらコーディング&インストールでもペアプロでもするか・・?とかよぎったけど、口実つけてお喋りしたがるムーブに自分の中のおっさんを感じた