-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfetch-google-calendar.rb
More file actions
executable file
·189 lines (160 loc) · 6.07 KB
/
fetch-google-calendar.rb
File metadata and controls
executable file
·189 lines (160 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/usr/bin/env ruby
# A script to fetch calendar events from Google via Google Calendar API
#
# Prints to stdout, as json, the events on the requested calendar
# You can specify which of the shared calendars (the default is primary) to access,
# and whether you want all or just upcoming.
# See README.md for installation and reference
# reference:
# https://developers.google.com/google-apps/calendar/setup
# https://developers.google.com/google-apps/calendar/instantiate
# https://developers.google.com/google-apps/calendar/v3/reference/events/list#examples
# https://developers.google.com/google-apps/calendar/v3/reference/
require 'google/api_client' # see http://code.google.com/p/google-api-ruby-client/
require 'json'
require 'slop' # see https://github.com/injekt/slop
require 'yaml'
module OAuth
class Google
@@config_file=ENV['HOME'] + '/.google-api.yaml'
class << self
def load
if File.exist?(@@config_file)
return YAML.load_file(@@config_file)
else
warn "#@@config_file not found"
self.abort_config_needed
end
end
def abort_config_needed
abort <<INTRO_TO_AUTH
This utility requires valid oauth credentials and token for your project in the
config file '#@@config_file'.
You can create it by jumping through some oauth hoops by setting up a project and then
using the google-api command, provided by the google-api-client gem:
- Go to Google API Console at https://code.google.com/apis/console/ and set up a project
that you will use to access this data.
- In the "API Access" section, in the list of "Redirect URIs" include
'http://localhost:12736/'.
- Get your project's CLIENT_ID and CLIENT_SECRET to use below.
- Users (including you) will need to grant permissions to access their calendars.
- Generate the config file '#@@config_file' by calling the following, which will launch
the browser and write the config file:
(LD_LIBRARY_PATH=
CLIENT_ID=[YOUR-CLIENT_ID]
CLIENT_SECRET=[YOUR-CLIENT-SECRET]
google-api oauth-2-login --scope=https://www.googleapis.com/auth/calendar --client-id="$CLIENT_ID" --client-secret="$CLIENT_SECRET" )
INTRO_TO_AUTH
end
end
end
end
module Calendar
class Time < ::Time
@@hour = 60*60
@@day = 24*@@hour
def start_of_day
Time.local(year, month, day)
end
def make_upcoming_filter(num_days)
num_days = (num_days || 1).to_f
now=Time.now
starts_before = now.start_of_day + (num_days*@@day + 3*@@hour) # 3am of num_days from today
ends_after = now # want to specify starts_after, but that's not provided
return {
:timeMax => starts_before.iso8601,
:timeMin => ends_after.iso8601,
:singleEvents => "true",
:orderBy => "startTime"}
end
end
end
module EventSugar
def local_start_time
self["start"]["dateTime"]
end
def starts_after?(time)
local_start_time > time
end
end
module Calendar
class GoogleAPIClient < ::Google::APIClient
attr_reader :client
def initialize(oauth)
super
authorization.client_id = oauth["client_id"]
authorization.client_secret = oauth["client_secret"]
authorization.scope = oauth["scope"]
authorization.refresh_token = oauth["refresh_token"]
authorization.access_token = oauth["access_token"]
# NB: seems authorization.expired? does not work b/c times are not stored
# in the yaml -- so we just call authorization.fetch_access_token! on error
# see update_token! in http://code.google.com/p/google-api-ruby-client/wiki/OAuth2
#if authorization.refresh_token && authorization.expired?
# authorization.fetch_access_token!
#end
end
def fetch_data_with_retry(api_method, params)
data = execute_aux(api_method, params).data
if data_error(data) # try refereshing the access token and updating the data
authorization.fetch_access_token!
data = execute_aux(api_method, params).data
end
if err = data_error(data) # raise exception if still an error
raise RuntimeError, err.to_json
end
return data
end
private
def execute_aux(api_method, params)
execute(:api_method => api_method, :parameters => params)
end
def data_error(data)
data.to_hash["error"]
end
end
end
module Calendar
class GoogleCalendarClient < GoogleAPIClient
def events(query)
Enumerator.new {|y| events_aux(query){|event| y << event}}
end
def upcoming_events(query, num_days)
updated_query = query.update(Calendar::Time.new.make_upcoming_filter(num_days))
events(updated_query).find_all do |event|
# TODO: consider including the recently started (like 15 mins?) to handle running late.
event.extend(EventSugar).starts_after? Time.now
end
end
private
def events_aux(cal_query, &block) # requires a block
data = list_events(cal_query)
data.items.each(&block)
if page_token = data.next_page_token
events_aux(cal_query.merge(:pageToken => page_token), &block)
end
end
# Params are as described in:
# https://developers.google.com/google-apps/calendar/v3/reference/events/list
def list_events(params)
fetch_data_with_retry(calendar_service.events.list, params)
end
def calendar_service
discovered_api('calendar', 'v3')
end
end
end
opts = Slop.parse(:help => true) do
banner "#{$0} [options]
Fetch calendar events from Google via Google Calendar API.
Prints to stdout, as json, the events on the requested calendar.\n"
on :calendar=, 'calendar id (defaults to "primary")', :default => 'primary'
on 'upcoming=?', 'events that start before 3am of next day and end after now.
Provide a number to make it that many days ahead.',
:as => :float, :default => 1
end
exit if opts.help?
cal = Calendar::GoogleCalendarClient.new(OAuth::Google.load)
query = {:calendarId => opts[:calendar]}
events = opts.upcoming? ? cal.upcoming_events(query, opts[:upcoming]) : cal.events(query)
events.each{|ev| puts ev.to_json }