google_calendar 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,120 +1,155 @@
1
+ require 'signet/oauth_2/client'
1
2
  require "addressable/uri"
2
- require 'google/net/https'
3
3
 
4
4
  module Google
5
5
 
6
- # This is a utility class that performs all of the
7
- # communication with the google calendar api.
6
+ #
7
+ # This is a utility class that communicates with the google calendar api.
8
8
  #
9
9
  class Connection
10
- # set the username, password, auth_url, app_name, and login.
10
+ BASE_URI = "https://www.googleapis.com/calendar/v3"
11
+
12
+ attr_accessor :client
13
+
14
+ #
15
+ # Prepare a connection to google for fetching a calendar events
16
+ #
17
+ # the +params+ paramater accepts
18
+ # * :client_id => the client ID that you received from Google after registering your application with them (https://console.developers.google.com/)
19
+ # * :client_secret => the client secret you received from Google after registering your application with them.
20
+ # * :redirect_uri => the url where your users will be redirected to after they have successfully permitted access to their calendars. Use 'urn:ietf:wg:oauth:2.0:oob' if you are using an 'application'"
21
+ # * :refresh_token => if a user has already given you access to their calendars, you can specify their refresh token here and you will be 'logged on' automatically (i.e. they don't need to authorize access again)
11
22
  #
12
23
  def initialize(params)
13
- @username = params[:username]
14
- @password = params[:password]
15
- @auth_url = params[:auth_url] || "https://www.google.com/accounts/ClientLogin"
16
- @app_name = params[:app_name] || "northworld.com-googlecalendar-integration"
17
24
 
18
- login()
25
+ raise ArgumentError unless Connection.credentials_provided?(params)
26
+
27
+ @client = Signet::OAuth2::Client.new(
28
+ :client_id => params[:client_id],
29
+ :client_secret => params[:client_secret],
30
+ :redirect_uri => params[:redirect_url],
31
+ :refresh_token => params[:refresh_token],
32
+ :authorization_uri => 'https://accounts.google.com/o/oauth2/auth',
33
+ :token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
34
+ :scope => "https://www.googleapis.com/auth/calendar"
35
+ )
36
+
37
+ calendar_id = params[:calendar_id]
38
+
39
+ # raise CalenarIDMissing unless calendar_id
40
+ @events_url = "#{BASE_URI}/calendars/#{CGI::escape calendar_id}/events"
41
+
42
+ # try to get an access token if possible.
43
+ if params[:refresh_token]
44
+ @client.refresh_token = params[:refresh_token]
45
+ @client.grant_type = 'refresh_token'
46
+ Connection.get_new_access_token(@client)
47
+ end
48
+
19
49
  end
20
50
 
21
- # login to the google calendar and grab an auth token.
22
51
  #
23
- def login()
24
- content = {
25
- 'Email' => @username,
26
- 'Passwd' => @password,
27
- 'source' => @app_name,
28
- 'accountType' => 'HOSTED_OR_GOOGLE',
29
- 'service' => 'cl'}
52
+ # The URL you need to send a user in order to let them grant you access to their calendars.
53
+ #
54
+ def authorize_url
55
+ @client.authorization_uri
56
+ end
30
57
 
31
- response = send(Addressable::URI.parse(@auth_url), :post_form, content)
58
+ #
59
+ # The single use auth code that google uses during the auth process.
60
+ #
61
+ def auth_code
62
+ @client.code
63
+ end
32
64
 
33
- raise HTTPRequestFailed unless response.kind_of? Net::HTTPSuccess
65
+ #
66
+ # The current access token. Used during a session, typically expires in a hour.
67
+ #
68
+ def access_token
69
+ @client.access_token
70
+ end
34
71
 
35
- @token = response.body.split('=').last
36
- @headers = {
37
- 'Authorization' => "GoogleLogin auth=#{@token}",
38
- 'Content-Type' => 'application/atom+xml'
39
- }
40
- @update_header = @headers.clone
41
- @update_header["If-Match"] = "*"
72
+ #
73
+ # The refresh token is used to obtain a new access token. It remains valid until a user revokes access.
74
+ #
75
+ def refresh_token
76
+ @client.refresh_token
42
77
  end
43
78
 
44
- # send a request to google.
45
79
  #
46
- def send(uri, method, content = '', redirect_count = 10)
47
- raise HTTPTooManyRedirections if redirect_count == 0
80
+ # Convenience method used to streamline the process of logging in with a auth code.
81
+ #
82
+ def login_with_auth_code(auth_code)
83
+ @client.code = auth_code
84
+ Connection.get_new_access_token(@client)
85
+ @client.refresh_token
86
+ end
48
87
 
