100年後の2月29日まで忘れない。Google Calendar APIを使う。【コード掲載】

まさくに


100年後の2月29日まで忘れない。Google Calendar APIを使う。【コード掲載】

こんにちは、バックエンドエンジニアのまさくにです。

僕は『こち亀』のキャラクターの中でも日暮熟睡男が好きでして、四年ごとに彼に会えることを楽しみにしていたところがあります。しかし、もう彼が起きているシーンを見ることはできないのですね……。

ところで彼の誕生日は 2 月 29 日だそうです。なるほど、四年に一度起きる男は、四年に一度のうるう年生まれ。そこでふと、疑問に思いました。

2 月 29 日生まれの人って Facebook とかで登録しておくと、通知されるのでしょうか? もしかして四年に一度しか「今日は日暮さんの誕生日」みたいに通知されないのでは? もしそうだとしたら不憫です。Facebook があてにならなくても、せめて人の記憶には留めておいてあげたい。

 
僕が救いましょう。

名付けて、Leap Road

 

これからすること

今から先 100 年間、その年がうるう年かどうかチェックして、もしうるう年だった場合、1 月 1 日 〜 2 月 29 日までの Google Calendar に予定を入れておきます。これで、みんな元旦から 2 月 29 日を意識して生きることができるはずです。

具体的には下記の手順になります。

  1. Google Calendar API を有効化する
  2. Calendar にアクセスするトークンを取得する
  3. うるう年のイベントを登録する

それでは開発環境から見ていきましょう。

 

開発環境

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 はプロジェクトに紐付いています。まずはプロジェクトを作りましょう。

 
スクリーンショット 2017-09-24 15.59.22

ページの左上をご確認ください。下記のように先程作成したプロジェクトが選択された状態であれば OK です(下記は test プロジェクトが選択された状態です)。

 
スクリーンショット 2017-09-24 16.02.53

次に使う API のを有効化します。今回はカレンダーなので Google Calendar API を このページ から検索して、「有効にする」を選択してください。

 
スクリーンショット 2017-09-21 20.34.41

これで API の有効化が完了しました。とは言っても、この API へアクセスするための認証情報がありません。ページ上部に下記のような情報が表示されているのではないでしょうか。

 
スクリーンショット 2017-09-21 20.34.59

この認証情報を作成することによって、この API へアクセスするためのクライアント(アプリケーション)の登録をおこない、そのトークンを使ってカレンダーへアクセスします。「認証情報を作成」から下記のように選択していってください。

 
スクリーンショット 2017-09-21 20.38.28

今回は Web サーバーからの認証、かつ、ユーザーのデータを操作するので、上記のように入力して「必要な認証情報」をクリックします。

 
スクリーンショット 2017-09-21 20.40.37

リダイレクトしてアクセストークンを取得するために、「承認済みのリダイレクト URI 」に下記を登録して、「クライアント ID の作成」をクリックします。

http://localhost:8000/authorize

 
スクリーンショット 2017-09-21 20.44.22

認可のページでユーザーに表示される情報を記入して「次へ」。

 
スクリーンショット 2017-09-21 20.44.36

このページで「完了」を選ぶことによって認証情報が作成されます。また「ダウンロード」から 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]

 
下記のようなページにリダイレクトされれば成功です。

 
スクリーンショット 2017-09-24 16.20.33

 
スクリーンショット 2017-09-24 16.20.44

「許可」からカレンダーを操作するための権限を「 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 のカレンダーへこのスクリプトは予定(イベント)を追加します。

 

結果

スクリーンショット 2017-09-24 14.11.28

おぉ……。

 
スクリーンショット 2017-09-24 14.13.41

バカげてる……!

 
スクリーンショット 2017-09-24 14.13.41

猟奇的……!

 

まとめ

ちなみに 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 で操作できるようになると、アイディア次第でいろいろと小回りを利かせられそうですね。

 
なお、今回のソースコードはこちらに置きました。

 

まさくに
この記事を書いた人
まさくに

バックエンドエンジニア

おすすめ記事

Recommended by