gcalapi 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. data/Rakefile +19 -15
  2. data/VERSION +1 -1
  3. data/example/mixi2gcal.rb +88 -0
  4. data/html/classes/GoogleCalendar.html +149 -280
  5. data/html/classes/GoogleCalendar/AuthSubFailed.html +165 -0
  6. data/html/classes/GoogleCalendar/AuthSubUtil.html +397 -0
  7. data/html/classes/GoogleCalendar/AuthenticationFailed.html +110 -110
  8. data/html/classes/GoogleCalendar/Calendar.html +381 -203
  9. data/html/classes/GoogleCalendar/Event.html +800 -510
  10. data/html/classes/GoogleCalendar/EventDeleteFailed.html +110 -110
  11. data/html/classes/GoogleCalendar/EventGetFailed.html +111 -0
  12. data/html/classes/GoogleCalendar/EventInsertFailed.html +110 -110
  13. data/html/classes/GoogleCalendar/EventUpdateFailed.html +110 -110
  14. data/html/classes/GoogleCalendar/InvalidCalendarURL.html +110 -110
  15. data/html/classes/GoogleCalendar/Service.html +305 -594
  16. data/html/classes/GoogleCalendar/ServiceAuthSub.html +181 -0
  17. data/html/classes/GoogleCalendar/ServiceBase.html +694 -0
  18. data/html/created.rid +1 -1
  19. data/html/files/README.html +116 -116
  20. data/html/files/lib/googlecalendar/auth_sub_util_rb.html +111 -0
  21. data/html/files/lib/googlecalendar/calendar_rb.html +109 -109
  22. data/html/files/lib/googlecalendar/event_rb.html +109 -109
  23. data/html/files/lib/googlecalendar/service_auth_sub_rb.html +108 -0
  24. data/html/files/lib/googlecalendar/service_base_rb.html +114 -0
  25. data/html/files/lib/googlecalendar/service_rb.html +107 -113
  26. data/html/fr_class_index.html +39 -34
  27. data/html/fr_file_index.html +32 -29
  28. data/html/fr_method_index.html +75 -52
  29. data/html/index.html +23 -23
  30. data/html/rdoc-style.css +207 -207
  31. data/lib/googlecalendar/auth_sub_util.rb +143 -0
  32. data/lib/googlecalendar/calendar.rb +48 -36
  33. data/lib/googlecalendar/event.rb +16 -11
  34. data/lib/googlecalendar/service.rb +30 -180
  35. data/lib/googlecalendar/service_auth_sub.rb +18 -0
  36. data/lib/googlecalendar/service_base.rb +197 -0
  37. data/test/00_service_test.rb +2 -1
  38. data/test/01_calendar_test.rb +1 -1
  39. data/test/02_event_test.rb +28 -1
  40. data/test/03_authsub_test.rb +119 -0
  41. data/test/base_unit.rb +3 -0
  42. metadata +105 -62