49
- set_session_if_necessary(uri)
88
+ #
89
+ # Convenience method used to streamline the process of logging in with a refresh token.
90
+ #
91
+ def login_with_refresh_token(refresh_token)
92
+ @client.refresh_token = refresh_token
93
+ @client.grant_type = 'refresh_token'
94
+ Connection.get_new_access_token(@client)
95
+ end
50
96
 
51
- http = (uri.scheme == 'https' ? Net::HTTPS.new(uri.host, uri.inferred_port) : Net::HTTP.new(uri.host, uri.inferred_port))
52
- response = http.request(build_request(uri, method, content))
97
+ #
98
+ # Send a request to google.
99
+ #
100
+ def send(uri, method, content = '')
53
101
 
54
- # recurse if necessary.
55
- if response.kind_of? Net::HTTPRedirection
56
- response = send(Addressable::URI.parse(response['location']), method, content, redirect_count - 1)
57
- end
102
+ response = @client.fetch_protected_resource(
103
+ :uri => uri,
104
+ :method => method,
105
+ :body => content,
106
+ :headers => {'Content-type' => 'application/json'}
107
+ )
58
108
 
59
109
  check_for_errors(response)
60
110
 
61
111
  return response
62
112
  end
63
113
 
64
- protected
114
+ #
115
+ # Wraps the `send` method. Send an event related request to Google.
116
+ #
117
+ def send_events_request(path_and_query_string, method, content = '')
118
+ send(Addressable::URI.parse(@events_url + path_and_query_string), method, content)
119
+ end
65
120
 
66
- # Check to see if we are using a session and extract it's values if required.
67
- #
68
- def set_session_if_necessary(uri) #:nodoc:
69
- # only extract the session if we don't already have one.
70
- @session_id = uri.query_values['gsessionid'] if @session_id == nil && uri.query
121
+ protected
71
122
 
72
- if @session_id
73
- uri.query ||= ''
74
- uri.query_values = uri.query_values.merge({'gsessionid' => @session_id})
123
+ #
124
+ # Utility method to centralize the process of getting an access token.
125
+ #
126
+ def self.get_new_access_token(client) #:nodoc:
127
+ begin
128
+ client.fetch_access_token!
129
+ rescue Signet::AuthorizationError
130
+ raise HTTPAuthorizationFailed
75
131
  end
76
132
  end
77
133
 
78
- # Construct the appropriate request object.
79
134
  #
80
- def build_request(uri, method, content) #:nodoc
81
- case method
82
- when :delete
83
- request = Net::HTTP::Delete.new(uri.to_s, @update_header)
84
-
85
- when :get
86
- request = Net::HTTP::Get.new(uri.to_s, @headers)
87
-
88
- when :post_form
89
- request = Net::HTTP::Post.new(uri.to_s, @headers)
90
- request.set_form_data(content)
91
-
92
- when :post
93
- request = Net::HTTP::Post.new(uri.to_s, @headers)
94
- request.body = content
95
-
96
- when :put
97
- request = Net::HTTP::Put.new(uri.to_s, @update_header)
98
- request.body = content
99
- end # case
100
-
101
- return request
102
- end
103
-
104
135
  # Check for common HTTP Errors and raise the appropriate response.
105
136
  #
106
137
  def check_for_errors(response) #:nodoc
107
- if response.kind_of? Net::HTTPForbidden
108
- raise HTTPAuthorizationFailed, response.body
138
+ case response.status
139
+ when 400 then raise HTTPRequestFailed, response.body
140
+ when 404 then raise HTTPNotFound, response.body
141
+ end
142
+ end
109
143
 
110
- elsif response.kind_of? Net::HTTPBadRequest
111
- raise HTTPRequestFailed, response.body
144
+ private
112
145
 
113
- elsif response.kind_of? Net::HTTPNotFound
114
- raise HTTPNotFound, response.body
115
- end
146
+ #
147
+ #
148
+ #
149
+ def self.credentials_provided?(params) #:nodoc:
150
+ blank = /[^[:space:]]/
151
+ !(params[:client_id] !~ blank) && !(params[:client_secret] !~ blank)
116
152
  end
117
153
 
118
154
  end
119
-
120
- end
155
+ end
@@ -4,4 +4,5 @@ module Google
4
4
  class HTTPNotFound < StandardError; end
