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.
102 103 104 105 106 107 108 109 |
# File 'lib/contacts/google.rb', line 102 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)
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/contacts/google.rb', line 62 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.
90 91 92 93 94 95 96 97 98 |
# File 'lib/contacts/google.rb', line 90 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
181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/contacts/google.rb', line 181 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
194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/contacts/google.rb', line 194 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
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 275 |
# File 'lib/contacts/google.rb', line 224 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
216 217 218 |
# File 'lib/contacts/google.rb', line 216 def batch_contacts(&blk) batch(PATH['contacts_batch'], &blk) end |
#batch_groups(&blk) ⇒ Object
220 221 222 |
# File 'lib/contacts/google.rb', line 220 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
162 163 164 165 166 |
# File 'lib/contacts/google.rb', line 162 def contacts( = {}) params = { :limit => 200 }.update() response = get(PATH['contacts_full'], params) parse_contacts response_body(response) end |
#get(path, params) ⇒ Object
:nodoc:
118 119 120 121 122 123 124 125 126 |
# File 'lib/contacts/google.rb', line 118 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
172 173 174 175 176 |
# File 'lib/contacts/google.rb', line 172 def groups( = {}) params = { :limit => 200 }.update() response = get(PATH['groups_full'], params) parse_groups response_body(response) end |
#new_contact(attr = {}) ⇒ Object
206 207 208 209 |
# File 'lib/contacts/google.rb', line 206 def new_contact(attr = {}) c = Contact.new(self) c.load_attributes(attr) end |
#new_group(attr = {}) ⇒ Object
211 212 213 214 |
# File 'lib/contacts/google.rb', line 211 def new_group(attr = {}) g = Group.new(self) g.load_attributes(attr) end |
#post(url, body, headers) ⇒ Object
139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/contacts/google.rb', line 139 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.
130 131 132 |
# File 'lib/contacts/google.rb', line 130 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
135 136 137 |
# File 'lib/contacts/google.rb', line 135 def updated_at_string @updated_string end |