@@ -0,0 +1,143 @@
1
+ require "cgi"
2
+ require "uri"
3
+ require "net/http"
4
+ require "net/https"
5
+
6
+ module GoogleCalendar
7
+
8
+ #
9
+ # Exception about AuthSub
10
+ #
11
+ class AuthSubFailed < StandardError
12
+ attr_accessor :http_response
13
+ def initialize(res)
14
+ http_response = res
15
+ end
16
+ end
17
+ #
18
+ # = Summary
19
+ # Helper class for AuthSub authentication.
20
+ # For detail, see http://code.google.com/apis/accounts/AuthForWebApps.html
21
+ # Currently, this class is available for only unregistered website.
22
+ #
23
+ # = How to use this class
24
+ #
25
+ # == Show AuthSubRequest link to a user.
26
+ #
27
+ # First, you need to show your user an anchor to the AuthSubRequest. The user can get authentication token
28
+ # in the page. And the user will redirect back to your Website with authentication token.
29
+ #
30
+ # request_url = AuthSubUtil.build_request_url(next_url, AuthSubUtil::CALENDAR_SCOPE, use_secure, use_session)
31
+ #
32
+ # == Get token from redirected URL.
33
+ #
34
+ # The redirected URL string contains one time session token. You can get the token using get_one_time_token method.
35
+ #
36
+ # token = AuthSubUtil.get_one_time_token(urlstr)
37
+ #
38
+ # == Get session token.
39
+ #
40
+ # You will get an one time token above process. Then you can get longtime living sessin token.
41
+ #
42
+ # session = AuthSubUtil.exchange_session_token(one_time_token)
43
+ #
44
+ # == make a ServiceAuthSub instance instead of Service.
45
+ #
46
+ # srv = GoogleCalendar::ServiceAuthSub.new(session_token)
47
+ #
48
+ # == Revoke session token.
49
+ #
50
+ # Google limits the number of session token per user. So you should revoke the session token after using.
51
+ #
52
+ # AuthSubUtil.revoke_session_token(session_token)
53
+ #
54
+ class AuthSubUtil
55
+ REQUEST_URL = "https://www.google.com/accounts/AuthSubRequest"
56
+ SESSION_URL = "https://www.google.com/accounts/AuthSubSessionToken"
57
+ REVOKE_URL = "https://www.google.com/accounts/AuthSubRevokeToken"
58
+ INFO_URL = "https://www.google.com/accounts/AuthSubTokenInfo"
59
+
60
+ CALENDAR_SCOPE = "http://www.google.com/calendar/feeds/"
61
+
62
+ #
63
+ # Build url for AuthSubRequest.
64
+ # http://code.google.com/apis/accounts/AuthForWebApps.html#AuthSubRequest
65
+ # Currently, secure token is not implemented.
66
+ #
67
+ def self.build_request_url(next_url, scope, use_secure, use_session)
68
+ hq = [["next", next_url],
69
+ ["scope", CALENDAR_SCOPE],
70
+ ["secure", use_secure ? "1" : "0"],
71
+ ["session", use_session ? "1" : "0"]]
72
+ query = hq.map do |elem| "#{elem[0]}=#{CGI.escape(elem[1])}" end.join("&")
73
+ return "#{REQUEST_URL}?#{query}"
74
+ end
75
+
76
+ #
77
+ # Get authentication token from the redirected url.
78
+ # When the AuthSubRequest is accepted, the edirected URL string (specified in next_url parameter of
79
+ # build_reque4st_url method) contains authentication token. This method retrieves the token from url string.
80
+ # This token is for a single use only. To get long-lived token, use exchange_session_token method.
81
+ #
82
+ def self.get_one_time_token(url_str)
83
+ uri = URI.parse(url_str)
84
+ params = CGI.parse(uri.query)
85
+ throw AuthSubFailed, "Token is not found" unless params.key?("token")
86
+ return params["token"][0]
87
+ end
88
+
89
+ #
90
+ # Get session token.
91
+ # The authentication token you get by calling AuthSubRequest is available only once.
92
+ # To get long-lived token, use this.
93
+ # For detail, see http://code.google.com/apis/accounts/AuthForWebApps.html#AuthSubSessionToken
94
+ #
95
+ def self.exchange_session_token(one_time_token)
96
+ res = do_get_with_ssl(SESSION_URL, one_time_token)
97
+ throw AuthSubFailed.new(res) unless res.code == "200"
98
+ session_token = nil
99
+ if /Token=(.*)$/ =~ res.body
100
+ session_token = $1.to_s
101
+ else
102
+ throw AuthSubFailed.new(res), "Token not found"
103
+ end
104
+ return session_token
105
+ end
106
+
107
+ #
108
+ # You can get session token by calling exchange_session_token method. Session token will remain
109
+ # until you revoke.
110
+ # For detail, http://code.google.com/apis/accounts/AuthForWebApps.html#AuthSubRevokeToken
111
+ #
112
+ def self.revoke_session_token(session_token)
113
+ res = do_get_with_ssl(REVOKE_URL, session_token)
114
+ throw AuthSubFailed.new(res) unless res.code == "200"
115
+ return res
116
+ end
117
+
118
+
119
+ def self.token_info(session_token)
120
+ res = do_get_with_ssl(INFO_URL, session_token)
121
+ throw AuthSubFailed.new(res), res.to_s unless res.code == "200"
122
+ ret = {}
123
+ res.body.each_line do |line|
124
+ ret[$1] = $2 if line =~ /^([^=]+)=(.+)$/
125
+ end
126
+ return ret
127
+ end
128
+
129
+ private
130
+
131
+ def self.do_get_with_ssl(str_uri, token)
132
+ res = nil
133
+ uri = URI.parse(str_uri)
134
+ https = Net::HTTP.new(uri.host, uri.port)
135
+ https.use_ssl = true
136
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
137
+ https.start do |http|
138
+ res = http.get(uri.path, {"Authorization" => "AuthSub token=\"#{token}\""})
139
+ end
140
+ return res
141
+ end
142
+ end # AuthSubUtil
143
+ end # GoogleCalendar
@@ -2,8 +2,14 @@ require "googlecalendar/service"
2
2
  require "googlecalendar/event"