5
5
  class HTTPTooManyRedirections < StandardError; end
6
6
  class InvalidCalendar < StandardError; end
7
- end
7
+ class CalenarIDMissing < StandardError; end
8
+ end
@@ -1,60 +1,69 @@
1
- require 'nokogiri'
2
1
  require 'time'
3
2
 
4
3
  module Google
5
4
 
5
+ #
6
6
  # Represents a Google Event.
7
7
  #
8
8
  # === Attributes
9
9
  #
10
- # * +id+ - The google assigned id of the event (nil until saved), read only.
11
- # * +title+ - The title of the event, read/write.
12
- # * +content+ - The content of the event, read/write.
13
- # * +start_time+ - The start time of the event (Time object, defaults to now), read/write.
14
- # * +end_time+ - The end time of the event (Time object, defaults to one hour from now), read/write.
15
- # * +calendar+ - What calendar the event belongs to, read/write.
16
- # * +raw_xml+ - The full google xml representation of the event.
17
- # * +html_link+ - An absolute link to this event in the Google Calendar Web UI. Read-only.
18
- # * +published_time+ - The time of the event creation. Read-only.
19
- # * +updated_time+ - The last update time of the event. Read-only.
10
+ # * +id+ - The google assigned id of the event (nil until saved). Read only.
11
+ # * +title+ - The title of the event. Read Write.
12
+ # * +description+ - The content of the event. Read Write.
13
+ # * +location+ - The location of the event. Read Write.
14
+ # * +start_time+ - The start time of the event (Time object, defaults to now). Read Write.
15
+ # * +end_time+ - The end time of the event (Time object, defaults to one hour from now). Read Write.
16
+ # * +calendar+ - What calendar the event belongs to. Read Write.
17
+ # * +all_day + - Does the event run all day. Read Write.
18
+ # * +quickadd+ - A string that Google parses when setting up a new event. If set and then saved it will take priority over any attributes you have set. Read Write.
19
+ # * +reminders+ - A hash containing reminders. Read Write.
20
+ # * +attendees+ - An array of hashes containing information about attendees. Read Write
21
+ # * +transparency+ - Does the event 'block out space' on the calendar. Valid values are true, false or 'transparent', 'opaque'. Read Write.
22
+ # * +duration+ - The duration of the event in seconds. Read only.
23
+ # * +html_link+ - An absolute link to this event in the Google Calendar Web UI. Read only.
24
+ # * +raw+ - The full google json representation of the event. Read only.
20
25
  #
21
26
  class Event
22
- attr_reader :id, :raw_xml, :html_link, :updated_time, :published_time
23
- attr_accessor :title, :content, :where, :calendar, :quickadd, :transparency
27
+ attr_reader :id, :raw, :html_link
28
+ attr_accessor :title, :location, :calendar, :quickadd, :transparency, :attendees, :description, :reminders
24
29
 
30
+ #
25
31
  # Create a new event, and optionally set it's attributes.
26
32
  #
27
33
  # ==== Example
28
- # Event.new(:title => 'Swimming',
29
- # :content => 'Do not forget a towel this time',
30
- # :where => 'The Ocean',
31
- # :start_time => Time.now,
32
- # :end_time => Time.now + (60 * 60),
33
- # :calendar => calendar_object)
34
+ #
35
+ # event = Google::Event.new
36
+ # event.calendar = AnInstanceOfGoogleCalendaer
37
+ # event.start_time = Time.now
38
+ # event.end_time = Time.now + (60 * 60)
39
+ # event.title = "Go Swimming"
40
+ # event.description = "The polar bear plunge"
41
+ # event.location = "In the arctic ocean"
42
+ # event.transparency = "opaque"
43
+ # event.reminders = { 'useDefault' => false, 'overrides' => ['minutes' => 10, 'method' => "popup"]}
44
+ # event.attendees = [
45
+ # {'email' => 'some.a.one@gmail.com', 'displayName' => 'Some A One', 'responseStatus' => 'tentative'},
46
+ # {'email' => 'some.b.one@gmail.com', 'displayName' => 'Some B One', 'responseStatus' => 'tentative'}
47
+ # ]
34
48
  #
35
49
  def initialize(params = {})
