gmail-contacts 0.0.4
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.
- data/README.md +55 -0
- data/Rakefile +150 -0
- data/gmail-contacts.gemspec +56 -0
- data/lib/gmail-contacts/google.rb +306 -0
- data/lib/gmail-contacts.rb +48 -0
- metadata +60 -0
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
gmail-contacts
|
2
|
+
==============
|
3
|
+
|
4
|
+
A Ruby gem for getting a list of your Gmail contacts. Most of the code here has been extracted from
|
5
|
+
[mislav's contacts gem](https://github.com/mislav/contacts). It's been updated to work with Ruby 1.9
|
6
|
+
and Rails 3.
|
7
|
+
|
8
|
+
Installation
|
9
|
+
-------
|
10
|
+
|
11
|
+
gem install gmail-contacts
|
12
|
+
|
13
|
+
Usage
|
14
|
+
-----
|
15
|
+
Get a link to the authorization URL:
|
16
|
+
|
17
|
+
GmailContacts::Google.authentication_url("http://mysite.com/invites")
|
18
|
+
|
19
|
+
The user will be redirected to the URL you pass and a `token` parameter will be sent along. Capture
|
20
|
+
that token and then request the contacts:
|
21
|
+
|
22
|
+
GmailContacts::Google.new("some_token").contacts
|
23
|
+
|
24
|
+
Every `Contact` has a name and email fields:
|
25
|
+
|
26
|
+
GmailContacts::Google.new("some_token").contacts.each do |contact|
|
27
|
+
puts "#{contact.name}: #{contact.email}"
|
28
|
+
end
|
29
|
+
|
30
|
+
Usage with Rails
|
31
|
+
----
|
32
|
+
First create the authorization link in one of your views:
|
33
|
+
|
34
|
+
# app/views/invites/new.html.erb
|
35
|
+
<%= link_to "Invite your Gmail contacts", GmailContacts::Google.authentication_url("http://mysite.com/invites") %>
|
36
|
+
|
37
|
+
Then create a controller action that receives the token and fetches the contacts:
|
38
|
+
|
39
|
+
# config/routes.rb
|
40
|
+
match "/invites" => "invites#index"
|
41
|
+
|
42
|
+
# app/controllers/invites_controller.rb
|
43
|
+
class InvitesController
|
44
|
+
def index
|
45
|
+
token = params[:token]
|
46
|
+
@contacts = GmailContacts::Google.new("some_token").contacts
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Finally, iterate through the contacts in your view:
|
51
|
+
|
52
|
+
# app/views/invites/index.html.erb
|
53
|
+
<% @contacts.each do |contact| %>
|
54
|
+
<span><strong><%= contact.name %></strong>: <% contact.email %></span>
|
55
|
+
<% end %>
|
data/Rakefile
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/testtask'
|
49
|
+
Rake::TestTask.new(:test) do |test|
|
50
|
+
test.libs << 'lib' << 'test'
|
51
|
+
test.pattern = 'test/**/test_*.rb'
|
52
|
+
test.verbose = true
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Generate RCov test coverage and open in your browser"
|
56
|
+
task :coverage do
|
57
|
+
require 'rcov'
|
58
|
+
sh "rm -fr coverage"
|
59
|
+
sh "rcov test/test_*.rb"
|
60
|
+
sh "open coverage/index.html"
|
61
|
+
end
|
62
|
+
|
63
|
+
require 'rdoc/task'
|
64
|
+
Rake::RDocTask.new do |rdoc|
|
65
|
+
rdoc.rdoc_dir = 'rdoc'
|
66
|
+
rdoc.title = "#{name} #{version}"
|
67
|
+
rdoc.rdoc_files.include('README*')
|
68
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Open an irb session preloaded with this library"
|
72
|
+
task :console do
|
73
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
74
|
+
end
|
75
|
+
|
76
|
+
#############################################################################
|
77
|
+
#
|
78
|
+
# Custom tasks (add your own tasks here)
|
79
|
+
#
|
80
|
+
#############################################################################
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
#############################################################################
|
85
|
+
#
|
86
|
+
# Packaging tasks
|
87
|
+
#
|
88
|
+
#############################################################################
|
89
|
+
|
90
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
91
|
+
task :release => :build do
|
92
|
+
unless `git branch` =~ /^\* master$/
|
93
|
+
puts "You must be on the master branch to release!"
|
94
|
+
exit!
|
95
|
+
end
|
96
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
97
|
+
sh "git tag v#{version}"
|
98
|
+
sh "git push origin master"
|
99
|
+
sh "git push origin v#{version}"
|
100
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "Build #{gem_file} into the pkg directory"
|
104
|
+
task :build => :gemspec do
|
105
|
+
sh "mkdir -p pkg"
|
106
|
+
sh "gem build #{gemspec_file}"
|
107
|
+
sh "mv #{gem_file} pkg"
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "Generate #{gemspec_file}"
|
111
|
+
task :gemspec => :validate do
|
112
|
+
# read spec file and split out manifest section
|
113
|
+
spec = File.read(gemspec_file)
|
114
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
115
|
+
|
116
|
+
# replace name version and date
|
117
|
+
replace_header(head, :name)
|
118
|
+
replace_header(head, :version)
|
119
|
+
replace_header(head, :date)
|
120
|
+
#comment this out if your rubyforge_project has a different name
|
121
|
+
replace_header(head, :rubyforge_project)
|
122
|
+
|
123
|
+
# determine file list from git ls-files
|
124
|
+
files = `git ls-files`.
|
125
|
+
split("\n").
|
126
|
+
sort.
|
127
|
+
reject { |file| file =~ /^\./ }.
|
128
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
129
|
+
map { |file| " #{file}" }.
|
130
|
+
join("\n")
|
131
|
+
|
132
|
+
# piece file back together and write
|
133
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
134
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
135
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
136
|
+
puts "Updated #{gemspec_file}"
|
137
|
+
end
|
138
|
+
|
139
|
+
desc "Validate #{gemspec_file}"
|
140
|
+
task :validate do
|
141
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
142
|
+
unless libfiles.empty?
|
143
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
144
|
+
exit!
|
145
|
+
end
|
146
|
+
unless Dir['VERSION*'].empty?
|
147
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
148
|
+
exit!
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
s.rubygems_version = '1.3.5'
|
11
|
+
|
12
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
13
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
14
|
+
## the sub! line in the Rakefile
|
15
|
+
s.name = 'gmail-contacts'
|
16
|
+
s.version = '0.0.4'
|
17
|
+
s.date = '2012-06-19'
|
18
|
+
s.rubyforge_project = 'gmail-contacts'
|
19
|
+
|
20
|
+
## Make sure your summary is short. The description may be as long
|
21
|
+
## as you like.
|
22
|
+
s.summary = "A Ruby library for fetching Gmail account contacts"
|
23
|
+
s.description = "A Ruby library for fetching Gmail account contacts"
|
24
|
+
|
25
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
26
|
+
## better to set the email to an email list or something. If you don't have
|
27
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
28
|
+
s.authors = ["Federico Builes"]
|
29
|
+
s.email = 'federico.builes@gmail.com'
|
30
|
+
s.homepage = 'https://github.com/febuiles/gmail-contacts'
|
31
|
+
|
32
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
33
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
34
|
+
s.require_paths = %w[lib]
|
35
|
+
|
36
|
+
## List your runtime dependencies here. Runtime dependencies are those
|
37
|
+
## that are needed for an end user to actually USE your code.
|
38
|
+
s.add_dependency("hpricot")
|
39
|
+
|
40
|
+
## Leave this section as-is. It will be automatically generated from the
|
41
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
42
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
43
|
+
# = MANIFEST =
|
44
|
+
s.files = %w[
|
45
|
+
README.md
|
46
|
+
Rakefile
|
47
|
+
gmail-contacts.gemspec
|
48
|
+
lib/gmail-contacts.rb
|
49
|
+
lib/gmail-contacts/google.rb
|
50
|
+
]
|
51
|
+
# = MANIFEST =
|
52
|
+
|
53
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
54
|
+
## matches what you actually use.
|
55
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
56
|
+
end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require 'gmail_contacts'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hpricot'
|
5
|
+
require 'cgi'
|
6
|
+
require 'time'
|
7
|
+
require 'zlib'
|
8
|
+
require 'stringio'
|
9
|
+
require 'net/http'
|
10
|
+
require 'net/https'
|
11
|
+
|
12
|
+
module GmailContacts
|
13
|
+
# == Fetching Google Contacts
|
14
|
+
#
|
15
|
+
# First, get the user to follow the following URL:
|
16
|
+
#
|
17
|
+
# GmailContacts::Google.authentication_url('http://mysite.com/invite')
|
18
|
+
#
|
19
|
+
# After he authenticates successfully to Google, it will redirect him back to the target URL
|
20
|
+
# (specified as argument above) and provide the token GET parameter. Use it to create a
|
21
|
+
# new instance of this class and request the contact list:
|
22
|
+
#
|
23
|
+
# gmail = GmailContacts::Google.new(params[:token])
|
24
|
+
# gmail.contacts
|
25
|
+
# # => [#<Contact 1>, #<Contact 2>, ...]
|
26
|
+
#
|
27
|
+
# == Storing a session token
|
28
|
+
#
|
29
|
+
# The basic token that you will get after the user has authenticated on Google is valid
|
30
|
+
# for <b>only one request</b>. However, you can specify that you want a session token which
|
31
|
+
# doesn't expire:
|
32
|
+
#
|
33
|
+
# GmailContacts::Google.authentication_url('http://mysite.com/invite', :session => true)
|
34
|
+
#
|
35
|
+
# When the user authenticates, he will be redirected back with a token that can be exchanged
|
36
|
+
# for a session token with the following method:
|
37
|
+
#
|
38
|
+
# token = GmailContacts::Google.sesion_token(params[:token])
|
39
|
+
#
|
40
|
+
# Now you have a permanent token. Store it with other user data so you can query the API
|
41
|
+
# on his behalf without him having to authenticate on Google each time.
|
42
|
+
class Google
|
43
|
+
DOMAIN = 'www.google.com'
|
44
|
+
AuthSubPath = '/accounts/AuthSub' # all variants go over HTTPS
|
45
|
+
ClientLogin = '/accounts/ClientLogin'
|
46
|
+
FeedsPath = '/m8/feeds/contacts/'
|
47
|
+
|
48
|
+
# default options for #authentication_url
|
49
|
+
def self.authentication_url_options
|
50
|
+
@authentication_url_options ||= {
|
51
|
+
:scope => "http://#{DOMAIN}#{FeedsPath}",
|
52
|
+
:secure => false,
|
53
|
+
:session => false
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
# default options for #client_login
|
58
|
+
def self.client_login_options
|
59
|
+
@client_login_options ||= {
|
60
|
+
:accountType => 'GOOGLE',
|
61
|
+
:service => 'cp',
|
62
|
+
:source => 'Contacts-Ruby'
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# URL to Google site where user authenticates. Afterwards, Google redirects to your
|
67
|
+
# site with the URL specified as +target+.
|
68
|
+
#
|
69
|
+
# Options are:
|
70
|
+
# * <tt>:scope</tt> -- the AuthSub scope in which the resulting token is valid
|
71
|
+
# (default: "http://www.google.com/m8/feeds/contacts/")
|
72
|
+
# * <tt>:secure</tt> -- boolean indicating whether the token will be secure. Only available
|
73
|
+
# for registered domains.
|
74
|
+
# (default: false)
|
75
|
+
# * <tt>:session</tt> -- boolean indicating if the token can be exchanged for a session token
|
76
|
+
# (default: false)
|
77
|
+
def self.authentication_url(target, options = {})
|
78
|
+
params = authentication_url_options.merge(options)
|
79
|
+
params[:next] = target
|
80
|
+
query = query_string(params)
|
81
|
+
"https://#{DOMAIN}#{AuthSubPath}Request?#{query}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Makes an HTTPS request to exchange the given token with a session one. Session
|
85
|
+
# tokens never expire, so you can store them in the database alongside user info.
|
86
|
+
#
|
87
|
+
# Returns the new token as string or nil if the parameter couldn't be found in response
|
88
|
+
# body.
|
89
|
+
def self.session_token(token)
|
90
|
+
response = http_start do |google|
|
91
|
+
google.get(AuthSubPath + 'SessionToken', authorization_header(token))
|
92
|
+
end
|
93
|
+
|
94
|
+
pair = response.body.split(/\n/).detect { |p| p.index('Token=') == 0 }
|
95
|
+
pair.split('=').last if pair
|
96
|
+
end
|
97
|
+
|
98
|
+
# Alternative to AuthSub: using email and password.
|
99
|
+
def self.client_login(email, password)
|
100
|
+
response = http_start do |google|
|
101
|
+
query = query_string(client_login_options.merge(:Email => email, :Passwd => password))
|
102
|
+
puts "posting #{query} to #{ClientLogin}" if GmailContacts::verbose?
|
103
|
+
google.post(ClientLogin, query)
|
104
|
+
end
|
105
|
+
|
106
|
+
pair = response.body.split(/\n/).detect { |p| p.index('Auth=') == 0 }
|
107
|
+
pair.split('=').last if pair
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_reader :user, :token, :headers
|
111
|
+
attr_accessor :projection
|
112
|
+
|
113
|
+
# A token is required here. By default, an AuthSub token from
|
114
|
+
# Google is one-time only, which means you can only make a single request with it.
|
115
|
+
def initialize(token, user_id = 'default', client = false)
|
116
|
+
@user = user_id.to_s
|
117
|
+
@token = token.to_s
|
118
|
+
@headers = {
|
119
|
+
'Accept-Encoding' => 'gzip',
|
120
|
+
'User-Agent' => Identifier + ' (gzip)'
|
121
|
+
}.update(self.class.authorization_header(@token, client))
|
122
|
+
@projection = 'thin'
|
123
|
+
end
|
124
|
+
|
125
|
+
def get(params) # :nodoc:
|
126
|
+
self.class.http_start(false) do |google|
|
127
|
+
path = FeedsPath + CGI.escape(@user)
|
128
|
+
google_params = translate_parameters(params)
|
129
|
+
query = self.class.query_string(google_params)
|
130
|
+
google.get("#{path}/#{@projection}?#{query}", @headers)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Timestamp of last update. This value is available only after the XML
|
135
|
+
# document has been parsed; for instance after fetching the contact list.
|
136
|
+
def updated_at
|
137
|
+
@updated_at ||= Time.parse @updated_string if @updated_string
|
138
|
+
end
|
139
|
+
|
140
|
+
# Timestamp of last update as it appeared in the XML document
|
141
|
+
def updated_at_string
|
142
|
+
@updated_string
|
143
|
+
end
|
144
|
+
|
145
|
+
# Fetches, parses and returns the contact list.
|
146
|
+
#
|
147
|
+
# ==== Options
|
148
|
+
# * <tt>:limit</tt> -- use a large number to fetch a bigger contact list (default: 200)
|
149
|
+
# * <tt>:offset</tt> -- 0-based value, can be used for pagination
|
150
|
+
# * <tt>:order</tt> -- currently the only value support by Google is "lastmodified"
|
151
|
+
# * <tt>:descending</tt> -- boolean
|
152
|
+
# * <tt>:updated_after</tt> -- string or time-like object, use to only fetch contacts
|
153
|
+
# that were updated after this date
|
154
|
+
def contacts(options = {})
|
155
|
+
params = { :limit => 200 }.update(options)
|
156
|
+
response = get(params)
|
157
|
+
parse_contacts response_body(response)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Fetches contacts using multiple API calls when necessary
|
161
|
+
def all_contacts(options = {}, chunk_size = 200)
|
162
|
+
in_chunks(options, :contacts, chunk_size)
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
167
|
+
def in_chunks(options, what, chunk_size)
|
168
|
+
returns = []
|
169
|
+
offset = 0
|
170
|
+
|
171
|
+
begin
|
172
|
+
chunk = send(what, options.merge(:offset => offset, :limit => chunk_size))
|
173
|
+
returns.push(*chunk)
|
174
|
+
offset += chunk_size
|
175
|
+
end while chunk.size == chunk_size
|
176
|
+
|
177
|
+
returns
|
178
|
+
end
|
179
|
+
|
180
|
+
def response_body(response)
|
181
|
+
unless response['Content-Encoding'] == 'gzip'
|
182
|
+
response.body
|
183
|
+
else
|
184
|
+
gzipped = StringIO.new(response.body)
|
185
|
+
Zlib::GzipReader.new(gzipped).read
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def parse_contacts(body)
|
190
|
+
doc = Hpricot::XML body
|
191
|
+
contacts_found = []
|
192
|
+
|
193
|
+
if updated_node = doc.at('/feed/updated')
|
194
|
+
@updated_string = updated_node.inner_text
|
195
|
+
end
|
196
|
+
|
197
|
+
(doc / '/feed/entry').each do |entry|
|
198
|
+
email_nodes = entry / 'gd:email[@address]'
|
199
|
+
|
200
|
+
unless email_nodes.empty?
|
201
|
+
title_node = entry.at('/title')
|
202
|
+
name = title_node ? title_node.inner_text : nil
|
203
|
+
contact = Contact.new(nil, name)
|
204
|
+
contact.emails.concat email_nodes.map { |e| e['address'].to_s }
|
205
|
+
contacts_found << contact
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
contacts_found
|
210
|
+
end
|
211
|
+
|
212
|
+
# Constructs a query string from a Hash object
|
213
|
+
def self.query_string(params)
|
214
|
+
params.inject([]) do |all, pair|
|
215
|
+
key, value = pair
|
216
|
+
unless value.nil?
|
217
|
+
value = case value
|
218
|
+
when TrueClass; '1'
|
219
|
+
when FalseClass; '0'
|
220
|
+
else value
|
221
|
+
end
|
222
|
+
|
223
|
+
all << "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
224
|
+
end
|
225
|
+
all
|
226
|
+
end.join('&')
|
227
|
+
end
|
228
|
+
|
229
|
+
def translate_parameters(params)
|
230
|
+
params.inject({}) do |all, pair|
|
231
|
+
key, value = pair
|
232
|
+
unless value.nil?
|
233
|
+
key = case key
|
234
|
+
when :limit
|
235
|
+
'max-results'
|
236
|
+
when :offset
|
237
|
+
value = value.to_i + 1
|
238
|
+
'start-index'
|
239
|
+
when :order
|
240
|
+
all['sortorder'] = 'descending' if params[:descending].nil?
|
241
|
+
'orderby'
|
242
|
+
when :descending
|
243
|
+
value = value ? 'descending' : 'ascending'
|
244
|
+
'sortorder'
|
245
|
+
when :updated_after
|
246
|
+
value = value.strftime("%Y-%m-%dT%H:%M:%S%Z") if value.respond_to? :strftime
|
247
|
+
'updated-min'
|
248
|
+
else key
|
249
|
+
end
|
250
|
+
|
251
|
+
all[key] = value
|
252
|
+
end
|
253
|
+
all
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.authorization_header(token, client = false)
|
258
|
+
type = client ? 'GoogleLogin auth' : 'AuthSub token'
|
259
|
+
{ 'Authorization' => %(#{type}="#{token}") }
|
260
|
+
end
|
261
|
+
|
262
|
+
def self.http_start(ssl = true)
|
263
|
+
port = ssl ? Net::HTTP::https_default_port : Net::HTTP::http_default_port
|
264
|
+
http = Net::HTTP.new(DOMAIN, port)
|
265
|
+
redirects = 0
|
266
|
+
if ssl
|
267
|
+
http.use_ssl = true
|
268
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
269
|
+
end
|
270
|
+
http.start
|
271
|
+
|
272
|
+
begin
|
273
|
+
response = yield(http)
|
274
|
+
|
275
|
+
loop do
|
276
|
+
inspect_response(response) if GmailContacts::verbose?
|
277
|
+
|
278
|
+
case response
|
279
|
+
when Net::HTTPSuccess
|
280
|
+
break response
|
281
|
+
when Net::HTTPRedirection
|
282
|
+
if redirects == TooManyRedirects::MAX_REDIRECTS
|
283
|
+
raise TooManyRedirects.new(response)
|
284
|
+
end
|
285
|
+
location = URI.parse response['Location']
|
286
|
+
puts "Redirected to #{location}"
|
287
|
+
response = http.get(location.path)
|
288
|
+
redirects += 1
|
289
|
+
else
|
290
|
+
response.error!
|
291
|
+
end
|
292
|
+
end
|
293
|
+
ensure
|
294
|
+
http.finish
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def self.inspect_response(response, out = $stderr)
|
299
|
+
out.puts response.inspect
|
300
|
+
for name, value in response
|
301
|
+
out.puts "#{name}: #{value}"
|
302
|
+
end
|
303
|
+
out.puts "----\n#{ response}\n----" unless response.body.empty?
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'gmail_contacts/google'
|
2
|
+
|
3
|
+
module GmailContacts
|
4
|
+
|
5
|
+
VERSION = "0.0.4"
|
6
|
+
|
7
|
+
Identifier = 'GmailContacts v' + VERSION
|
8
|
+
|
9
|
+
# An object that represents a single contact
|
10
|
+
class Contact
|
11
|
+
attr_reader :name, :username, :emails
|
12
|
+
|
13
|
+
def initialize(email, name = nil, username = nil)
|
14
|
+
@emails = []
|
15
|
+
@emails << email if email
|
16
|
+
@name = name
|
17
|
+
@username = username
|
18
|
+
end
|
19
|
+
|
20
|
+
def email
|
21
|
+
@emails.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
%!#<GmailContacts::Contact "#{name}"#{email ? " (#{email})" : ''}>!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.verbose?
|
30
|
+
'irb' == $0
|
31
|
+
end
|
32
|
+
|
33
|
+
class Error < StandardError
|
34
|
+
end
|
35
|
+
|
36
|
+
class TooManyRedirects < Error
|
37
|
+
attr_reader :response, :location
|
38
|
+
|
39
|
+
MAX_REDIRECTS = 2
|
40
|
+
|
41
|
+
def initialize(response)
|
42
|
+
@response = response
|
43
|
+
@location = @response['Location']
|
44
|
+
super "exceeded maximum of #{MAX_REDIRECTS} redirects (Location: #{location})"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gmail-contacts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Federico Builes
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: hpricot
|
16
|
+
requirement: &70217158537480 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70217158537480
|
25
|
+
description: A Ruby library for fetching Gmail account contacts
|
26
|
+
email: federico.builes@gmail.com
|
27
|
+
executables: []
|
28
|
+
extensions: []
|
29
|
+
extra_rdoc_files: []
|
30
|
+
files:
|
31
|
+
- README.md
|
32
|
+
- Rakefile
|
33
|
+
- gmail-contacts.gemspec
|
34
|
+
- lib/gmail-contacts.rb
|
35
|
+
- lib/gmail-contacts/google.rb
|
36
|
+
homepage: https://github.com/febuiles/gmail-contacts
|
37
|
+
licenses: []
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project: gmail-contacts
|
56
|
+
rubygems_version: 1.8.10
|
57
|
+
signing_key:
|
58
|
+
specification_version: 2
|
59
|
+
summary: A Ruby library for fetching Gmail account contacts
|
60
|
+
test_files: []
|