LINEBOTからGoogleカレンダーを取得する

# 完成物
[前回の投稿](https://qiita.com/LunaChevalier/items/06706546762dfde04b44#%E8%A6%81%E4%BB%B6%E5%AE%9A%E7%BE%A9)の
> 「スケジュール教えて!」って言ったら，今日から1週間のイベントを教える

が完成しました．
厳密には「スケジュール」だけで教えてくれるようになりましたが，細かいことは気にしない!
スケジュールがある時の動作が↓

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ついに、曜ちゃんがスケジュールを教えてくれる様になりました <a href="https://t.co/bxS3ZUPl0w">pic.twitter.com/bxS3ZUPl0w</a></p>&mdash; ルナ・チェバリー (@Luna_Chevalier) <a href="https://twitter.com/Luna_Chevalier/status/953854171546988544?ref_src=twsrc%5Etfw">2018年1月18日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ない時の動作が↓です．

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">予定がない時もきっちり教えてくれます <a href="https://t.co/9wFgNrabbw">pic.twitter.com/9wFgNrabbw</a></p>&mdash; ルナ・チェバリー (@Luna_Chevalier) <a href="https://twitter.com/Luna_Chevalier/status/953854534643736576?ref_src=twsrc%5Etfw">2018年1月18日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

ちなみに，「スケジュール」ではなくても，文字ならランダムで何か返事してくれます．

# 開発環境
* [Ruby](https://www.ruby-lang.org/ja/)(2.4.1)
* [google-api-client](https://github.com/google/google-api-ruby-client)(0.13.6)
* [line-bot-api](https://developers.line.me/ja/)(1.2.0)
* [Sinatra](http://sinatrarb.com/intro-ja.html)(2.0.0)
* [Apache](https://httpd.apache.org/)(2.4.6)
* passenger(5.1.8)

# 主なお話
Google周りの設定が中心です．

# 現在の実装完了機能
* 文字を送信したら，ランダムで何か返事してくれる
* 「スケジュール」と送信したら，今日から1週間のスケジュールを教えてくれる←new!
  * ベースはGoogleカレンダー

# 今回の作業
## Googleカレンダーからスケジュール取得
今回はプライベートのカレンダーから取得するので，まずは認証してそれに成功してからスケジュールを取得しに行きます．

### サービスアカウントの作成
[Google APIs](https://console.developers.google.com/apis)で，下準備を行います．
右側の認証情報に移動して，「認証情報を作成」ボタンから作成します．
投稿時点では

* APIキー
* OAuthクライアントID
* サービスアカウントキー

の3種類がありますが，今回はサービスアカウントキーを使って認証情報を作成しました．
サービスアカウントの「新しいサービスアカウント」を選択して，「サービスアカウント名」と「役割」を設定しました．
**「サービスアカウント名」** はわかりやすければなんでも大丈夫です．
**「役割」** は初回のサービスアカウントキーを作成したので，「Project」の「オーナー」にしました．
**「サービスアカウントID」** は勝手に入力されるので，今は特に何もしなくても大丈夫です．
**「キーのタイプ」** は自分が扱い慣れているJSONにして，いざ作成!
無事に成功したら，認証情報に移動すると「サービスアカウントキー」の欄に作成したキーが表示されています．
このファイルを使って取得するので，ファイルはダウンロードしておきましょう．

### サービスアカウントとGoogleカレンダーの連携
さっき作ったサービスアカウントはその名の通り「アカウント」でしか無いので，まだカレンダーの情報を取得できません．
「サービスアカウントID」を取得したい非公開のGoogleカレンダーに連携させる必要があります．
「サービスアカウントの管理」に移動すると，さっき作成した「サービスアカウントID」があるので，これをメモ帳か何かにコピペしておきます．
次にブラウザからGoogleカレンダーを開いて，設定画面を開きます．
左側にある「マイカレンダーの設定」の自分のユーザー名を開くと，「特定のユーザーとの共有」という欄があるので，さっきコピペしたIDを追加します．
権限は「閲覧権限(すべての予定の詳細)」に設定しました．
これで，取得できるようになりました．

### 実際に取得する
ここまでくれば，ダウンロードしたファイルを使って実際に取得する処理を書けば終わりです．

```rb:calendar.rb
require 'google/apis/calendar_v3'
require 'googleauth'

require 'fileutils'
require 'json'

APPLICATION_NAME = 'GOOGLE_CALENDAR_EXAMPLE'
MY_CALENDAR_ID = 'さっき連携したカレンダーID'
CLIENT_SECRET_PATH = 'ダウンロードしたJSONファイルのパス'

class Calendar
  def initialize
    @service = Google::Apis::CalendarV3::CalendarService.new
    @service.client_options.application_name = APPLICATION_NAME
    @service.authorization = authorize
    @calendar_id = MY_CALENDAR_ID
  end

  def authorize
    authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
      json_key_io: File.open(CLIENT_SECRET_PATH),
      scope: Google::Apis::CalendarV3::AUTH_CALENDAR)
    authorizer.fetch_access_token!
    authorizer
  end

  # 実際にスケジュールを取得するメソッド
  # 今から丁度1週間後までのスケジュールを取ってくれる
  def get_schedule(time_min = Time.now.iso8601, time_max = (Time.now + 24*60*60*7*0).iso8601, max_results = 256)
    response = @service.list_events(@calendar_id,
                                    max_results: max_results,
                                    single_events: true,
                                    order_by: 'startTime',
                                    time_min: Time.now.iso8601)
    response.items
  end
end
```

↑がスケジュールを取得するソースです．
`Calendar`のインスタンスを生成して`get_schedule`を呼び出せば，スケジュールを取得できます．

## LINEBOTに処理を追加する
↑の動作確認が完了したら，後はLINEBOTにこの処理を組み込むだけ!
今回は「スケジュール」と言ったときに教える様にするため，その時に取得するようにしました．

# 今後の予定
当日にイベントがあったら，朝にそれを教える機能を実装しようと思います．
GASを利用して，設定したURLにPOSTすれば実装できるかなと思ってますが・・・
しかし，ルート設定されたファイルとクラスが混在して混沌としているので，先にリファクタリングですね・・・