3
3
  require "rexml/document"
4
4
 
5
+ #
6
+ # = SUMMARY
7
+ # google calendar api for ruby
8
+ #
5
9
  module GoogleCalendar
6
10
  class InvalidCalendarURL < StandardError; end #:nodoc: all
11
+
12
+ #
7
13
  # = SUMMARY
8
14
  # This class represents User's Calendar.
9
15
  #
@@ -22,47 +28,14 @@ module GoogleCalendar
22
28
  # srv = GoogleCalendar::Service.new(MAIL, PASS)
23
29
  # cal = Calendar.new(srv, FEED)
24
30
  #
25
- class Calendar
26
-
27
- #
28
- # get user's calendar list.
29
- #
30
- def self.calendars(srv)
31
- ret = srv.calendar_list
32
- list = REXML::Document.new(ret.body)
33
- h = {}
34
- list.root.elements.each("entry/link") do |e|
35
- if e.attributes["rel"] == "alternate"
36
- feed = e.attributes["href"]
37
- h[feed] = Calendar.new(srv, feed)
38
- end
39
- end
40
- h
41
- end
42
31
 
43
- #
44
- # defines calendar's readonly attributes
45
- #
46
- ATTRIBUTES = {
47
- "updated" => ["updated"],
48
- "title" => ["title"],
49
- "subtitle" => ["subtitle"],
50
- "name" => ["author/name"],
51
- "email" => ["author/email"],
52
- "timezone" => ["gCal:timezone", "value"],
53
- "where" => ["gd:where", "valueString"]}.each do |key, val|
54
- module_eval(
55
- "def #{key}; self.source.root.elements[\"#{val[0]}\"]." +
56
- (val.length == 1 ? "text" : "attributes[\"#{val[1]}\"]") +
57
- "; end"
58
- )
59
- end
32
+ class Calendar
60
33
 
61
34
  attr_reader :feed
62
35
 
63
36
  # srv: GoogleCalendar::Service object
64
- # feed: Calendar's editable feed url
65
- def initialize(srv, feed)
37
+ # feed: Calendar's editable feed url(default value: user's default calendar)
38
+ def initialize(srv, feed = DEFAULT_CALENDAR_FEED)
66
39
  @srv = srv
67
40
  @feed = feed
68
41
  @source = nil
@@ -112,6 +85,45 @@ module GoogleCalendar
112
85
  raise InvalidCalendarURL, ret.inspect unless ret.code == "200"
113
86
  REXML::Document.new(ret.body)
114
87
  end
