Googleカレンダーからrubyで今年の祝日を取得する

# 前置き
自分の日々のスケジュールを楽に管理したいと思いました。スケジュールはGoogleカレンダーをメインに管理するとして、ある程度決まった行事をスクリプトからAPIで操作できれば楽になりそうです。そこで、第一の目標は使い慣れたrubyで平日と休日の判定をしたいと思った次第。

# 事前調査
先行事例は山ほどあるはずなので、とりあえず適当なキーワードで検索します。

[rubyにてgoogleカレンダーの情報を取得する](http://qiita.com/iron-breaker/items/2440c4ab41a482b1b096)

そのままの記事がありました。 Rubyから利用するには[google-api-ruby-client](https://github.com/google/google-api-ruby-client)を使うとのこと。このライブラリについて調べてみると[こんな](https://rcmdnk.com/blog/2016/01/17/blog-octopress-javascript-analytics/)記事が… 。参考文献はできるだけ新しい記事を選ぶのがよさそうです。Calendar apiについては次の記事が役に立ちそうです。

[Google Calendar API を Ruby から使う: 認証と基本的なAPI](https://mmyoji.github.io/2016/07/29/google-calendar-api-from-ruby-auth.html)
[Google Calendar API Rubyでイベントを登録する](http://mcommit.hatenadiary.com/entry/2016/04/18/183200)

参考資料はかなり集まってきましたが、 oauth認証については記事ごとにやり方が違う… 。焦げ臭い匂いが漂ってきますからoauthを中心に検索してしてみると[Google Auth Library for Ruby](https://github.com/google/google-auth-library-ruby)という認証ライブラリもあるようです。どっちを使えばいいのやら。

共通点は[google-api-ruby-client](https://github.com/google/google-api-ruby-client)を使うってことです。

# GoogleアカウントへのOAuth認証
何をおいてもGoogleにお願いして、クライアントアプリを登録しなければなりません。

登録については[こちら](http://qiita.com/meguroman/items/e161544acec565f00fa4)を参照していただくのがよろしいかと思います。手順としましては、 

0. [google apiコンソール](https://console.developers.google.com/?hl=JA&pli=1)でカレンダーapiを有効
1. 認証情報タブから認証情報を作成ボタンをクリック
2. OAuthクライアントIDを選択
3. アプリケーションの種別はその他(*installed*)を選択
0. 作成されたクライアントID(jsonファイル)をダウンロード

でございます。

ここからさらにトークンを取得する作業が必要なのですが、後回しにします。

# 動作環境の準備
先程の工程でダウンロードしたjsonファイルはこのスクリプトと同じディレクトリにファイル名client_id.jsonで配置してください。

```json:client_id.json
{
  "installed": {
    "client_id": "..."
  }
}
```

内容は見やすいようにに編集してあります

認証に関しては[google-auth-library-ruby](https://github.com/google/google-auth-library-ruby)を使います。APIの利用は[google-api-ruby-client](https://github.com/google/google-api-ruby-client)を使います。

```:Gemfile
source 'https://rubygems.org'
gem 'google-api-client'
gem 'googleauth'
#gem 'redis'
```

認証処理は[ドキュメントのサンプル](https://github.com/google/google-auth-library-ruby#example-command-line)にある通り、次のようの手順となります。ファイル名やメールアドレスは想像力で補ってください。

```rb:example.rb
require 'googleauth'
require 'googleauth/stores/file_token_store'

client_id = Google::Auth::ClientId.from_file("client_id.json")
token_store = Google::Auth::Stores::FileTokenStore.new(file: 'google_api.yaml')
scope = 'https://www.googleapis.com/auth/calendar'
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)

credentials = authorizer.get_credentials('your_api_id@google.com')
```

認証に成功すれば*credentials*に証明書が入ります。失敗した場合は*nil*が入ります。ここで手に入れた証明書を使ってapiを利用するイメージです。

*authorizer*を取得する際に、第三パラメータで与えているtoken_storeってなんだよって話ですが、これはgoogleからもらったトークンをファイルに保存したものです。このライブラリでは、保存先をローカルファイルとredisのいずれかで選べるようになっています。不特定多数が利用するアプリではユーザごとにトークンを保存しなければならないので、redisの利用が推奨されているようです。今回はコンソールアプリなので、ローカルに保存すればよいでしょう。

ところで、このスクリプトは初回実行時には、必ず失敗します。というのもトークンを入手するには、ブラウザから許可*(アプリケーションがAPIを利用する)*を与えなければなりません。以前の工程で後回しにした作業をこれから行います。

作業は簡単。ブラウザで特定のURLから許諾ページを開き、 APIの利用を許可した上で、返ってきたページからコードをゲットします。

この作業は手動でも実行できますが、トークンには利用期限があることですし、毎回手作業ではやってられません。そこで上記のコードに次のスクリプトを追加して、ある程度自動化します。

```rb:example.rb
  if credentials.nil?
    # トークンが設定されていない、または期限切れの場合
    url = authorizer.get_authorization_url(base_url: OOB_URI)
    puts 'ブラウザで次のURLを開いてAPIの利用を許可してください'
    puts url
    puts '応答ページに表示されるコードを入力してenterを押してください'
    puts 'code:'
    code = gets
    credentials = authorizer.get_and_store_credentials_from_code(user_id: user_id, code: code, base_url: OOB_URI)
  end
```

このスクリプトは認証がこけたとき、コンソールにURLを表示してコマンド待ちになります。コピペを駆使して、ページを開いて手に入れたcode(トークン)を入力してください。

トークンは、*get_and_store_credentials_from_code*を通じて、ファイルgoogle_api.yamlに保存されます。

# Rubyスクリプト
そんな感じで出来上がったのが以下のスクリプトです。

Windowsのコマンドプロンプトはコピペがめんどくさいので、URLをお節介にファイルurl.txtに出力する機能付き。

あと、コメントにもある通り、Google APIを利用するユーザのIDを環境変数*google_id*の設定してください。

```
C:\Sites\cal>set google_id=name@gmail.com
```

```ruby:calendar.rb
require 'google/apis/calendar_v3'
require 'googleauth'
require 'googleauth/stores/file_token_store'
  
  OOB_URI = "urn:ietf:wg:oauth:2.0:oob"
  dir_path = File.dirname(__FILE__)
  
  # Google APIを利用するユーザのID
  # ex) name@gmail.com
  user_id = ENV['google_id']
  # アプリケーション名
  app_name = 'Calendar world'
  # カレンダーのID
  # ここでは日本の祝日
  cal_id = 'ja.japanese#holiday@group.v.calendar.google.com'
  
  # トークンを保存するファイル
  store_file = dir_path + 'google_api.yaml'
  # Google API Console からダウンロードしたclient idファイル
  client_id = Google::Auth::ClientId.from_file("#{dir_path}/client_id.json")
  token_store = Google::Auth::Stores::FileTokenStore.new(file: store_file)
  scope = 'https://www.googleapis.com/auth/calendar'
  authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)
  
  credentials = authorizer.get_credentials(user_id)
  if credentials.nil?
    # トークンが設定されていない、または期限切れの場合
    url = authorizer.get_authorization_url(base_url: OOB_URI)
    File.write 'url.txt', url
    puts 'ブラウザで次のURLを開いてAPIの利用を許可してください'
    puts url
    puts 'URLは(url.txt)にも出力されています'
    puts '応答ページに表示されるコードを入力してenterを押してください'
    puts 'code:'
    code = gets
    credentials = authorizer.get_and_store_credentials_from_code(user_id: user_id, code: code, base_url: OOB_URI)
  end
  
  service = Google::Apis::CalendarV3::CalendarService.new
  service.client_options.application_name = app_name
  service.authorization = credentials
  
  # 対象期間は今年限定
  year = Date.today.year
  time_min = Time.utc(year, 1, 1, 0, 0).iso8601
  time_max = Time.utc(year, 12, 31).iso8601
  # 日付順にソートする
  # このケースでは終日イベントに限定しないとソートに失敗するようだ
  options = {order_by: 'startTime', single_events: true, time_min: time_min, time_max: time_max}
  puts service.list_events(cal_id, options).items.map {|event|
    event.start.date.to_s + ',' + event.summary
  }.join("\n")
```

以下が実行結果です。なお、検索条件に日時順ソートを指定した場合、当日限りの終日イベントに限定しなければ、APIは失敗に終わります。*single_events: true*だいじ。

```
C:\Sites\cal>ruby calendar.rb
2017-01-01,元日
2017-01-02,元日 振替休日
2017-01-09,成人の日
2017-02-11,建国記念の日
2017-03-20,春分の日
2017-04-29,昭和の日
2017-05-03,憲法記念日
2017-05-04,みどりの日
2017-05-05,こどもの日
2017-07-17,海の日
2017-08-11,山の日
2017-09-18,敬老の日
2017-09-23,秋分の日
2017-10-09,体育の日
2017-11-03,文化の日
2017-11-23,勤労感謝の日
2017-12-23,天皇誕生日
```



カレンダー以外のAPIを使うには、*require 'google/apis/calendar_v3'*の*calendar_v3*を書き換えます。[こちら](https://github.com/google/google-api-ruby-client/tree/master/generated/google/apis)を探すと良いでしょう。