LoginSignup
17

More than 5 years have passed since last update.

Node.jsでgoogleカレンダーから今日の予定を取得

Last updated at Posted at 2018-10-12

イントロダクション

nodejsでgoogle calendar APIを使ってイベント取得するまでの流れを解説します。
なお取得するカレンダーはマイカレンダーだけでなく、閲覧権限のある他のカレンダーの情報も同時に取得します。

環境準備

大まかな流れはほぼ公式ドキュメント通りです。
公式ドキュメント

まずカレンダーAPIの有効化と、必要なキーを発行します。
※公式ドキュメント内の[ENABLE THE GOOGLE CALENDAR API]のボタンから簡単に発行できます。
credentials.jsonのファイルが生成されますので、実行ファイルと同じパスに保存しておきます。

次にgoogleカレンダーへ接続するためのクライアント用モジュールを追加します。

# npm install googleapis@27 --save

準備の手続きは以上になります。あとはソースを生成して実行するだけです。

カレンダーデータの取得

(※以下すべてのソースはwebpackを使ってビルドしています。直接nodejsで実行するソースの場合はimport記述はエラーになるためrequireに置き換えた記述方法にしてください。)

/googleCal.js
import fs from 'fs'
import readline from 'readline'
import {google} from 'googleapis'
import moment from 'moment'

export default class GoogleCal{
  constructor(config){
    this.config = Object.assign({},this.defaults(),config);
  }
  /**
   * 初期値
   */
  defaults(){
    return {
      scopes : ['https://www.googleapis.com/auth/calendar.readonly'],
      tokenPath : '/app/script/token.json',

    }
  }

  /**
   * google Calendar APIへの接続と認証
   */
  connect(){
    return new Promise((resolve,reject) => {
      fs.readFile('credentials.json', (err, content) => {
        if (err){
          console.log('Error loading client secret file:', err);
          reject();
        }
        this.authorize(JSON.parse(content))
        .then(auth => {
          this.config.auth = auth;
          resolve();
        });
      });
    });
  }

  /**
   * OAuth認証処理
   * given callback function.
   * @param {Object} credentials 認証データ
   * @return (promise) object oAuth2Client.
   */
  authorize(credentials) {
    const {client_secret, client_id, redirect_uris} = credentials.installed;
    const oAuth2Client = new google.auth.OAuth2(
        client_id, client_secret, redirect_uris[0]);

    return new Promise((resolve,reject) => {
      // 保存してあるトークン情報を読み取り、なければトークン生成
      fs.readFile(this.config.tokenPath, (err, token) => {
        if (err) {
          this.getAccessToken(oAuth2Client)
          .then(() => {
            resolve(oAuth2Client);
          })
        } else {
          // アクセストークンは1時間ほどしか期限がないため、毎回リフレッシュトークンから新たなトークンを生成する
          oAuth2Client.credentials = JSON.parse(token);
          oAuth2Client.refreshAccessToken((err, token) => {
            if (err) {
              console.log(err);
              reject();
            }
            oAuth2Client.setCredentials(token);
            fs.writeFile(this.config.tokenPath, JSON.stringify(token), (err) => {
              if (err){
                console.error(err);
                reject();
              }
              console.log('Token stored to', this.config.tokenPath);
            });
          });
          resolve(oAuth2Client);
        }
      });
    });
  }

  /**
   * OAuthのアクセストークンを取得
   * @param oAuth2Client {object} google.auth.OAuth2の戻り値
   * @return (promise) object 有効なtokenを含めたgoogle.auth.OAuth2
   */
  getAccessToken(oAuth2Client) {
    const authUrl = oAuth2Client.generateAuthUrl({
      access_type: 'offline',
      scope: this.config.scopes,
    });
    console.log('右記のURLをブラウザで開いてください:', authUrl);
    const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    return new Promise((resolve,reject) => {
      rl.question('表示されたコードを貼り付けてください: ', (code) => {
        rl.close();
        oAuth2Client.getToken(code, (err, token) => {
          if (err) return console.error('トークンを発行できませんでした', err);
          oAuth2Client.setCredentials(token);
          // トークンデータをファイルに書き出し
          fs.writeFile(this.config.tokenPath, JSON.stringify(token), (err) => {
            if (err) {
              console.error(err);
              reject();
            }
            console.log('トークンを保存しました', this.config.tokenPath);
          });
          resolve(oAuth2Client);
        });
      });
    });
  }