36
- @id = params[:id]
37
- @title = params[:title]
38
- @where = params[:where]
39
- @raw_xml = params[:raw_xml]
40
- @content = params[:content]
41
- @calendar = params[:calendar]
42
- @end_time = params[:end_time]
43
- @quickadd = params[:quickadd]
44
- @html_link = params[:html_link]
45
- @start_time = params[:start_time]
46
- self.all_day = params[:all_day] if params[:all_day]
47
- @updated_time = params[:updated]
48
- @transparency = params[:transparency]
49
- @published_time = params[:published]
50
- end
51
-
52
- # Sets the start time of the Event. Must be a Time object or a parsable string representation of a time.
50
+ [:id, :raw, :html_link,
51
+ :title, :location, :calendar, :quickadd, :attendees, :description, :reminders, :start_time, :end_time, ].each do |attribute|
52
+ instance_variable_set("@#{attribute}", params[attribute])
53
+ end
54
+
55
+ self.transparency = params[:transparency]
56
+ self.all_day = params[:all_day] if params[:all_day]
57
+ end
58
+
59
+ #
60
+ # Sets the start time of the Event. Must be a Time object or a parse-able string representation of a time.
53
61
  #
54
62
  def start_time=(time)
55
63
  @start_time = parse_time(time)
56
64
  end
57
65
 
66
+ #
58
67
  # Get the start_time of the event.
59
68
  #
60
69
  # If no time is set (i.e. new event) it defaults to the current time.
@@ -64,6 +73,7 @@ module Google
64
73
  (@start_time.is_a? String) ? @start_time : @start_time.xmlschema
65
74
  end
66
75
 
76
+ #
67
77
  # Get the end_time of the event.
68
78
  #
69
79
  # If no time is set (i.e. new event) it defaults to one hour in the future.
@@ -73,7 +83,8 @@ module Google
73
83
  (@end_time.is_a? String) ? @end_time : @end_time.xmlschema
74
84
  end
75
85
 
76
- # Sets the end time of the Event. Must be a Time object or a parsable string representation of a time.
86
+ #
87
+ # Sets the end time of the Event. Must be a Time object or a parse-able string representation of a time.
77
88
  #
78
89
  def end_time=(time)
79
90
  @end_time = parse_time(time)
@@ -81,13 +92,18 @@ module Google
81
92
  @end_time = (time.is_a? String) ? Time.parse(time) : time.dup.utc
82
93
  end
83
94
 
95
+ #
84
96
  # Returns whether the Event is an all-day event, based on whether the event starts at the beginning and ends at the end of the day.
85
97
  #
86
98
  def all_day?
87
- time = Time.parse(@start_time)
99
+ time = (@start_time.is_a? String) ? Time.parse(@start_time) : @start_time.dup.utc
88
100
  duration % (24 * 60 * 60) == 0 && time == Time.local(time.year,time.month,time.day)
89
101
  end
90
102
 
103
+ #
104
+ # Makes an event all day, by setting it's start time to the passed in time and it's end time 24 hours later.
105
+ # Note: this will clobber both the start and end times currently set.
106
+ #
91
107
  def all_day=(time)
92
108
  if time.class == String
93
109
  time = Time.parse(time)
@@ -96,61 +112,138 @@ module Google
96
112
  @end_time = (time + 24*60*60).strftime("%Y-%m-%d")
97
113
  end
98
114
 
99
- # Duration in seconds
115
+ #
116
+ # Duration of the event in seconds
117
+ #
100
118
  def duration
101
119
  Time.parse(end_time) - Time.parse(start_time)
102
120
  end
103
121
 
122
+ #
123
+ # Stores reminders for this event. Multiple reminders are allowed.
124
+ #
125
+ # Examples
126
+ #
127
+ # event = cal.create_event do |e|
128
+ # e.title = 'Some Event'
129
+ # e.start_time = Time.now + (60 * 10)
130
+ # e.end_time = Time.now + (60 * 60) # seconds * min
131
+ # e.reminders = { 'useDefault' => false, 'overrides' => [{method: 'email', minutes: 4}, {method: 'popup', minutes: 60}, {method: 'sms', minutes: 30}]}
132
+ # end
133
+ #
134
+ # event = Event.new :start_time => "2012-03-31", :end_time => "2012-04-03", :reminders => { 'useDefault' => false, 'overrides' => [{'minutes' => 10, 'method' => "popup"}]}
135
+ #
136
+ def reminders
137
+ @reminders ||= {}
138
+ end
139
+
140
+ #
141
+ # Utility method that simplifies setting the transparency of an event.
142
+ # You can pass true or false. Defaults to transparent.
143
+ #
144
+ def transparency=(val)
145
+ if val == false || val.to_s.downcase == 'opaque'
146
+ @transparency = 'opaque'
147
+ else
148
+ @transparency = 'transparent'
149
+ end
150
+ end
151
+
152
+ #
153
+ # Returns true if the event is transparent otherwise returns false.
154
+ # Transparent events do not block time on a calendar.
155
+ #
104
156
  def transparent?
