読者です 読者をやめる 読者になる 読者になる

non vorrei lavorare

ブログ名の通りです。javascript three.js mruby rust OCaml golang julialang blender

herokuでbotkitを動かしGoogle Calendar APIを使う

こんにちは、長男は片付けしろと言えば、片付けるものの、次男が さっぱり片づけをしません。しかも、長男が次男の分までおもちゃを片付けています。 そんな状況で手を焼いて、長男には自分の使いたいおもちゃだけ片付けさせ、 次男が片付けなかったおもちゃは翌日回収してしまってしまう作戦にでたところ、 若干次男片付けに参加するようになりました。kjunichiです。

前提

すでにHerokuでdockerでbotkitを動かしている

OAuth関連のパラメータの取得

Google Calendar APIのページで、サンプルを動かす。

サンプルを動かすため必要なjsonファイルの取得

サンプルの説明に従い、OAuth認証に必要なjsonファイルを取得し、これを

  • client_secret.json

として保存する。

サンプルを動かし、キーを設定する

サンプルを起動し、メッセージに従い、キーを取得し、サンプル入力して動かす。

この作業により

  • ~/.credentials/calendar-nodejs-quickstart.json

にファイルが出来る。

herokuに環境変数としてOAuth関連のパラメータを登録する

DockerfileのENVにし記述してもHerokuの実行環境ではこれらの環境変数は無効化されているので、 以下の様にherokuコマンドで環境変数を設定する必要がある模様。

JSONファイルを環境変数として登録

json2envという便利なnpmパッケージがあり、今回はこれを利用して

npm install -g json2env
heroku config:set CALENDAR_TOKEN=`json2env ~/.credentials/calendar-nodejs-quickstart.json` --app アプリ名
heroku config:set CALENDAR_SECRET=`json2env ./client_secret.json` --app アプリ名
ファイル名 環境変数
client_secret.json CALENDAR_SECRET
calendar-nodejs-quickstart.json CALENDAR_TOKEN

コード

  • サンプルのファイルベースで行っていたOAuth認証に必要なパラメータを環境変数で受けとるように変更
  • ストア処理等不要な処理も削除
  • 結果を取得後コールバックするように変更
var google = require('googleapis');
var googleAuth = require('google-auth-library');

var buf = [];
getOurEvents = (cb) => {
  buf = [];

  // Load client secrets from a local file.
  const content = process.env['CALENDAR_SECRET'];
  if (content) {
    console.log("CALENDAR_SECRET.");
    authorize(JSON.parse(content), cb, listAllEvents);
  }
};

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 *
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with getting calendar data.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, cb, callback) {
  var clientSecret = credentials.installed.client_secret;
  var clientId = credentials.installed.client_id;
  var redirectUrl = credentials.installed.redirect_uris[0];
  var auth = new googleAuth();
  var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);

  // Check if we have previously stored a token.
  const token = process.env['CALENDAR_TOKEN'];
  if (token) {
    console.log("calendar_token from ENV.");
    oauth2Client.credentials = JSON.parse(token);
    callback(oauth2Client,cb);
  }
}

function listAllEvents(auth,cb) {
  var calendar = google.calendar('v3');
  calendar.calendarList.list({
    auth: auth
  }, (err, res) => {
    if (err) {
      console.log('The API returned an error: ' + err);
      return;
    }
    //console.log(res.items);
    const numCals = res.items.length;
    var doneCals = 0;
    for (i = 0; i < res.items.length; i++) {
      var calId = res.items[i].id;
      var tday = new Date();
      var targetSec = tday.getTime() + (2 * 86400000);
      tday.setTime(targetSec);
      //console.log("get "+calId);
      //console.log(tday.toISOString());

      calendar.events.list({
        auth: auth,
        calendarId: calId,
        timeMin: (new Date()).toISOString(),
        timeMax: tday.toISOString(),
        maxResults: 10,
        singleEvents: true,
        orderBy: 'startTime'
      }, (err2, res2) => {
        if (err2) {
          console.log('The API returned an error: ' + err2);
          return;
        }
        //console.log(res2);
        var events = res2.items;
        if (!events || events.length == 0) {
          //console.log('No upcoming events found. ');
          //console.log(res2);
        } else {
          //console.log('Upcoming events:');
          for (var i = 0; i < events.length; i++) {
            var event = events[i];
            var start = event.start.dateTime || event.start.date;
            buf.push(start + " " + event.summary);
            //console.log('%s - %s', start, event.summary);
          }
        }
        doneCals++;
        if (doneCals + 1 >= numCals) {
          return cb(buf);
        }
      });
    }
  });
}

exports.getOurEvents = getOurEvents;

これをlibcalendar.jsとして保存。

botkitに組み込む

以下を既存のbotkitでつくったボットのコードに追加。

const libcalendar = require('./libcalendar.js');

// snip

controller.hears(['予定'],'ambient',function(bot,message) {
  libcalendar.getOurEvents((b)=>{
    var a = "";
    for(var i = 0; i < b.length; i++) {
      a = a + b[i]+"\n";
    }
    bot.reply(message,a);
  });
});

これで、ボットがいるチャンネルで「予定」と言えば、Googleカレンダーに登録されている複数のカレンダーから二日後の予定まで教えてくれる。

ローカルで動かすための設定

herokuで動かすには前述の設定で良いが、ローカルで動かすためには、 以下のようにdocker-compose.ymlを編集して、環境変数を受け取れるようにする必要がある。

web:
  build: .
  command: 'bash -c ''node web.js'''
  working_dir: /app/user
  environment:
    TZ: 'Asia/Tokyo'
    PORT: 8080
    CALENDAR_TOKEN: $CALENDAR_TOKEN
    CALENDAR_SECRET: $CALENDAR_SECRET
    SLACK_TOKEN: $SLACK_TOKEN
  ports:
    - '8080:8080'

ローカルのdockerで動かすには

SLACK_TOKEN=hogehoge CALENDAR_TOKEN=`json2env ~/.credentials/calendar-nodejs-quickstart.json` CALENDAR_SECRET=`json2env client_secret.json` docker-compose up web

関連記事