88
+
89
+ public
90
+
91
+ DEFAULT_CALENDAR_FEED = "http://www.google.com/calendar/feeds/default/private/full"
92
+ #
93
+ # get user's calendar list.
94
+ #
95
+ def self.calendars(srv)
96
+ ret = srv.calendar_list
97
+ list = REXML::Document.new(ret.body)
98
+ h = {}
99
+ list.root.elements.each("entry/link") do |e|
100
+ if e.attributes["rel"] == "alternate"
101
+ feed = e.attributes["href"]
102
+ h[feed] = Calendar.new(srv, feed)
103
+ end
104
+ end
105
+ h
106
+ end
107
+
108
+ #
109
+ # defines calendar's readonly attributes
110
+ #
111
+ ATTRIBUTES = {
112
+ "updated" => ["updated"],
113
+ "title" => ["title"],
114
+ "subtitle" => ["subtitle"],
115
+ "name" => ["author/name"],
116
+ "email" => ["author/email"],
117
+ "timezone" => ["gCal:timezone", "value"],
118
+ "where" => ["gd:where", "valueString"]}.each do |key, val|
119
+ module_eval(
120
+ "def #{key}; self.source.root.elements[\"#{val[0]}\"]." +
121
+ (val.length == 1 ? "text" : "attributes[\"#{val[1]}\"]") +
122
+ "; end"
123
+ )
124
+ end
125
+
115
126
  end # class Calendar
127
+
116
128
  end # module
117
129
 
@@ -6,6 +6,7 @@ module GoogleCalendar
6
6
  class EventInsertFailed < StandardError; end #:nodoc: all
7
7
  class EventUpdateFailed < StandardError; end #:nodoc: all
8
8
  class EventDeleteFailed < StandardError; end #:nodoc: all
9
+ class EventGetFailed < StandardError; end #:nodoc: all
9
10
 
10
11
  #
11
12
  # = Summary
@@ -16,6 +17,8 @@ module GoogleCalendar
16
17
  # * MAIL: your gmail account.
17
18
  # * PASS: password for MAIL.
18
19
  # * FEED: a calendar's editable feed url.
20
+ # 0. your default calendar's feed url is defined in Calendar::DEFAULT_CALENDAR_FEED.
21
+ # To get other calendar's feed url, read below.
19
22
  # 1. click "Manage Calendars" in Google Calendar.
20
23
  # 2. select a calendar you want to edit.
21
24
  # 3. copy private address of XML.
@@ -70,6 +73,10 @@ module GoogleCalendar
70
73
  # event.allday = true
71
74
  # event.save!
72
75
  #
76
+ # == get existint event
77
+ #
78
+ # event = Event.get(FEED, Service.new(MAIL, PASS))
79
+ #
73
80
  # = TODO
74
81
  #
75
82
  # * this class doesn't support recurring event.
@@ -156,6 +163,15 @@ XML
156
163
  @xml.to_s
157
164
  end
158
165
 
166
+ # get event from event feed
167
+ def self.get(feed, srv)
168
+ ret = srv.query(feed)
169
+ raise EventGetFailed, ret.body unless ret.code == "200"
170
+ evt = Event.new
171
+ evt.srv = srv
172
+ evt.load_xml(ret.body)
173
+ evt
174
+ end
159
175
  private
160
176
 
161
177
  def do_without_exception(method)
@@ -268,16 +284,5 @@ XML
268
284
  end
269
285
  ret
270
286
  end