105
- transparency == "transparent"
157
+ @transparency == "transparent"
106
158
  end
107
159
 
160
+ #
161
+ # Returns true if the event is opaque otherwise returns false.
162
+ # Opaque events block time on a calendar.
163
+ #
108
164
  def opaque?
109
- transparency == "opaque"
165
+ @transparency == "opaque"
166
+ end
167
+
168
+ #
169
+ # Convenience method used to build an array of events from a Google feed.
170
+ #
171
+ def self.build_from_google_feed(response, calendar)
172
+ events = response['items'] ? response['items'] : [response]
173
+ events.collect {|e| new_from_feed(e, calendar)}.flatten
110
174
  end
111
175
 
112
176
  #
113
- def self.build_from_google_feed(xml, calendar)
114
- Nokogiri::XML(xml).xpath("//xmlns:entry").collect {|e| new_from_xml(e, calendar)}
177
+ # Google JSON representation of an event object.
178
+ #
179
+ def to_json
180
+ "{
181
+ \"summary\": \"#{title}\",
182
+ \"description\": \"#{description}\",
183
+ \"location\": \"#{location}\",
184
+ \"start\": {
185
+ \"dateTime\": \"#{start_time}\"
186
+ },
187
+ \"end\": {
188
+ \"dateTime\": \"#{end_time}\"
189
+ },
190
+ #{attendees_json}
191
+ \"reminders\": {
192
+ #{reminders_json}
193
+ }
194
+ }"
115
195
  end
196
+
197
+ #
198
+ # JSON representation of attendees
199
+ #
200
+ def attendees_json
201
+ return unless @attendees
116
202
 
117
- # Google XMl representation of an evetn object.
203
+ attendees = @attendees.map do |attendee|
204
+ "{
205
+ \"displayName\": \"#{attendee['displayName']}\",
206
+ \"email\": \"#{attendee['email']}\",
207
+ \"responseStatus\": \"#{attendee['responseStatus']}\"
208
+ }"
209
+ end.join(",\n")
210
+
211
+ "\"attendees\": [\n#{attendees}],"
212
+ end
213
+
214
+ #
215
+ # JSON representation of a reminder
118
216
  #
119
- def to_xml
120
- unless quickadd
121
- "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'>
122
- <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'></category>
123
- <title type='text'>#{title}</title>
124
- <content type='text'>#{content}</content>
125
- <gd:transparency value='http://schemas.google.com/g/2005#event.#{transparency}'></gd:transparency>
126
- <gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'></gd:eventStatus>
127
- <gd:where valueString=\"#{where}\"></gd:where>
128
- <gd:when startTime=\"#{start_time}\" endTime=\"#{end_time}\"></gd:when>
129
- </entry>"
217
+ def reminders_json
218
+ if reminders && reminders.is_a?(Hash) && reminders['overrides']
219
+ overrides = reminders['overrides'].map do |reminder|
220
+ "{
221
+ \"method\": \"#{reminder['method']}\",
222
+ \"minutes\": #{reminder['minutes']}
223
+ }"
224
+ end.join(",\n")
225
+ "\n\"useDefault\": false,\n\"overrides\": [\n#{overrides}]"
130
226
  else
131
- %Q{<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gCal='http://schemas.google.com/gCal/2005'>
132
- <content type="html">#{content}</content>
133
- <gCal:quickadd value="true"/>
134
- </entry>}
227
+ "\"useDefault\": true"
135
228
  end
136
229
  end
137
230
 
231
+ #
138
232
  # String representation of an event object.
139
233
  #
140
234
  def to_s
141
- s = "#{title} (#{self.id})\n\t#{start_time}\n\t#{end_time}\n\t#{where}\n\t#{content}"
142
- s << "\n\t#{quickadd}" if quickadd
143
- s
235
+ "Event Id '#{self.id}'\n\tTitle: #{title}\n\tStarts: #{start_time}\n\tEnds: #{end_time}\n\tLocation: #{location}\n\tDescription: #{description}\n\n"
144
236
  end
145
237
 
238
+ #
146
239
  # Saves an event.
