前置き
自分の日々のスケジュールを楽に管理したいと思いました。スケジュールはGoogleカレンダーをメインに管理するとして、ある程度決まった行事をスクリプトからAPIで操作できれば楽になりそうです。そこで、第一の目標は使い慣れたrubyで平日と休日の判定をしたいと思った次第。
事前調査
先行事例は山ほどあるはずなので、とりあえず適当なキーワードで検索します。
そのままの記事がありました。 Rubyから利用するにはgoogle-api-ruby-clientを使うとのこと。このライブラリについて調べてみるとこんな記事が… 。参考文献はできるだけ新しい記事を選ぶのがよさそうです。Calendar apiについては次の記事が役に立ちそうです。
Google Calendar API を Ruby から使う: 認証と基本的なAPI
Google Calendar API Rubyでイベントを登録する
参考資料はかなり集まってきましたが、 oauth認証については記事ごとにやり方が違う… 。焦げ臭い匂いが漂ってきますからoauthを中心に検索してしてみるとGoogle Auth Library for Rubyという認証ライブラリもあるようです。どっちを使えばいいのやら。
共通点はgoogle-api-ruby-clientを使うってことです。
GoogleアカウントへのOAuth認証
何をおいてもGoogleにお願いして、クライアントアプリを登録しなければなりません。
登録についてはこちらを参照していただくのがよろしいかと思います。手順としましては、
- google apiコンソールでカレンダーapiを有効
- 認証情報タブから認証情報を作成ボタンをクリック
- OAuthクライアントIDを選択
- アプリケーションの種別はその他(installed)を選択
- 作成されたクライアントID(jsonファイル)をダウンロード
でございます。
ここからさらにトークンを取得する作業が必要なのですが、後回しにします。
動作環境の準備
先程の工程でダウンロードしたjsonファイルはこのスクリプトと同じディレクトリにファイル名client_id.jsonで配置してください。
{
"installed": {
"client_id": "..."
}
}
内容は見やすいようにに編集してあります
認証に関してはgoogle-auth-library-rubyを使います。APIの利用はgoogle-api-ruby-clientを使います。
source 'https://rubygems.org'
gem 'google-api-client'
gem 'googleauth'
#gem 'redis'
認証処理はドキュメントのサンプルにある通り、次のようの手順となります。ファイル名やメールアドレスは想像力で補ってください。
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の利用を許可した上で、返ってきたページからコードをゲットします。
この作業は手動でも実行できますが、トークンには利用期限があることですし、毎回手作業ではやってられません。そこで上記のコードに次のスクリプトを追加して、ある程度自動化します。
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
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を書き換えます。こちらを探すと良いでしょう。