271
-
272
- # convert string to numeric character reference
273
- def num_char_ref(str)
274
- str.to_s.split(//u).map do |c|
275
- if /[[:alnum:][:space:][:punct:]]/.match(c) then
276
- c
277
- else
278
- "&##{c.unpack('U*')[0]};"
279
- end
280
- end.join
281
- end
282
287
  end #class Event
283
288
  end #module GoogleCalendar
@@ -1,23 +1,12 @@
1
- require "cgi"
2
- require "uri"
3
- require "net/http"
4
- require "net/https"
5
- require "open-uri"
6
- require "nkf"
7
- require "time"
8
-
9
- Net::HTTP.version_1_2
1
+ require "googlecalendar/service_base"
10
2
 
11
3
  module GoogleCalendar
12
-
13
- class AuthenticationFailed < StandardError; end #:nodoc: all
14
-
15
4
  #
16
- # This class interacts with google calendar service.
5
+ # This class interacts with google calendar service.
6
+ # If you want to use ClientLogin for authentication, use this class.
7
+ # If you want to use AuthSub, use ServiceAuthSub.
17
8
  #
18
- class Service
19
- # Server name to Authenticate
20
- AUTH_SERVER = "www.google.com"
9
+ class Service < ServiceBase
21
10
 
22
11
  # Server Path to authenticate
23
12
  AUTH_PATH = "/accounts/ClientLogin"
@@ -25,192 +14,53 @@ module GoogleCalendar
25
14
  # URL to get calendar list
26
15
  CALENDAR_LIST_PATH = "http://www.google.com/calendar/feeds/"
27
16
 
28
- # proxy server address
29
- @@proxy_addr = nil
30
- def self.proxy_addr
31
- @@proxy_addr
32
- end
33
-
34
- def self.proxy_addr=(addr)
35
- @@proxy_addr=addr
36
- end
37
-
38
- # proxy server port number
39
- @@proxy_port = nil
40
- def self.proxy_port
41
- @@proxy_port
42
- end
43
-
44
- def self.proxy_port=(port)
45
- @@proxy_port = port
46
- end
47
-
48
- # proxy server username
49
- @@proxy_user = nil
50
- def self.proxy_user
51
- @@proxy_user
52
- end
53
-
54
- def self.proxy_user=(user)
55
- @@proxy_user = user
56
- end
57
-
58
- # proxy server password
59
- @@proxy_pass = nil
60
- def self.proxy_pass
61
- @@proxy_pass
62
- end
63
-
64
- def self.proxy_pass=(pass)
65
- @@proxy_pass = pass
66
- end
67
-
68
-
69
- def initialize(email, pass)
70
- @email = email
71
- @pass = pass
72
- @session = nil
73
- @cookie = nil
74
- @auth = nil
75
- end
76
-
77
17
  #
78
18
  # get the list of user's calendars and returns http response object
79
19
  #
80
20
  def calendar_list
21
+ logger.info("-- calendar list st --") if logger
81
22
  auth unless @auth
82
23
  uri = URI.parse(CALENDAR_LIST_PATH + @email)
83
- do_get(uri, "Authorization" => "GoogleLogin auth=#{@auth}")
24
+ res = do_get(uri, {})
25
+ logger.info("-- calendar list en(#{res.message}) --") if logger
26
+ res
84
27
  end
85
28
 
86
29
  alias :calendars :calendar_list
87
30
 
88
- #
89
- # send query for events of a calendar and returns http response object.
90
- # available condtions are
91
- # * :q => query string
92
- # * :max-results => max contents count. (default: 25)
93
- # * :start-index => 1-based index of the first result to be retrieved
94
- # * :orderby => the order of retrieved data.
95
- # * :published-min => Bounds on the entry publication date(oldest)
96
- # * :published-max => Bounds on the entry publication date(newest)
97
- # * :updated-min => Bounds on the entry update date(oldest)
98
- # * :updated-max => Bounds on the entry update date(newest)
99
- # * :author => Entry author
100
- #
101
- # For detail, see http://code.google.com/apis/gdata/protocol.html#Queries
102
- #
103
- def query(cal_url, conditions)
104
- auth unless @auth
105
- uri = URI.parse(cal_url)
106
- uri.query = conditions.map do |key, val|
107
- "#{key}=#{URI.escape(val.kind_of?(Time) ? val.getutc.iso8601 : val.to_s)}"
108
- end.join("&")
109
- do_get(uri, "Authorization" => "GoogleLogin auth=#{@auth}")
110
- end
111
-
112
- #
113
- # delete an event.
114
- #
115
- def delete(feed)
116
- auth unless @auth
117
- uri = URI.parse(feed)
118
- do_post(uri,
119
- {"X-HTTP-Method-Override" => "DELETE",
120
- "Authorization" => "GoogleLogin auth=#{@auth}"},
121
- "DELETE " + uri.path)
122
- end
123
-
124
- #
125
- # insert an event
126
- #
127
- def insert(feed, event)
128
- auth unless @auth
129
- uri = URI.parse(feed)
130
- do_post(uri,
131
- {"Authorization" => "GoogleLogin auth=#{@auth}",
132
- "Content-Type" => "application/atom+xml",
133
- "Content-Length" => event.length.to_s}, event)
134
- end
135
-
136
- #
137
- # update an event.
138
- #
139
- def update(feed, event)
140
- auth unless @auth
141
- uri = URI.parse(feed)
142
- do_post(uri,
143
- {"X-HTTP-Method-Override" => "PUT",
144
- "Authorization" => "GoogleLogin auth=#{@auth}",
145
- "Content-Type" => "application/atom+xml",
146
- "Content-Length" => event.length.to_s}, event)
147
- end
148
-
149
- private
150
-
151
- # authencate
31
+ def initialize(email, pass)
32
+ @email = email
33
+ @pass = pass
34
+ @session = nil
35
+ @cookie = nil
36
+ @auth = nil
37
+ end
38
+
39
+ private
152
40
  def auth
153
41
  https = Net::HTTP.new(AUTH_SERVER, 443, @@proxy_addr, @@proxy_port, @@proxy_user, @@proxy_pass)
154
42
  https.use_ssl = true
155
43
  https.verify_mode = OpenSSL::SSL::VERIFY_NONE
156
44
  head = {'Content-Type' => 'application/x-www-form-urlencoded'}
45
+ logger.info "-- auth st --" if logger
157
46
  https.start do |w|
158
47
  res = w.post(AUTH_PATH, "Email=#{@email}&Passwd=#{@pass}&source=company-app-1&service=cl", head)
48
+ logger.debug res if logger
159
49
  if res.body =~ /Auth=(.+)/
160
50
  @auth = $1
161
51
  else
162
- raise AuthenticationFailed
163
- end
164
- end
165
- end
166
-
167
- def do_post(uri, header, content)
168
- res = nil
169
- try_http(uri, header, content) do |http,path,head,args|
170
- cont = args[0]
171
- res = http.post(path, cont, head)
172
- end
173
- res
174
- end
175
-
176
- def do_get(uri, header)
177
- res = nil
178
- try_http(uri, header) do |http,path,head|
179
- res = http.get(path, head)
180
- end
181
- res
182
- end
183
-
184
- def try_http(uri, header, *args)
185
- res = nil
186
- Net::HTTP.start(uri.host, uri.port, @@proxy_addr, @@proxy_port, @@proxy_user, @@proxy_pass) do |http|
187
- header["Cookie"] = @cookie if @cookie
188
- res = yield(http, path_with_authorized_query(uri), header, args)
189
- if res.code == "302"
190
- ck = sess = nil
191
- ck = res["set-cookie"] if res.key?("set-cookie")
192
- uri = URI.parse(res["location"]) if res.key?("location")
193
- if uri && uri.query
194
- qr = CGI.parse(uri.query)
195
- sess = qr["gsessionid"][0] if qr.key?("gsessionid")
196
- end
197
- if ck && sess
198
- header["Cookie"] = @cookie = ck
199
- @session = sess
200
- res = yield(http, path_with_authorized_query(uri), header, args)
201
- else
202
- p res
52
+ if logger
53
+ logger.fatal(res)
54
+ logger.fatal(res.body)
203
55
  end
56
+ raise AuthenticationFailed
204
57
  end
205
58
  end
206
- res
59
+ logger.info "-- auth en --" if logger
207
60
  end
208
-
209
- def path_with_authorized_query(uri)
210
- query = CGI.parse(uri.query.nil? ? "" : uri.query)
211
- query["gsessionid"] = [@session] if @session
212
- qs = query.map do |k,v| "#{CGI.escape(k)}=#{CGI.escape(v[0])}" end.join("&")
213
- qs.empty? ? uri.path : "#{uri.path}?#{qs}"
61
+
62
+ def add_authorize_header(header)
63
+ header["Authorization"] = "GoogleLogin auth=#{@auth}"
214
64
  end
215
- end # class Service
216
- end # module
65
+ end # Service
66
+ end # GoogleCalendar