147
- # Note: If using this on an event you created without using a calendar object,
148
- # make sure to set the calendar before calling this method.
240
+ # Note: make sure to set the calendar before calling this method.
149
241
  #
150
242
  def save
151
243
  update_after_save(@calendar.save_event(self))
152
244
  end
153
245
 
246
+ #
154
247
  # Deletes an event.
155
248
  # Note: If using this on an event you created without using a calendar object,
156
249
  # make sure to set the calendar before calling this method.
@@ -160,49 +253,53 @@ module Google
160
253
  @id = nil
161
254
  end
162
255
 
163
- protected
164
-
165
- # Create a new event from a google 'entry' xml block.
166
256
  #
167
- def self.new_from_xml(xml, calendar) #:nodoc:
168
- id = xml.at_xpath("gCal:uid")['value'].split('@').first
257
+ # Returns true if the event will use quickadd when it is saved.
258
+ #
259
+ def use_quickadd?
260
+ quickadd && id == nil
261
+ end
169
262
 
170
- # Check if this event came from an apple program (ios, iCal, Calendar, etc)
171
- # Id format ex: E52411E2-8DB9-4A26-AD5A-8B6104320D3C
172
- if id.match( /[0-9A-Z]{8}-([0-9A-Z]{4}-){3}[0-9A-Z]{12}/ )
173
- # Use the ID field instead of the UID which apple overwrites for its own purposes.
174
- # TODO With proper testing, this should be way to parse all event id's
175
- id = xml.at_xpath("xmlns:id").content.split('/').last
176
- end
263
+ #
264
+ # Returns true if this a new event.
265
+ #
266
+ def new_event?
267
+ id == nil || id == ''
268
+ end
177
269
 
178
- event_time_data = xml.at_xpath("gd:when")
270
+ protected
179
271
 
180
- Event.new(:id => id,
272
+ #
273
+ # Create a new event from a google 'entry'
274
+ #
275
+ def self.new_from_feed(e, calendar) #:nodoc:
276
+ Event.new(:id => e['id'],
181
277
  :calendar => calendar,
182
- :raw_xml => xml,
183
- :title => xml.at_xpath("xmlns:title").content,
184
- :content => xml.at_xpath("xmlns:content").content,
185
- :where => xml.at_xpath("gd:where")['valueString'],
186
- :start_time => (event_time_data.nil? ? nil : xml.at_xpath("gd:when")['startTime']),
187
- :end_time => (event_time_data.nil? ? nil : xml.at_xpath("gd:when")['endTime']),
188
- :transparency => xml.at_xpath("gd:transparency")['value'].split('.').last,
189
- :quickadd => (xml.at_xpath("gCal:quickadd") ? (xml.at_xpath("gCal:quickadd")['quickadd']) : nil),
190
- :html_link => xml.at_xpath('//xmlns:link[@title="alternate" and @rel="alternate" and @type="text/html"]')['href'],
191
- :published => xml.at_xpath("xmlns:published").content,
192
- :updated => xml.at_xpath("xmlns:updated").content )
278
+ :raw => e,
279
+ :title => e['summary'],
280
+ :description => e['description'],
281
+ :location => e['location'],
282
+ :start_time => (e['start'] ? e['start']['dateTime'] : ''),
283
+ :end_time => (e['end'] ? e['end']['dateTime'] : ''),
284
+ :transparency => e['transparency'],
285
+ :html_link => e['htmlLink'],
286
+ :updated => e['updated'],
287
+ :reminders => e['reminders'],
288
+ :attendees => e['attendees'] )
289
+
193
290
  end
194
291
 
292
+ #
195
293
  # Set the ID after google assigns it (only necessary when we are creating a new event)
196
294
  #
197
295
  def update_after_save(respose) #:nodoc:
198
296
  return if @id && @id != ''
199
-
200
- xml = Nokogiri::XML(respose.body).at_xpath("//xmlns:entry")
201
- @id = xml.at_xpath("gCal:uid")['value'].split('@').first
202
- @html_link = xml.at_xpath('//xmlns:link[@title="alternate" and @rel="alternate" and @type="text/html"]')['href']
203
- @raw_xml = xml
297
+ @raw = JSON.parse(respose.body)
298
+ @id = @raw['id']
299
+ @html_link = @raw['htmlLink']
204
300
  end
205
301
 
302
+ #
206
303
  # A utility method used centralize time parsing.
207
304
  #
208
305
  def parse_time(time) #:nodoc