id:otn:20150901 「MS Exchange/Outlook のカレンダーとGoogle Calendarの同期」のその後。
google-api-client が 0.9 になってそのままでは動かなくなった。0.8のまま使っていたのだが、認証がおかしくなった(毎回認証しないとタイムスタンプがおかしいというエラーになる)ので、0.9 対応に修正。いろいろあった。
認証については、googleauth の README.md を参考に。しかし、以前のように「ブラウザが自動的に開いて、認証するとアプリが続行」という風には出来ず、自分でブラウザを開いて、認証したあと、そこに表示された文字列を入力しないといけない。これは不便だがしょうが無い。
あともAPIが色々変わっており、ソースを追ったり、gemにデバッグプリントを入れたりしてなんとかなった。手こずったのがリマインダ。リマインダメソッド指定のキーが、"method" から :reminder_method に変わっている!
#! ruby
CAL_ID = "xxxxxxxxxxxxxxxxx@gmail.com"
DAYS = 30
AUTH_FILE = "authfile.yaml"
SECRET_FILE = "client_secret.json"
ENV["http_proxy"] = "http://user:pass@proxyserver:port"
ENV["https_proxy"] = "http://user:pass@proxyserver:port"
Dir.chdir File.dirname($0)
require "googleauth"
require "googleauth/stores/file_token_store"
require "google/apis/calendar_v3"
require "google/api_client/client_secrets"
require "win32ole"
class Event
def to_s
[@start.strftime("%Y-%m-%d %H:%M"),
@end.strftime("%Y-%m-%d %H:%M"),
@allday.to_s[0],
@reminder.to_s,
@title, @body.gsub(/\s/," "), @location].join(",")
end
def ==(other)
@start == other.instance_variable_get(:@start) and
@end == other.instance_variable_get(:@end) and
@allday == other.instance_variable_get(:@allday) and
reminder_eql?(@start, @reminder, other.instance_variable_get(:@reminder)) and
@title == other.instance_variable_get(:@title) and
@body == other.instance_variable_get(:@body) and
@location == other.instance_variable_get(:@location)
end
def delete
Gcal.delete(@id)
end
def add
event = {
summary: @title,
description: @body,
start: start,
end: ende,
location: @location,
reminders: reminder,
source: {title: "Exchange", url: "http://localhost"}
}
Gcal.add(event)
end
def is_holiday
@allday and @location=="日本"
end
private
def start
if @allday
{date: @start.strftime("%Y-%m-%d")}
else
{date_time: @start.iso8601}
end
end
def ende
if @allday
{date: @end.strftime("%Y-%m-%d")}
else
{date_time: @end.iso8601}
end
end
def reminder
if @reminder
{use_default: false, overrides: [{reminder_method: :popup, minutes: @reminder}]}
else
{use_default: false}
end
end
def reminder_eql?(start, x, y)
@@now ||= Time.now
( x == y ) or
( x and not y and start - x < @@now ) or
( y and not x and start - y < @@now )
end
end
class Gcal < Event
def self.get_auth(calendar_id, auth_filename, secret_filename)
@@cal_id = calendar_id
client_id = Google::Auth::ClientId.from_file(SECRET_FILE)
scope = "https://www.googleapis.com/auth/calendar"
token_store = Google::Auth::Stores::FileTokenStore.new(file: AUTH_FILE)
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)
credentials = authorizer.get_credentials(@@cal_id)
if credentials.nil?
url = authorizer.get_authorization_url(base_url: "urn:ietf:wg:oauth:2.0:oob")
system(%Q|CMD /c start "" "#{url}"|)
print "Enter the resulting code: "
code = gets
credentials = authorizer.get_and_store_credentials_from_code(
user_id: @@cal_id, code: code, base_url: "urn:ietf:wg:oauth:2.0:oob")
end
@@client = Google::Apis::CalendarV3::CalendarService.new
@@client.authorization = credentials
end
def self.delete(id)
@@client.delete_event(@@cal_id, id)
end
def self.add(event)
@@client.insert_event(@@cal_id, Google::Apis::CalendarV3::Event.new(event))
end
def self.is_exchange(item)
item.source and item.source.title == "Exchange"
end
def self.list(from, to)
@@client.list_events(@@cal_id,
order_by: "startTime",
time_min: from.iso8601,
time_max: to.iso8601,
single_events: "True"
).items
end
def initialize(event)
if event.start.date
@start = Time.parse(event.start.date)
@end = Time.parse(event.end.date)
@allday = true
else
@start = event.start.date_time.to_time
@end = event.end.date_time.to_time
@allday = false
end
if event.reminders.use_default
@reminder = 10
elsif event.reminders.overrides
@reminder = event.reminders.overrides[0].minutes
else
@reminder = nil
end
@title = event.summary || ""
@body = event.description || ""
@location = event.location || ""
@id = event.id
end
end
class Ecal < Event
FolderCalendar = 9
def self.list(from, to)
@@calendar ||= WIN32OLE.new("Outlook.Application")
.GetNamespace("MAPI").GetDefaultFolder(FolderCalendar)
items = @@calendar.Items
items.Sort "[Start]"
items.IncludeRecurrences = true
item = items.Find(%Q/[Start] < "#{to.strftime("%Y-%m-%d %H:%M")}" AND [End] >= "#{from.strftime("%Y-%m-%d %H:%M")}"/)
Enumerator.new do |y|
while item
y << item
item = items.FindNext
end
end
end
def initialize(event)
@start = event.Start
@end = event.End
@allday = event.AllDayEvent
@reminder = event.ReminderSet ? event.ReminderMinutesBeforeStart : nil
@title = event.Subject.encode(Encoding::UTF_8)
@body = event.Body.encode(Encoding::UTF_8)
@location = event.Location.encode(Encoding::UTF_8)
end
end
today = Time.now
time_min = today - 3600*24*DAYS
time_max = today + 3600*24*DAYS
e_events = Ecal.list(time_min, time_max).map{|ev| Ecal.new(ev)}.reject(&:is_holiday)
Gcal.get_auth(CAL_ID, AUTH_FILE, SECRET_FILE)
g_events = Gcal.list(time_min, time_max)
.select{|ev| Gcal.is_exchange(ev)}.map{|ev| Gcal.new(ev)}
g_events.reject{|ev| e_events.include?(ev)}.each{|ev| ev.delete}
e_events.reject{|ev| g_events.include?(ev)}.each{|ev| ev.add}