gcalapi 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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