こんにちは、バックエンドエンジニアのまさくにです。
僕は『こち亀』のキャラクターの中でも日暮熟睡男が好きでして、四年ごとに彼に会えることを楽しみにしていたところがあります。しかし、もう彼が起きているシーンを見ることはできないのですね……。
ところで彼の誕生日は 2 月 29 日だそうです。なるほど、四年に一度起きる男は、四年に一度のうるう年生まれ。そこでふと、疑問に思いました。
2 月 29 日生まれの人って Facebook とかで登録しておくと、通知されるのでしょうか? もしかして四年に一度しか「今日は日暮さんの誕生日」みたいに通知されないのでは? もしそうだとしたら不憫です。Facebook があてにならなくても、せめて人の記憶には留めておいてあげたい。
僕が救いましょう。
名付けて、Leap Road。
これからすること
今から先 100 年間、その年がうるう年かどうかチェックして、もしうるう年だった場合、1 月 1 日 〜 2 月 29 日までの Google Calendar に予定を入れておきます。これで、みんな元旦から 2 月 29 日を意識して生きることができるはずです。
具体的には下記の手順になります。
- Google Calendar API を有効化する
- Calendar にアクセスするトークンを取得する
- うるう年のイベントを登録する
それでは開発環境から見ていきましょう。
開発環境
PC が変わったので以前までと少し変わっています。なお、この記事は 2017 年 9 月 27 日時点のものとなります。
$ sw_vers # macのバージョン ProductName: Mac OS X ProductVersion: 10.12.6 BuildVersion: 16G29 $ rbenv -v # rbenvでrubyを入れています rbenv 1.1.1 $ ruby -v ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16]
実行手順
Google Calendar APIを有効化する
Google の API を使用するには、Google Cloud Platform から API の有効化をして、クライアント ID とクライアントシークレットを取得する必要があります。
API はプロジェクトに紐付いています。まずはプロジェクトを作りましょう。
ページの左上をご確認ください。下記のように先程作成したプロジェクトが選択された状態であれば OK です(下記は test プロジェクトが選択された状態です)。
次に使う API のを有効化します。今回はカレンダーなので Google Calendar API を このページ から検索して、「有効にする」を選択してください。
これで API の有効化が完了しました。とは言っても、この API へアクセスするための認証情報がありません。ページ上部に下記のような情報が表示されているのではないでしょうか。
この認証情報を作成することによって、この API へアクセスするためのクライアント(アプリケーション)の登録をおこない、そのトークンを使ってカレンダーへアクセスします。「認証情報を作成」から下記のように選択していってください。
今回は Web サーバーからの認証、かつ、ユーザーのデータを操作するので、上記のように入力して「必要な認証情報」をクリックします。
リダイレクトしてアクセストークンを取得するために、「承認済みのリダイレクト URI 」に下記を登録して、「クライアント ID の作成」をクリックします。
http://localhost:8000/authorize
認可のページでユーザーに表示される情報を記入して「次へ」。
このページで「完了」を選ぶことによって認証情報が作成されます。また「ダウンロード」から Client ID と Client Secret の取得ができるのです。この情報はこれから作るアプリケーションの ID となりますので、ダウンロードしておいてください。以降は特に json のことに言及しませんが、Client ID と Client Secret はこの json に書かれています。必要に応じて、ここから取得をしてください。
Calendarにアクセスするトークンを取得する
アプリケーションがカレンダーを操作できるよう、ユーザーへ認可をもらうためのスクリプトを 3 つ書きます。Googleのクイックスタート には、認証情報をファイルに保存する形での作成例も載っていましたので、そちらもご参照ください。
▼server.rb [Webサーバの代わり]
require 'webrick' srv = WEBrick::HTTPServer.new({ :DocumentRoot => './', :BindAddress => '127.0.0.1', :Port => 8000, :CGIInterpreter => `which ruby`.strip!, :Logger => WEBrick::Log::new(STDOUT, WEBrick::Log::DEBUG), }) srv.mount('/', WEBrick::HTTPServlet::CGIHandler, 'request.rb') srv.mount('/authorize', WEBrick::HTTPServlet::CGIHandler, 'authorize.rb') trap("INT"){ srv.shutdown } srv.start
▼request.rb [トークン取得のリクエスト]
require 'json' require 'net/https' require 'cgi' require 'cgi/session' SCOPE='https://www.googleapis.com/auth/calendar' REDIRECT_URI='http://localhost:8000/authorize' # パラメタからクライアント情報を取得する cgi = CGI.new client_id = cgi.params['client_id'].first.to_s client_secret = cgi.params['client_secret'].first.to_s # authorize.rbの方でも使うのでセッションに入れておく session = CGI::Session.new(cgi) session['client_id'] = client_id session['client_secret'] = client_secret # 認可のためのリダイレクト link = URI.escape("https://accounts.google.com/o/oauth2/auth?client_id=#{client_id}&redirect_uri=#{REDIRECT_URI}&response_type=code&scope=#{SCOPE}&access_type=offline&approval_prompt=force") print cgi.header({ "status" => "REDIRECT", "Location" => link })
▼authorize.rb [トークンの取得と表示]
require 'json' require 'net/https' require 'cgi' require 'cgi/session' cgi = CGI.new session = CGI::Session.new(cgi) params = { :client_id => session['client_id'], :client_secret => session['client_secret'], :redirect_uri => 'http://localhost:8000/authorize', :grant_type => 'authorization_code', :code => cgi.params['code'].first.to_s, } data = params.map { |k, v| [k, v.to_s.encode('utf-8')] }.to_h http = Net::HTTP.new('accounts.google.com', 443) http.use_ssl = true req = Net::HTTP::Post.new('/o/oauth2/token') req.set_form_data(data) res = http.request(req) raise "error: cannot get response." unless res.is_a?(Net::HTTPOK) # show acces_code res_json = JSON.parse(res.body) cgi.out(:type => 'text/plain', :charset => 'UTF-8') { "access_token: #{res_json['access_token']}\nrefresh_token: #{res_json['refresh_token']}" }
それではローカルでサーバーを起動して、自分のカレンダーの認可を取得します。下記のコマンドで Web サーバが起動し、ローカルの 8000 ポートでアクセスができるようになります。ruby server.rb
下記の URL を先ほど取得した client_id、client_secret に書き換えてから、ブラウザでアクセスしてください。
http://localhost:8000/?client_id=[YOUR_CLIENT_ID]&client_secret=[YOUR_CLIENT_SECRET]
下記のようなページにリダイレクトされれば成功です。
「許可」からカレンダーを操作するための権限を「 leap road 」へ渡してあげてください。その後、またローカルへリダイレクトされてアクセストークンと、リフレッシュトークンが表示されるはずです。これもメモっておいてください。
ここまでで取得したトークンの類は、以下の 4 つです。
- client id
- アプリケーションのID。
- client secret
- アプリケーションを証明するためのトークン。
- access token
- アプリケーションを認可したユーザーのトークンで、このトークンを使ってカレンダーを操作する。
- refresh token
- アクセストークンを再取得するためのトークン。アクセストークンには使用期限があり、期限が過ぎると再取得が必要になります。
うるう年のイベントを登録する
やっと準備が整いました。下記スクリプトを実行することによって、先 100 年のうるう年を逃すことがなくなり、うるう年に生まれた人の涙を救うことができるはずです。
実行には Google 謹製の Gem を使います。これを使えば、Google で提供されている API に対して多くの操作が可能になるようですので後々ご覧ください。ちょっと内容がボリューミー過ぎる。
▼Gemfile
source 'https://rubygems.org' gem 'google-api-client'
▼create_leap_road.rb
require 'io/console' require 'signet/oauth_2/client' require 'google/apis/calendar_v3' print "type your client id: " client_id=STDIN.noecho(&:gets).chomp print "\ntype your client_secret: " client_secret=STDIN.noecho(&:gets).chomp print "\ntype your access token: " access_token=STDIN.noecho(&:gets).chomp print "\ntype your refresh_token token: " refresh_token=STDIN.noecho(&:gets).chomp print "\ntype your calendar id: " calendar_id=STDIN.noecho(&:gets).chomp authorization = Signet::OAuth2::Client.new( client_id: client_id, client_secret: client_secret, access_token: access_token, refresh_token: refresh_token, token_credential_uri: 'https://accounts.google.com/o/oauth2/token', scope: 'https://www.googleapis.com/auth/calendar', ) authorization.refresh! service = Google::Apis::CalendarV3::CalendarService.new service.authorization = authorization print "\n\n## start to create leap roads ##\n\n" current_year = Date.today.year current_year.upto(current_year + 100) do |future_year| # うるう年以外は無視 next future_year unless Date.valid_date?(future_year, 2, 29) event = { summary: 'うるう年を忘れるな!', start: { date_time: DateTime.new(future_year, 1, 1, 0, 0, 0, "+09:00:00").to_s, }, end: { date_time: DateTime.new(future_year, 2, 29, 23, 59, 59, "+09:00:00").to_s, } } event = Google::Apis::CalendarV3::Event.new(event) service.insert_event(calendar_id, event) print "create #{future_year}'s leap road!\n" end
実行
実行します。今回のスクリプトは標準入力からトークン情報を入力するようにしてあります。ちなみにマジで 100 年先のうるう年まで予定が入るので、覚悟を決めてから実行してください。
$ bundle exec ruby create_leap_road.rb # 実行 type your client id: # client idを入れる type your client_secret: # client secretを入れる type your access token: # access tokenを入れる type your refresh_token token: # refresh tokenを入れる type your calendar id: # 【後述】calendar idを入れる ## start to create leap roads ## create 2020's leap road! create 2024's leap road! create 2028's leap road! create 2032's leap road! create 2036's leap road! create 2040's leap road! create 2044's leap road! create 2048's leap road! create 2052's leap road! create 2056's leap road! create 2060's leap road! create 2064's leap road! create 2068's leap road! create 2072's leap road! create 2076's leap road! create 2080's leap road! create 2084's leap road! create 2088's leap road! create 2092's leap road! create 2096's leap road! create 2104's leap road! create 2108's leap road! create 2112's leap road! create 2116's leap road!
- Calendar IDについて
- マイカレンダーにカレンダーを追加すると ID が付与されます。マイカレンダーの「カレンダー設定」のページから「カレンダーのアドレス>カレンダー ID 」を検索してください。その ID のカレンダーへこのスクリプトは予定(イベント)を追加します。
結果
おぉ……。
バカげてる……!
猟奇的……!
まとめ
ちなみに Facebook では 2 月 29 日に誕生日登録しておくと、友達へは 28 日に通知されるようです。
How do I get my leap year birthday to show up on March 1st instead of Feb. 28th?
https://www.facebook.com/help/community/question/?id=10202126574184956
超意訳:
2 月 29 日が誕生日なのに 28 日に通知されるの超悲しい。
(うるう年でないなら 3 月 1 日に通知して欲しい)
本稿のスクリプトを使うことによって、先 100 年のうるう年を逃すことはなくなるはずです。もし好きな人が 2 月 29 日生まれだった場合などにご利用ください!
また Google はすでにインフラといえるほど生活に根ざしたサービスになりました。それを API で操作できるようになると、アイディア次第でいろいろと小回りを利かせられそうですね。
なお、今回のソースコードはこちらに置きました。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。