Tech 4 Mine :-) 忘れぬ先のテックメモ

ゆるエンジニアな私が、多少なり役に立ったひらめきやらTipsを忘れる前に書いていくブログです。

RocketChat+Hubotにどうしてもプログレスバーが欲しかった

.1. 実装環境

  1. 利用バージョン一覧

・Hubot			    v3.3.2
・RocketChat		v1.3.1
・node.js			v10.16.2
・request			v2.88.0
・request-promise	v4.2.4
・node-cron	    	v2.0.3
・uuid		    	v3.3.2


実装はVisual Studio CodeJavaScriptを利用しています。 動確はWindows10上でDocker+VSCode+Chromeで行っています。

.2. 概要

何かしらの方法で通知したい残タスク数を取得して、Botプログレスバー代わりのメッセージを表示・更新してもらう処理を作りました。

robot.adapter.callMethod()でも以前はできたようですが、今回利用のバージョンでは無理だったようなのでRocketChatのAPIを利用する形にしました。

.3. Hubotでトリガを受け取る

yoでBotのテンプレート生成などは詳しく説明してくださっている方もいるので割愛します。

まずはHubot側で処理開始のトリガを受け取ります。 テストも兼ねて「roading」のあとに入力した数値分、タスクを処理したことにしてみます。

const uuidv4 = require('uuid/v4');

module.exports = (robot) => {
    // 後々使い回す値などの定義
    let countNow, _memid, _rid, _token, _userId;
    const bar = ':man_running:';
    const barEnd = ':flying_saucer:';
    const update_uri = 'http://localhost:3000/api/v1/chat.update'
    const auth_uri = 'http://localhost:3000/api/v1/login'
    let _msgid;

    // roadingのあとに数字のみを入力した場合に処理する
    robot.hear(/^roading [0-9]+$/i, async (res) =>{

        // 入力から数値のみを取得・・・初期の表示数を設定する
        let count = res.message.text.replace('roading ','');
        count = Number(count);

         // カウント数共有する(テスト用)
         countNow = count;

        // 初回出力用のメッセージを生成
        let message = " ";
        // 出力が必要な分だけ連結
        for(var i = 0; i < count; i++){
            message = bar + message;
         }

        // ユニークIDをつける、ここではuuidを利用
        _msgid = uuidv4();
        _rid = res.message.user.roomID;

				// 送信用のパラメータをセット
        let sendMsg = {
             _id: _msgid,
             rid: _rid,
             msg: message
         }
         // オブジェクト化してメッセージIDを指定する(uuid v4つかう)
         res.send(sendMsg);

				// 初回認証用のメッセージを送る
        await rcAuth();
				// 定義したTaskを開始
        task.start();

    }
}

初回認証、Task定義については後ほど。 実際に利用する際は、countの値を別途取得した残タスク数に置き換えます。 _idをメッセージ更新用の処理と共有する必要があります。

.4. REST API用のクラスを作る

request-promise を利用してRocket ChatのAPIをコールするためのクラスを作ります。 他の処理や、短時間でのループ処理中の同時呼び出しを考慮してクラスにしています。

const request = require('request-promise');
module.exports = class {

    constructor(){}

		// 今回はPOSTが送れればいいのでpostを定義
		// optionsを渡したら受け取ったbodyか、nullを返す
    post(options) {
        return request.post(options)
        .then(body => {
					if(!body) return null;
					else if(typeof body === 'string') return JSON.parse(body);
          else return body;

				}).catch(err => {
            console.log(__filename + ':\n ' + err);
        });
    }
}

.5. RocketChatのREST APIへログイン

RocketChatのAPIを利用するためには、認証トークンを取得します。 設定詳細は以下のページを参照。 https://rocket.chat/docs/developer-guides/rest-api/authentication/login

まずはREST用のOptionを作ります。 uriは別途定義済、user/passwordはRocketChatに登録したBotユーザーのものをセット。

  let options = {
    uri: auth_uri,
    headers: { 'Content-type': 'application/json'},
    json: {
      user: 'hubot',
      password: 'botpassword'
    }
  }

auth_uriにはRocketChatのURL + /api/v1/login を指定します。

上記に定義したoptionsを先に定義したRestクラスに渡して認証トークンを取得します。

  let rest = new PostRequest();

  // _token, _userId はチャットメッセージ更新処理時に利用できるレベルに定義する。
  await rest.post(options)
  .then(body => {
    _token = body.data.authToken;
    _userId = body.data.userId;
  }).catch(err => {
    console.log(__filename + ':\n ' + err);
  });

取得したトークンとユーザIDを以降の処理で利用します。 値渡しか、変数を共有できるレベルにセットします。

.6. 残タスクや進捗率を表示したチャットを表示・更新する

node-cronを利用して表示内容を反復更新するタスクを定義します。 実行間隔は別途設定ファイルを作ったほうがいいかも。

  const cron = require('node-cron');

  // 2秒ごとにroopFuncを実行する。
  let task = cron.schedule('*/2 * * * * *', () => {
      roopFunc();
  },{
  // falseにすることで任意のタイミングで起動する。
      scheduled: false
  });

タスク内で処理するFuncを定義します。 別ファイルに書き出したりする場合はカウント数を引数に渡せるように変更します。 ついでに横着してメッセージの更新も行っちゃいます。

  const roopFunc = async () => {

    // countNowを現在の残りタスク数などに置き換えて使う。
    console.log('実行回数残り : ' + countNow);
      let newMsg = barEnd;
      for(var i = 0; i < countNow; i++){
          newMsg = bar + ' ' + newMsg;
      }
      if(!(countNow + 1)) {
          newMsg = ':rocket: finish!';
          task.stop();
      }

      // 本来はプログレス表示したいシステムの残タスク数などを取得して更新する。
      // ここではテストのために減算して次回表示数を減らしていく。
      countNow--;

      // ヘッダに先に取得しているトークンとユーザIDをセットする。
      let options = {
          uri: update_uri,
          headers: {
            'X-Auth-Token': _token,
            'X-User-Id': _userId,
            'Content-type': 'application/json'
          },
          json: {
            // 初回のres.send時に設定したメッセージIDを諸々と合わせてセット。
            roomId: _rid,
            msgId: _memid,
            text: newMsg
          }
      }
      let rest = new PostRequest();
      await rest.post(options);
  }

countNowは初回呼び出し時にcountを代入しています。 update_uriにはRocketChatのURL + /api/v1/chat.update を指定します。

.7. 動いた感じ。

f:id:teemine:20190916220735j:plain

f:id:teemine:20190916220730j:plain

 

だんだんとランナーがUFOに吸い込まれていって、完了したらRocketが飛ぶようになっています。

非常にざっくりですが、WSL2のリリースとラズパイ4の技適通過をしながらお届けしました。 JSもHubotも1ヶ月程度しか触ってないので、もっとこう書くんだYO!とかの指摘も頂けると嬉しいです!

.8. お決まりのアレ

バージョンの組み合わせや実装環境で想定通り動かないこともあるかと思いますが、動作等の保証は致しかねますのでそのあたりは自己責任において引用等お願い致します。