im_contacts 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,61 @@
1
+ == Welcome to im_contacts
2
+
3
+ Contacts_cn is a universal interface to grab contact list information from various providers including Hotmail, AOL, Gmail, Plaxo, 126, 163, Yeah, Sina, Sohu and Yahoo.It is extended from contacts gem.
4
+
5
+ == Download
6
+
7
+ * gem install im_contacts
8
+ * http://github.com/liangwenke/im_contacts
9
+ * git clone git://github.com/liangwenke/im_contacts.git
10
+
11
+ == Background
12
+
13
+ For a long time, the only way to get a list of contacts from your free online email accounts was with proprietary PHP scripts that would cost you $50. The act of grabbing that list is a simple matter of screen scrapping and this library gives you all the functionality you need. Thanks to the generosity of the highly popular Rails website MOG (http://mog.com) for allowing this library to be released open-source to the world. It is easy to extend this library to add new free email providers, so please contact the author if you would like to help.
14
+
15
+ == Usage
16
+
17
+ Contacts::Hotmail.new(login, password).contacts # => [["name", "foo@bar.com"], ["another name", "bow@wow.com"]]
18
+ Contacts::Yahoo.new(login, password).contacts
19
+ Contacts::Gmail.new(login, password).contacts
20
+ Contacts::NetEase.new(login,password).contacts
21
+ Contacts::Sina.new(login,password).contacts
22
+
23
+ Contacts.new(:gmail, login, password).contacts
24
+ Contacts.new(:hotmail, login, password).contacts
25
+ Contacts.new(:yahoo, login, password).contacts
26
+ Contacts.new(:net_ease,login,password).contacts
27
+ Contacts.new(:sina,login,password).contacts
28
+
29
+ Contacts.guess(login, password).contacts
30
+
31
+ Notice there are three ways to use this library so that you can limit the use as much as you would like in your particular application. The Contacts.guess method will automatically concatenate all the address book contacts from each of the successful logins in the case that a username password works across multiple services.
32
+
33
+ == Captcha error
34
+
35
+ If there are too many failed attempts with the gmail login info, Google will raise a captcha response. To integrate the captcha handling, pass in the token and response via:
36
+
37
+ Contacts::Gmail.new(login, password, :captcha_token => params[:captcha_token], :captcha_response => params[:captcha_response]).contacts
38
+
39
+ == Examples
40
+
41
+ See the examples/ directory.
42
+
43
+ == Authors
44
+
45
+ * Lucas Carlson from MOG (mailto:lucas@rufy.com) - http://mog.com
46
+
47
+ == Contributors
48
+
49
+ * Britt Selvitelle from Twitter (mailto:anotherbritt@gmail.com) - http://twitter.com
50
+ * Tony Targonski from GigPark (mailto:tony@gigpark.com) - http://gigpark.com
51
+ * Waheed Barghouthi from Watwet (mailto:waheed.barghouthi@gmail.com) - http://watwet.com
52
+ * Glenn Sidney from Glenn Fu (mailto:glenn@glennfu.com) - http://glennfu.com
53
+ * Brian McQuay from Onomojo (mailto:brian@onomojo.com) - http://onomojo.com
54
+ * Adam Hunter (mailto:adamhunter@me.com) - http://adamhunter.me/
55
+ * Glenn Ford (mailto:glenn@glennfu.com) - http://www.glennfu.com/
56
+ * Leonardo Wong (mailto:mac@boy.name)
57
+ * Rusty Burchfield
58
+ * justintv
59
+ * kame
60
+
61
+ This library is released under the terms of the BSD.
data/Rakefile ADDED
@@ -0,0 +1,91 @@
1
+ # require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ # require 'rake/contrib/rubyforgepublisher'
7
+ require 'lib/im_contacts'
8
+
9
+ PKG_VERSION = Contacts::VERSION
10
+
11
+ PKG_FILES = FileList[
12
+ "lib/**/*", "bin/*", "test/**/*", "[A-Z]*", "Rakefile", "doc/**/*", "examples/**/*"
13
+ ] - ["test/accounts.yml"]
14
+
15
+ desc "Default Task"
16
+ task :default => [ :test ]
17
+
18
+ # Run the unit tests
19
+ desc "Run all unit tests"
20
+ Rake::TestTask.new("test") { |t|
21
+ t.libs << "lib"
22
+ t.pattern = 'test/*/*_test.rb'
23
+ t.verbose = true
24
+ }
25
+
26
+ # Make a console, useful when working on tests
27
+ desc "Generate a test console"
28
+ task :console do
29
+ verbose( false ) { sh "irb -I lib/ -r 'contacts'" }
30
+ end
31
+
32
+ # Genereate the RDoc documentation
33
+ desc "Create documentation"
34
+ Rake::RDocTask.new("doc") { |rdoc|
35
+ rdoc.title = "Contact List - ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail"
36
+ rdoc.rdoc_dir = 'doc'
37
+ rdoc.rdoc_files.include('README')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ }
40
+
41
+ # Genereate the package
42
+ spec = Gem::Specification.new do |s|
43
+
44
+ #### Basic information.
45
+
46
+ s.name = 'im_contacts'
47
+ s.version = PKG_VERSION
48
+ s.summary = <<-EOF
49
+ A universal interface to grab contact list information from various providers including Yahoo, AOL, Gmail, Hotmail, 126, 163, Yeah, Sohu, Sina and Plaxo.It is extended from contacts gem.
50
+ EOF
51
+ s.description = <<-EOF
52
+ A universal interface to grab contact list information from various providers including Yahoo, AOL, Gmail, Hotmail, 126, 163, Yeah, Sohu, Sina and Plaxo.It is extended from contacts gem.
53
+ EOF
54
+
55
+ #### Which files are to be included in this gem? Everything! (Except CVS directories.)
56
+
57
+ s.files = PKG_FILES
58
+
59
+ #### Load-time details: library and application (you will need one or both).
60
+
61
+ s.require_path = 'lib'
62
+ s.autorequire = 'contacts'
63
+
64
+ s.add_dependency('json', '>= 0.4.1')
65
+ s.add_dependency("gdata19", "~> 0.1.9.2")
66
+ s.requirements << "A json parser, the gdata ruby gem"
67
+
68
+ #### Documentation and testing.
69
+
70
+ s.has_rdoc = true
71
+
72
+ #### Author and project details.
73
+
74
+ s.authors = ["Mike Liang"]
75
+ s.email = ["liangwenke.com@gmail.com"]
76
+ s.homepage = "http://github.com/liangwenke/im_contacts"
77
+ end
78
+
79
+ Rake::GemPackageTask.new(spec) do |pkg|
80
+ pkg.need_zip = true
81
+ pkg.need_tar = true
82
+ end
83
+
84
+ desc "Report code statistics (KLOCs, etc) from the application"
85
+ task :stats do
86
+ require 'code_statistics'
87
+ CodeStatistics.new(
88
+ ["Library", "lib"],
89
+ ["Units", "test"]
90
+ ).to_s
91
+ end
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__)+"/../lib/im_contacts"
2
+
3
+ login = ARGV[0]
4
+ password = ARGV[1]
5
+ #login is your email
6
+ Contacts::Gmail.new(login, password).contacts
7
+
8
+ Contacts.new(:gmail, login, password).contacts
9
+
10
+ Contacts.new("gmail", login, password).contacts
11
+
12
+ Contacts.guess(login, password).contacts
13
+
14
+ Contacts.new(:yahoo,login,password).contacts
15
+
16
+ Contacts.new(:hotmail,login,password).contacts
17
+
18
+ #net_ease support 163.com, 126.com, yeah.net
19
+ Contacts.new(:net_ease,login,password).contacts
20
+
21
+ Contacts.new(:sina,login,password).contacts
22
+
23
+ Contacts.new(:sohu,login,password).contacts
@@ -0,0 +1,154 @@
1
+ class Contacts
2
+ require 'hpricot'
3
+ require 'csv'
4
+ class Aol < Base
5
+ URL = "http://www.aol.com/"
6
+ LOGIN_URL = "https://my.screenname.aol.com/_cqr/login/login.psp"
7
+ LOGIN_REFERER_URL = "http://webmail.aol.com/"
8
+ LOGIN_REFERER_PATH = "sitedomain=sns.webmail.aol.com&lang=en&locale=us&authLev=0&uitype=mini&loginId=&redirType=js&xchk=false"
9
+ AOL_NUM = "29970-343" # this seems to change each time they change the protocol
10
+
11
+ CONTACT_LIST_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ContactList.aspx?folder=Inbox&showUserFolders=False"
12
+ CONTACT_LIST_CSV_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ABExport.aspx?command=all"
13
+ PROTOCOL_ERROR = "AOL has changed its protocols, please upgrade this library first. If that does not work, dive into the code and submit a patch at http://github.com/cardmagic/contacts"
14
+
15
+ def real_connect
16
+ if login.strip =~ /^(.+)@aol\.com$/ # strip off the @aol.com for AOL logins
17
+ login = $1
18
+ end
19
+
20
+ postdata = {
21
+ "loginId" => login,
22
+ "password" => password,
23
+ "rememberMe" => "on",
24
+ "_sns_fg_color_" => "",
25
+ "_sns_err_color_" => "",
26
+ "_sns_link_color_" => "",
27
+ "_sns_width_" => "",
28
+ "_sns_height_" => "",
29
+ "offerId" => "mail-second-en-us",
30
+ "_sns_bg_color_" => "",
31
+ "sitedomain" => "sns.webmail.aol.com",
32
+ "regPromoCode" => "",
33
+ "mcState" => "initialized",
34
+ "uitype" => "std",
35
+ "siteId" => "",
36
+ "lang" => "en",
37
+ "locale" => "us",
38
+ "authLev" => "0",
39
+ "siteState" => "",
40
+ "isSiteStateEncoded" => "false",
41
+ "use_aam" => "0",
42
+ "seamless" => "novl",
43
+ "aolsubmit" => CGI.escape("Sign In"),
44
+ "idType" => "SN",
45
+ "usrd" => "",
46
+ "doSSL" => "",
47
+ "redirType" => "",
48
+ "xchk" => "false"
49
+ }
50
+
51
+ # Get this cookie and stick it in the form to confirm to Aol that your cookies work
52
+ data, resp, cookies, forward = get(URL)
53
+ postdata["stips"] = cookie_hash_from_string(cookies)["stips"]
54
+ postdata["tst"] = cookie_hash_from_string(cookies)["tst"]
55
+
56
+ data, resp, cookies, forward, old_url = get(LOGIN_REFERER_URL, cookies) + [URL]
57
+ until forward.nil?
58
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
59
+ end
60
+
61
+ data, resp, cookies, forward, old_url = get("#{LOGIN_URL}?#{LOGIN_REFERER_PATH}", cookies) + [LOGIN_REFERER_URL]
62
+ until forward.nil?
63
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
64
+ end
65
+
66
+ doc = Hpricot(data)
67
+ (doc/:input).each do |input|
68
+ postdata["usrd"] = input.attributes["value"] if input.attributes["name"] == "usrd"
69
+ end
70
+ # parse data for <input name="usrd" value="2726212" type="hidden"> and add it to the postdata
71
+
72
+ postdata["SNS_SC"] = cookie_hash_from_string(cookies)["SNS_SC"]
73
+ postdata["SNS_LDC"] = cookie_hash_from_string(cookies)["SNS_LDC"]
74
+ postdata["LTState"] = cookie_hash_from_string(cookies)["LTState"]
75
+ # raise data.inspect
76
+
77
+ data, resp, cookies, forward, old_url = post(LOGIN_URL, h_to_query_string(postdata), cookies, LOGIN_REFERER_URL) + [LOGIN_REFERER_URL]
78
+
79
+ until forward.nil?
80
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
81
+ end
82
+
83
+ if data.index("Invalid Username or Password. Please try again.")
84
+ raise AuthenticationError, "Username and password do not match"
85
+ elsif data.index("Required field must not be blank")
86
+ raise AuthenticationError, "Login and password must not be blank"
87
+ elsif data.index("errormsg_0_logincaptcha")
88
+ raise AuthenticationError, "Captcha error"
89
+ elsif data.index("Invalid request")
90
+ raise ConnectionError, PROTOCOL_ERROR
91
+ elsif cookies == ""
92
+ raise ConnectionError, PROTOCOL_ERROR
93
+ end
94
+
95
+ @cookies = cookies
96
+ end
97
+
98
+ def contacts
99
+ postdata = {
100
+ "file" => 'contacts',
101
+ "fileType" => 'csv'
102
+ }
103
+
104
+ return @contacts if @contacts
105
+ if connected?
106
+ data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
107
+
108
+ until forward.nil?
109
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
110
+ end
111
+
112
+ if resp.code_type != Net::HTTPOK
113
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
114
+ end
115
+
116
+ # parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
117
+ doc = Hpricot(data)
118
+ (doc/:input).each do |input|
119
+ postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
120
+ end
121
+
122
+ data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
123
+
124
+ until forward.nil?
125
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
126
+ end
127
+
128
+ if data.include?("error.gif")
129
+ raise AuthenticationError, "Account invalid"
130
+ end
131
+
132
+ parse data
133
+ end
134
+ end
135
+ private
136
+
137
+ def parse(data, options={})
138
+ data = CSV::Reader.parse(data)
139
+ col_names = data.shift
140
+ @contacts = data.map do |person|
141
+ ["#{person[0]} #{person[1]}", person[4]] if person[4] && !person[4].empty?
142
+ end.compact
143
+ end
144
+
145
+ def h_to_query_string(hash)
146
+ u = ERB::Util.method(:u)
147
+ hash.map { |k, v|
148
+ u.call(k) + "=" + u.call(v)
149
+ }.join("&")
150
+ end
151
+ end
152
+
153
+ TYPES[:aol] = Aol
154
+ end
@@ -0,0 +1,229 @@
1
+ require "cgi"
2
+ require "net/http"
3
+ require "net/https"
4
+ require "uri"
5
+ require "zlib"
6
+ require "stringio"
7
+ require "thread"
8
+ require "erb"
9
+
10
+ class Contacts
11
+ TYPES = {}
12
+ VERSION = "1.2.7"
13
+
14
+ class Base
15
+ def initialize(login, password, options={})
16
+ @login = login
17
+ @password = password
18
+ @captcha_token = options[:captcha_token]
19
+ @captcha_response = options[:captcha_response]
20
+ @connections = {}
21
+ connect
22
+ end
23
+
24
+ def connect
25
+ raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @login.empty? || @password.nil? || @password.empty?
26
+ real_connect
27
+ end
28
+
29
+ def connected?
30
+ @cookies && !@cookies.empty?
31
+ end
32
+
33
+ def contacts(options = {})
34
+ return @contacts if @contacts
35
+ if connected?
36
+ url = URI.parse(contact_list_url)
37
+ http = open_http(url)
38
+ resp, data = http.get("#{url.path}?#{url.query}",
39
+ "Cookie" => @cookies
40
+ )
41
+
42
+ if resp.code_type != Net::HTTPOK
43
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
44
+ end
45
+
46
+ parse(data, options)
47
+ end
48
+ end
49
+
50
+ def login
51
+ @attempt ||= 0
52
+ @attempt += 1
53
+
54
+ if @attempt == 1
55
+ @login
56
+ else
57
+ if @login.include?("@#{domain}")
58
+ @login.sub("@#{domain}","")
59
+ else
60
+ "#{@login}@#{domain}"
61
+ end
62
+ end
63
+ end
64
+
65
+ def login_with_domain
66
+ @login.include?("@#{domain}") ? "#{@login}" : "#{@login}@#{domain}"
67
+ end
68
+
69
+ def login_without_domain
70
+ @login.include?("@#{domain}") ? @login.sub("@#{domain}",'') : "#{@login}"
71
+ end
72
+
73
+ def password
74
+ @password
75
+ end
76
+
77
+ private
78
+
79
+ def domain
80
+ @d ||= self.class.const_get(:DOMAIN) rescue nil
81
+ @d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
82
+ end
83
+
84
+ def contact_list_url
85
+ self.class.const_get(:CONTACT_LIST_URL)
86
+ end
87
+
88
+ def address_book_url
89
+ self.class.const_get(:ADDRESS_BOOK_URL)
90
+ end
91
+
92
+ def open_http(url)
93
+ c = @connections[Thread.current.object_id] ||= {}
94
+ http = c["#{url.host}:#{url.port}"]
95
+ unless http
96
+ http = Net::HTTP.new(url.host, url.port)
97
+ if url.port == 443
98
+ http.use_ssl = true
99
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
100
+ end
101
+ c["#{url.host}:#{url.port}"] = http
102
+ end
103
+ http.start unless http.started?
104
+ http
105
+ end
106
+
107
+ def cookie_hash_from_string(cookie_string)
108
+ cookie_string.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
109
+ end
110
+
111
+ def parse_cookies(data, existing="")
112
+ return existing if data.nil?
113
+
114
+ cookies = cookie_hash_from_string(existing)
115
+
116
+ data.gsub!(/ ?[\w]+=EXPIRED;/,'')
117
+ data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
118
+ data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
119
+ data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
120
+ data.gsub!(/(;\s*){2,}/,', ')
121
+ data.gsub!(/(,\s*){2,}/,', ')
122
+ data.sub!(/^,\s*/,'')
123
+ data.sub!(/\s*,$/,'')
124
+
125
+ data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
126
+ k, v = data.split("=", 2).map{|j|j.strip}
127
+ if cookies[k] && v.empty?
128
+ cookies.delete(k)
129
+ elsif v && !v.empty?
130
+ cookies[k] = v
131
+ end
132
+ end
133
+
134
+ cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
135
+ end
136
+
137
+ def remove_cookie(cookie, cookies)
138
+ parse_cookies("#{cookie}=", cookies)
139
+ end
140
+
141
+ def post(url, postdata, cookies="", referer="")
142
+ url = URI.parse(url)
143
+ http = open_http(url)
144
+ resp, data = http.post(url.path, postdata,
145
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
146
+ "Accept-Encoding" => "gzip",
147
+ "Cookie" => cookies,
148
+ "Referer" => referer,
149
+ "Content-Type" => 'application/x-www-form-urlencoded'
150
+ )
151
+ data = uncompress(resp, data)
152
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
153
+ forward = resp.response['Location']
154
+ forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
155
+ if (not forward.nil?) && URI.parse(forward).host.nil?
156
+ forward = url.scheme.to_s + "://" + url.host.to_s + forward
157
+ end
158
+ return data, resp, cookies, forward
159
+ end
160
+
161
+ def get(url, cookies="", referer="")
162
+ url = URI.parse(url)
163
+ http = open_http(url)
164
+ resp, data = http.get("#{url.path}?#{url.query}",
165
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
166
+ "Accept-Encoding" => "gzip",
167
+ "Cookie" => cookies,
168
+ "Referer" => referer
169
+ )
170
+ data = uncompress(resp, data)
171
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
172
+ forward = resp.response['Location']
173
+ if (not forward.nil?) && URI.parse(forward).host.nil?
174
+ forward = url.scheme.to_s + "://" + url.host.to_s + forward
175
+ end
176
+ return data, resp, cookies, forward
177
+ end
178
+
179
+ def uncompress(resp, data)
180
+ case resp.response['content-encoding']
181
+ when 'gzip'
182
+ gz = Zlib::GzipReader.new(StringIO.new(data))
183
+ data = gz.read
184
+ gz.close
185
+ resp.response['content-encoding'] = nil
186
+ # FIXME: Not sure what Hotmail was feeding me with their 'deflate',
187
+ # but the headers definitely were not right
188
+ when 'deflate'
189
+ data = Zlib::Inflate.inflate(data)
190
+ resp.response['content-encoding'] = nil
191
+ end
192
+
193
+ data
194
+ end
195
+ end
196
+
197
+ class ContactsError < StandardError
198
+ end
199
+
200
+ class AuthenticationError < ContactsError
201
+ end
202
+
203
+ class ConnectionError < ContactsError
204
+ end
205
+
206
+ class TypeNotFound < ContactsError
207
+ end
208
+
209
+ class MailServerError < ContactsError
210
+ end
211
+
212
+ def self.new(type, login, password, options={})
213
+ if TYPES.include?(type.to_s.intern)
214
+ TYPES[type.to_s.intern].new(login, password, options)
215
+ else
216
+ raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
217
+ end
218
+ end
219
+
220
+ def self.guess(login, password, options={})
221
+ TYPES.inject([]) do |a, t|
222
+ begin
223
+ a + t[1].new(login, password, options).contacts
224
+ rescue AuthenticationError
225
+ a
226
+ end
227
+ end.uniq
228
+ end
229
+ end
@@ -0,0 +1,35 @@
1
+ require 'gdata'
2
+
3
+ class Contacts
4
+ class Gmail < Base
5
+
6
+ CONTACTS_SCOPE = 'http://www.google.com/m8/feeds/'
7
+ CONTACTS_FEED = CONTACTS_SCOPE + 'contacts/default/full/?max-results=1000'
8
+
9
+ def contacts
10
+ return @contacts if @contacts
11
+ end
12
+
13
+ def real_connect
14
+ @client = GData::Client::Contacts.new
15
+ @client.clientlogin(@login, @password, @captcha_token, @captcha_response)
16
+
17
+ feed = @client.get(CONTACTS_FEED).to_xml
18
+
19
+ @contacts = feed.elements.to_a('entry').collect do |entry|
20
+ title, email = entry.elements['title'].text, nil
21
+ entry.elements.each('gd:email') do |e|
22
+ email = e.attribute('address').value if e.attribute('primary')
23
+ end
24
+ [title, email] unless email.nil?
25
+ end
26
+ @contacts.compact!
27
+ rescue GData::Client::AuthorizationError => e
28
+ raise AuthenticationError, "Username or password are incorrect"
29
+ end
30
+
31
+ private
32
+
33
+ TYPES[:gmail] = Gmail
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ require 'erb'
2
+ class Hash
3
+ def to_query_string
4
+ u = ERB::Util.method(:u)
5
+ map { |k, v|
6
+ u.call(k) + "=" + u.call(v)
7
+ }.join("&")
8
+ end
9
+ end