  /**
   * イベントを取得
   * @param params {object} 取得時のパラメータ
   * @return (promise) Array イベントデータ一覧
   */
  listEvents(params) {
    if(params.calendarId.match(/#/)){
      params.calendarId = encodeURIComponent(params.calendarId);
    }
    return new Promise((resolve,reject) => {
      const calendar = google.calendar({version: 'v3', auth: this.config.auth});
      calendar.events.list(params, (err, res) => { // events.listでイベント一覧を取得
        if (err) {
          console.log('The API returned an error: ' + err);
          reject();
        }
        resolve(res.data.items);
      });
    });
  }

  /**
   * カレンダー一覧を取得
   * @return (promise) Array カレンダーデータ一覧
   */
  calendarLists(){
    return new Promise((resolve,reject) => {
      const calendar = google.calendar({version: 'v3', auth : this.config.auth});
      calendar.calendarList.list({},(err, res) => { // calendarList.listでカレンダーの一覧を取得
        resolve(res.data.items);
      })
    });
  }

  /*
   * 本日のイベントを取得
   * @param filterFunc {function} イベントを取得するカレンダーリストの条件フィルター
   */
  getTodayEvents(filterFunc = null){
    // カレンダー毎に順次処理するためのタスク
    const task = (calName,params,result) => {
      return new Promise((resolve, reject) => {
        this.listEvents(params).then(resp => {
          const data = Object.assign([],result);
          data.push({
            calName,
            items : resp
          })
          resolve(data)
        });
      });
    }

    return new Promise((resolve,reject) => {
      this.calendarLists() // カレンダー一覧を取得
      .then(calList => {
        if(filterFunc){
          calList = calList.filter(filterFunc); // イベント取得するカレンダーを絞り込み
        }
        calList.reduce((prev, current) => {
          return prev.then(resp => {
            const today = moment(moment().format("YYYY-MM-DD")).utcOffset("+09:00"); // 本日の00:00(日本時間)
            const todayMax = moment(moment().format("YYYY-MM-DD")).add(1,'days').add(-1,'minutes').utcOffset("+09:00"); // 本日の23:59(日本時間)
            const params = {
              calendarId: current.id,
              timeMin: today.format(), // 本日の0:00
              timeMax: todayMax().format(), // 本日の23:59
              singleEvents: true,
              orderBy: 'startTime',
            }
            const summary = current.summaryOverride || current.summary; // カレンダー名
            return task(summary,params,resp)
          });
        }, Promise.resolve([]))
        .then(resp => {
          resolve(resp);
        })
        .catch(()=>{
          console.log("error!")
        })
      })
    });
  }
}
/app.js
import GoogleCal from './googleCal'

const googleCal = new GoogleCal;
googleCal.connect()
.then(() => {
  return googleCal.getTodayEvents(_val => !_val.id.match(/#/)); // 誕生日や祝日のカレンダーは取得対象から除外
})
.then(val => {
  console.log(val);

});

実行

# node app.js

初回はtokenを持っていないため以下のような表示が出ますので、指示通りブラウザで指定のURLを開き、アカウント認証を行います。
アカウント認証が完了するとコードが表示されますので、それをコピペしてターミナルに入力します。

右記のURLをブラウザで開いてください:  https://accounts.google.com/o/oauth2/auth?access_type=offline&scope=****
表示されたコードを貼り付けてください:

認証に成功すると、カレンダーとそのタスクが表示されますので(/app.jsの最後のconsole.log(val)の部分)
あとはうまく加工して表示してください。
ちなみにデータの形式はこんな感じです。

response.json
[
  {
    calName : xxx,
    items : [
      {
        kind: 'calendar#event',
         etag: '"1234567890"',
         id: '20frmklfmerkfrefmqw',
         status: 'confirmed',
         htmlLink: 'https://www.google.com/calendar/event?eid=xxxxxxxxxxxxxx',
         created: '2018-10-09T07:09:42.000Z',
         updated: '2018-10-09T07:09:42.778Z',
         summary: 'てすと',
      },
       (item分繰り返し)

    ]
  }
   (カレンダー分繰り返し)
]

所感

googleの公式ドキュメントが例文コード含め丁寧に解説されているため、わりと簡単に設置することができました。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17