Class: Contacts::Google
- Inherits:
-
Object
- Object
- Contacts::Google
- Defined in:
- lib/contacts/google.rb
Overview
Fetching Google Contacts
Web applications should use AuthSub proxy authentication to get an authentication token for a Google account.
First, get the user to follow the following URL:
Contacts::Google.authentication_url('http://mysite.com/invite')
After he authenticates successfully, Google will redirect him back to the target URL (specified as argument above) and provide the token GET parameter. Use it to create a new instance of this class and request the contact list:
gmail = Contacts::Google.new('[email protected]', params[:token])
contacts = gmail.contacts
#-> [ ['Fitzgerald', '[email protected]', '[email protected]'],
['William Paginate', '[email protected]'], ...
]
Storing a session token
The basic token that you will get after the user has authenticated on Google is valid for only one request. However, you can specify that you want a session token which doesn’t expire:
Contacts::Google.authentication_url('http://mysite.com/invite', :session => true)
When the user authenticates, he will be redirected back with a token which still isn’t a session token, but can be exchanged for one!
token = Contacts::Google.sesion_token(params[:token])
Now you have a permanent token. Store it with other user data so you can query the API on his behalf without him having to authenticate on Google each time.
Defined Under Namespace
Constant Summary collapse
- DOMAIN =
'www.google.com'
- AuthSubPath =
all variants go over HTTPS
'/accounts/AuthSub'
- AuthScope =
"http://#{DOMAIN}/m8/feeds/"
- PATH =
{ 'contacts_full' => '/m8/feeds/contacts/default/full', 'contacts_batch' => '/m8/feeds/contacts/default/full/batch', 'groups_full' => '/m8/feeds/groups/default/full', 'groups_batch' => '/m8/feeds/groups/default/full/batch', }
Class Method Summary collapse
-
.authentication_url(target, options = {}) ⇒ Object
URL to Google site where user authenticates.
-
.session_token(token) ⇒ Object
Makes an HTTPS request to exchange the given token with a session one.
Instance Method Summary collapse
-
#all_contacts ⇒ Object
Fetches all contacts in chunks of 200.
- #all_groups ⇒ Object
- #batch(url, &blk) ⇒ Object
- #batch_contacts(&blk) ⇒ Object
- #batch_groups(&blk) ⇒ Object
-
#contacts(options = {}) ⇒ Object
Fetches, parses and returns the contact list.
-
#get(path, params) ⇒ Object
:nodoc:.
-
#groups(options = {}) ⇒ Object
Fetches, parses and returns the group list.
-
#initialize(user_id, token) ⇒ Google
constructor
User ID (email) and token are required here.
- #new_contact(attr = {}) ⇒ Object
- #new_group(attr = {}) ⇒ Object
- #post(url, body, headers) ⇒ Object
-
#updated_at ⇒ Object
Timestamp of last update.
-
#updated_at_string ⇒ Object
Timestamp of last update as it appeared in the XML document.
Constructor Details
#initialize(user_id, token) ⇒ Google
User ID (email) and token are required here. By default, an AuthSub token from Google is one-time only, which means you can only make a single request with it.
101 102 103 104 105 106 107 108 |
# File 'lib/contacts/google.rb', line 101 def initialize(user_id, token) @user = user_id.to_s @headers = { 'Accept-Encoding' => 'gzip', 'User-Agent' => 'agent-that-accepts-gzip', }.update(self.class.auth_headers(token)) @in_batch = false end |
Class Method Details
.authentication_url(target, options = {}) ⇒ Object
URL to Google site where user authenticates. Afterwards, Google redirects to your site with the URL specified as target
.
Options are:
-
:scope
– the AuthSub scope in which the resulting token is valid (default: “www.google.com/m8/feeds/”) -
:secure
– boolean indicating whether the token will be secure (default: false) -
:session
– boolean indicating if the token can be exchanged for a session token (default: false)
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/contacts/google.rb', line 61 def self.authentication_url(target, = {}) params = { :next => target, :scope => AuthScope, :secure => false, :session => false }.merge() query = params.inject [] do |url, pair| unless pair.last.nil? value = case pair.last when TrueClass; 1 when FalseClass; 0 else pair.last end url << "#{pair.first}=#{CGI.escape(value.to_s)}" end url end.join('&') "https://#{DOMAIN}#{AuthSubPath}Request?#{query}" end |
.session_token(token) ⇒ Object
Makes an HTTPS request to exchange the given token with a session one. Session tokens never expire, so you can store them in the database alongside user info.
Returns the new token as string or nil if the parameter couln’t be found in response body.
89 90 91 92 93 94 95 96 97 |
# File 'lib/contacts/google.rb', line 89 def self.session_token(token) http = Net::HTTP.new(DOMAIN, 443) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE response = http.request_get(AuthSubPath + 'SessionToken', auth_headers(token)) pair = response.body.split(/\s+/).detect {|p| p.index('Token') == 0 } pair.split('=').last if pair end |
Instance Method Details
#all_contacts ⇒ Object
Fetches all contacts in chunks of 200.
For example: if you have 1000 contacts, this will render in 5 GET requests
180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/contacts/google.rb', line 180 def all_contacts ret = [] chunk_size = 200 offset = 0 while (chunk = contacts(:limit => chunk_size, :offset => offset)).size != 0 ret.push(*chunk) offset += chunk_size break if chunk.size < chunk_size end ret end |
#all_groups ⇒ Object
193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/contacts/google.rb', line 193 def all_groups ret = [] chunk_size = 200 offset = 0 while (chunk = groups(:limit => chunk_size, :offset => offset)).size != 0 ret.push(*chunk) offset += chunk_size end ret end |
#batch(url, &blk) ⇒ Object
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/contacts/google.rb', line 223 def batch(url, &blk) # Init limit = 512 * 1024 @batch_request = [] @in_batch = true # Execute the block yield # Pack post-request in batch job(s) while !@batch_request.empty? doc = Hpricot("<?xml version='1.0' encoding='UTF-8'?>\n<feed/>", :xml => true) root = doc.root root['xmlns'] = 'http://www.w3.org/2005/Atom' root['xmlns:gContact'] = 'http://schemas.google.com/contact/2008' root['xmlns:gd'] = 'http://schemas.google.com/g/2005' root['xmlns:batch'] = 'http://schemas.google.com/gdata/batch' size = doc.to_s.size 100.times do break if size >= limit || @batch_request.empty? r = @batch_request.shift # Get stuff for request headers = r[1] xml = r[0] # Delete all namespace attributes xml.root.attributes.each { |a,v| xml.root.remove_attribute(a) if a =~ /^xmlns/ } # Find out what to do operation = case headers['X-HTTP-Method-Override'] when 'PUT' 'update' when 'DELETE' 'delete' else 'insert' end xml.root.children << Hpricot.make("<batch:operation type='#{operation}'/>").first root.children << xml.root size += xml.root.to_s.size end #puts "Doing POST... (#{size} bytes)" @in_batch = false post(url, doc, 'Content-Type' => 'application/atom+xml') @in_batch = true end @in_batch = false end |
#batch_contacts(&blk) ⇒ Object
215 216 217 |
# File 'lib/contacts/google.rb', line 215 def batch_contacts(&blk) batch(PATH['contacts_batch'], &blk) end |
#batch_groups(&blk) ⇒ Object
219 220 221 |
# File 'lib/contacts/google.rb', line 219 def batch_groups(&blk) batch(PATH['groups_batch'], &blk) end |
#contacts(options = {}) ⇒ Object
Fetches, parses and returns the contact list.
Options
-
:limit
– use a large number to fetch a bigger contact list (default: 200) -
:offset
– 0-based value, can be used for pagination -
:order
– currently the only value support by Google is “lastmodified” -
:descending
– boolean -
:updated_after
– string or time-like object, use to only fetch contacts that were updated after this date
161 162 163 164 165 |
# File 'lib/contacts/google.rb', line 161 def contacts( = {}) params = { :limit => 200 }.update() response = get(PATH['contacts_full'], params) parse_contacts response_body(response) end |
#get(path, params) ⇒ Object
:nodoc:
117 118 119 120 121 122 123 124 125 |
# File 'lib/contacts/google.rb', line 117 def get(path, params) #:nodoc: response = Net::HTTP.start(DOMAIN) do |google| google.get(path + '?' + query_string(params), @headers) end raise FetchingError.new(response) unless response.is_a? Net::HTTPSuccess response end |
#groups(options = {}) ⇒ Object
Fetches, parses and returns the group list.
Options
see contacts
171 172 173 174 175 |
# File 'lib/contacts/google.rb', line 171 def groups( = {}) params = { :limit => 200 }.update() response = get(PATH['groups_full'], params) parse_groups response_body(response) end |
#new_contact(attr = {}) ⇒ Object
205 206 207 208 |
# File 'lib/contacts/google.rb', line 205 def new_contact(attr = {}) c = Contact.new(self) c.load_attributes(attr) end |
#new_group(attr = {}) ⇒ Object
210 211 212 213 |
# File 'lib/contacts/google.rb', line 210 def new_group(attr = {}) g = Group.new(self) g.load_attributes(attr) end |
#post(url, body, headers) ⇒ Object
138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/contacts/google.rb', line 138 def post(url, body, headers) if @in_batch @batch_request << [body, headers] else response = Net::HTTP.start(DOMAIN) do |google| google.post(url, body.to_s, @headers.merge(headers)) end raise FetchingError.new(response) unless response.is_a? Net::HTTPSuccess response end end |
#updated_at ⇒ Object
Timestamp of last update. This value is available only after the XML document has been parsed; for instance after fetching the contact list.
129 130 131 |
# File 'lib/contacts/google.rb', line 129 def updated_at @updated_at ||= Time.parse @updated_string if @updated_string end |
#updated_at_string ⇒ Object
Timestamp of last update as it appeared in the XML document
134 135 136 |
# File 'lib/contacts/google.rb', line 134 def updated_at_string @updated_string end |