Hubotで英語学習bot作った

f:id:amagitakayosi:20181127144718g:plain

どうも天城です。最近は戸籍について悩んでます。

きょうは英単語を覚えるためのSlack botを作ったので紹介します。 Slackの #dictionary に英単語とその意味をメモしておくと、トランプ大統領が定期的にクイズを出題してくれる。

もくじ

前回までのあらすじ

blog.gmork.in

TOEICではいい点数とれたけど、まだまだボキャブラリーが無いのでもっと勉強したい。

我が家では以前からSlackを利用しており、Hubotでを洗濯やゴミ出しのリマインダーを作っていた。 このSlackに #dictionary に単語をメモしていたのだが、このbotを利用して単語クイズができればいいな〜ということを思いついた。

単語学習サービスは既にAnkiなどがあるが、様々なデバイスでデータを共有するのが大変そうなのと、新たにツールを導入するのは面倒なので、既にあるHubotを利用することにした。

仕組み

HerokuでDBといえばPostgresだけど、無料だと10000行までという制約がある。 実際困る事はないかもしれないが、なんとなく制限のことを考えるのがめんどい。 HerokuのAdd-onは他もだいたい無料だと制限があるので見送り。

というわけで、今回はGoogle Spreadsheetを使うことにした。 Google Spreadsheetなら200万セルまでらしいので、1単語に5セル使っても400000単語まで記録できる。

API周りの処理は、node-google-spreadsheetを利用した。 Google謹製のgoogleapisというのもあるけど、前者のほうが認証周りがシンプルそうだった。

github.com

事前にSpreadsheetで以下のようなシートを作成しておき、botが記録/閲覧できるようにしておく。

f:id:amagitakayosi:20181127153932p:plain

サービスアカウント作成

botがSpreadsheetにアクセスするためには、Googleダッシュボード上でサービスアカウントを作成する必要がある。 詳しい手順はこの辺を参照。

https://github.com/theoephraim/node-google-spreadsheet#service-account-recommended-method

サービスアカウントを作成できたら認証用データの入ったJSONがダウンロードされる。 JSON内のメールアドレス、プライベートキー等は、Herokuの設定画面などから環境変数に入れておくとよい。

f:id:amagitakayosi:20181127154226p:plain

記録部分

特定の形式のメッセージが来たらスプレッドシートに記録する。 今回は以下の形式を採用。

*america* 
アメリカ

Hubotレポジトリの /scripts に以下のスクリプトを保存すると良い。 doc.addRow() を呼ぶだけで行を追加できて便利。

const GoogleSpreadsheet = require('google-spreadsheet');
const p = require('pify'); // promisify

// 認証周りのデータ
const doc = new GoogleSpreadsheet(process.env.SPREADSHEET_KEY);
const creds = { 
  client_email: process.env.SPREADSHEET_CLIENT_EMAIL,
  private_key: process.env.SPREADSHEET_PRIVATE_KEY,
};

// メッセージにマッチする正規表現
const re = /\*?([^\n\*]+)\*?\n(.+)/;

module.exports = (robot) => {
  robot.hear(re, async (res) => {
    let m = res.message.rawText.match(re);
    if (m) {
      // メッセージから英単語と意味を抽出
      const english = m[1];
      const japanese = m[2];

      // 認証
      await p(doc.useServiceAccountAuth)(creds); 

      // スプレッドシートに保存      
      p(doc.addRow)(1, { english, japanese })
        .then(() => res.send('OK, saved.'))
        .catch((e) => res.send('ERROR! Try again.'));
    }
  });
};

クイズ部分

こういう感じでSpreadsheetから単語を取得して

const GoogleSpreadsheet = require('google-spreadsheet');
const p = require('pify'); // promisify

const doc = new GoogleSpreadsheet(process.env.SPREADSHEET_KEY);
const creds = { 
  client_email: process.env.SPREADSHEET_CLIENT_EMAIL,
  private_key: process.env.SPREADSHEET_PRIVATE_KEY,
};

// 認証
await p(doc.useServiceAccountAuth)(creds); 

// 行を取得
const info = await p(doc.getInfo)();
const rows = p(info.worksheets[0].getRows)({}));

// セルのデータを取得
const words = rows.map(r => ({
  english: r.english,
  japanese: r.japanese,
}));

こういうのでSlackに発言すればよい。 send.trump はメッセージ送信部分のラッパー。

module.exports = async (robot, room) => {
  send.trump(robot, room, '@channel 今から皆さんに、英語に関するクイズを出すピィ〜♪\n');

  // 単語を取得
  let rows = await sheet.getRows();
  rows = shuffle(rows);

  for (let i = 0; i < limit; i++) {
    const r = rows[i];

    send.trump(robot, room, `*${r.english}* は何という意味でしょう?`)
    await sleep(5000);
    send.trump(robot, room, `正解は \`${r.japanese}\` でした!!\n.\n`);
    await sleep(5000);
  }

  await sleep(1000);
  send.trump(robot, room, 'お疲れ様!!今日も一日頑張ろうヾ(๑╹◡╹)ノ"');
};

実際には、同じ単語は3回までしかクイズに出ないようにしたり、一日の出題数を制限したりしてる。 大量にあると疲れてしまうので……


今回のbotは単語帳サービスみたいなものなので、英語に限らず何でも記録出来ます。 ことわざとか技術用語の学習に使ってもいいかも。

f:id:amagitakayosi:20181127153707p:plain