yehezkielbs-contacts 1.2.11

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ Copyright (c) 2006, Lucas Carlson, MOG
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ Neither the name of the Lucas Carlson nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10
+
data/README.rdoc ADDED
@@ -0,0 +1,69 @@
1
+ = yehezkielbs-contacts
2
+
3
+ deneux-contacts is a fork of the liangzan-contacts gem. It aims to be compatible with 1.8 when liangzan-contacts is 1.9&above compatible.
4
+ Original gem: cardmagick/contacts .
5
+
6
+ == Intro
7
+
8
+ Contacts is a universal interface to grab contact list information from various providers including Hotmail, AOL, Gmail, Plaxo and Yahoo.
9
+
10
+ == Installation
11
+
12
+ $ gem install yehezkielbs-contacts
13
+
14
+ or on the *Gemfile*
15
+
16
+ gem 'yehezkielbs-contacts', '~> 1.2.11', :require => 'contacts'
17
+
18
+ == Background
19
+
20
+ 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.
21
+
22
+ == Usage
23
+
24
+ # returns [["name", "foo@bar.com"], ["another name", "bow@wow.com"]]
25
+ Contacts::Hotmail.new(login, password).contacts
26
+ Contacts::Yahoo.new(login, password).contacts
27
+ Contacts::Gmail.new(login, password).contacts
28
+
29
+ Contacts.new(:gmail, login, password).contacts
30
+ Contacts.new(:hotmail, login, password).contacts
31
+ Contacts.new(:yahoo, login, password).contacts
32
+
33
+ Contacts.guess(login, password).contacts
34
+
35
+ 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.
36
+
37
+ == Captcha error
38
+
39
+ 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:
40
+
41
+ Contacts::Gmail.new(login,
42
+ password,
43
+ :captcha_token => params[:captcha_token],
44
+ :captcha_response => params[:captcha_response]).contacts
45
+
46
+ == Examples
47
+
48
+ See the examples/ directory.
49
+
50
+ == Authors
51
+
52
+ * Lucas Carlson from MOG (mailto:lucas@rufy.com) - http://mog.com
53
+
54
+ == Contributors
55
+
56
+ * Britt Selvitelle from Twitter (mailto:anotherbritt@gmail.com) - http://twitter.com
57
+ * Tony Targonski from GigPark (mailto:tony@gigpark.com) - http://gigpark.com
58
+ * Waheed Barghouthi from Watwet (mailto:waheed.barghouthi@gmail.com) - http://watwet.com
59
+ * Glenn Sidney from Glenn Fu (mailto:glenn@glennfu.com) - http://glennfu.com
60
+ * Brian McQuay from Onomojo (mailto:brian@onomojo.com) - http://onomojo.com
61
+ * Adam Hunter (mailto:adamhunter@me.com) - http://adamhunter.me/
62
+ * Glenn Ford (mailto:glenn@glennfu.com) - http://www.glennfu.com/
63
+ * Leonardo Wong (mailto:mac@boy.name)
64
+ * Rusty Burchfield
65
+ * justintv
66
+ * Wong Liang Zan (mailto:zan@liangzan.net) - http://liangzan.net
67
+
68
+ This library is released under the terms of the BSD.
69
+
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ def gemspec
6
+ @gemspec ||= begin
7
+ file = File.expand_path("../yehezkielbs-contacts.gemspec", __FILE__)
8
+ eval(File.read(file), binding, file)
9
+ end
10
+ end
11
+
12
+ # Run the unit tests
13
+ desc "Run all unit tests"
14
+ Rake::TestTask.new do |t|
15
+ t.libs << "lib"
16
+ t.test_files = FileList['test/test*.rb']
17
+ t.verbose = true
18
+ end
19
+
20
+ require 'rdoc/task'
21
+ Rake::RDocTask.new
22
+
23
+ begin
24
+ require 'rubygems/package_task'
25
+ Gem::PackageTask.new(gemspec) do |pkg|
26
+ pkg.gem_spec = gemspec
27
+ end
28
+ task :gem => :gemspec
29
+ rescue LoadError
30
+ task(:gem){abort "`gem install rake` to package gems"}
31
+ end
32
+
33
+ desc "Install the gem locally"
34
+ task :install => :gem do
35
+ sh "gem install pkg/#{gemspec.full_name}.gem"
36
+ end
37
+
38
+ desc "Validate the gemspec"
39
+ task :gemspec do
40
+ gemspec.validate
41
+ end
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__)+"/../lib/contacts"
2
+
3
+ login = ARGV[0]
4
+ password = ARGV[1]
5
+
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
@@ -0,0 +1,155 @@
1
+ class Contacts
2
+ require 'fastercsv'
3
+ require 'hpricot' if RUBY_VERSION < '1.9'
4
+ require 'csv'
5
+ class Aol < Base
6
+ URL = "http://www.aol.com/"
7
+ LOGIN_URL = "https://my.screenname.aol.com/_cqr/login/login.psp"
8
+ LOGIN_REFERER_URL = "http://webmail.aol.com/"
9
+ LOGIN_REFERER_PATH = "sitedomain=sns.webmail.aol.com&lang=en&locale=us&authLev=0&uitype=mini&loginId=&redirType=js&xchk=false"
10
+ AOL_NUM = "35752-111" # this seems to change each time they change the protocol
11
+
12
+ CONTACT_LIST_URL = "http://mail.aol.com/#{AOL_NUM}/aol-6/en-us/Lite/ContactList.aspx"
13
+ CONTACT_LIST_CSV_URL = "http://mail.aol.com/#{AOL_NUM}/aol-6/en-us/Lite/ABExport.aspx?command=all"
14
+ 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"
15
+
16
+ def real_connect
17
+ if login.strip =~ /^(.+)@aol\.com$/ # strip off the @aol.com for AOL logins
18
+ login = $1
19
+ end
20
+
21
+ postdata = {
22
+ "loginId" => login,
23
+ "password" => password,
24
+ "rememberMe" => "on",
25
+ "_sns_fg_color_" => "",
26
+ "_sns_err_color_" => "",
27
+ "_sns_link_color_" => "",
28
+ "_sns_width_" => "",
29
+ "_sns_height_" => "",
30
+ "offerId" => "mail-second-en-us",
31
+ "_sns_bg_color_" => "",
32
+ "sitedomain" => "sns.webmail.aol.com",
33
+ "regPromoCode" => "",
34
+ "mcState" => "initialized",
35
+ "uitype" => "std",
36
+ "siteId" => "",
37
+ "lang" => "en",
38
+ "locale" => "us",
39
+ "authLev" => "0",
40
+ "siteState" => "",
41
+ "isSiteStateEncoded" => "false",
42
+ "use_aam" => "0",
43
+ "seamless" => "novl",
44
+ "aolsubmit" => CGI.escape("Sign In"),
45
+ "idType" => "SN",
46
+ "usrd" => "",
47
+ "doSSL" => "",
48
+ "redirType" => "",
49
+ "xchk" => "false"
50
+ }
51
+
52
+ # Get this cookie and stick it in the form to confirm to Aol that your cookies work
53
+ data, resp, cookies, forward = get(URL)
54
+ postdata["stips"] = cookie_hash_from_string(cookies)["stips"]
55
+ postdata["tst"] = cookie_hash_from_string(cookies)["tst"]
56
+
57
+ data, resp, cookies, forward, old_url = get(LOGIN_REFERER_URL, cookies) + [URL]
58
+ until forward.nil?
59
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
60
+ end
61
+
62
+ data, resp, cookies, forward, old_url = get("#{LOGIN_URL}?#{LOGIN_REFERER_PATH}", cookies) + [LOGIN_REFERER_URL]
63
+ until forward.nil?
64
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
65
+ end
66
+
67
+ doc = Hpricot(data)
68
+ (doc/:input).each do |input|
69
+ postdata["usrd"] = input.attributes["value"] if input.attributes["name"] == "usrd"
70
+ end
71
+ # parse data for <input name="usrd" value="2726212" type="hidden"> and add it to the postdata
72
+
73
+ postdata["SNS_SC"] = cookie_hash_from_string(cookies)["SNS_SC"]
74
+ postdata["SNS_LDC"] = cookie_hash_from_string(cookies)["SNS_LDC"]
75
+ postdata["LTState"] = cookie_hash_from_string(cookies)["LTState"]
76
+ # raise data.inspect
77
+
78
+ data, resp, cookies, forward, old_url = post(LOGIN_URL, h_to_query_string(postdata), cookies, LOGIN_REFERER_URL) + [LOGIN_REFERER_URL]
79
+
80
+ until forward.nil?
81
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
82
+ end
83
+
84
+ if data.index("Invalid Username or Password. Please try again.")
85
+ raise AuthenticationError, "Username and password do not match"
86
+ elsif data.index("Required field must not be blank")
87
+ raise AuthenticationError, "Login and password must not be blank"
88
+ elsif data.index("errormsg_0_logincaptcha")
89
+ raise AuthenticationError, "Captcha error"
90
+ elsif data.index("Invalid request")
91
+ raise ConnectionError, PROTOCOL_ERROR
92
+ elsif cookies == ""
93
+ raise ConnectionError, PROTOCOL_ERROR
94
+ end
95
+
96
+ @cookies = cookies
97
+ end
98
+
99
+ def contacts
100
+ postdata = {
101
+ "file" => 'contacts',
102
+ "fileType" => 'csv'
103
+ }
104
+
105
+ return @contacts if @contacts
106
+ if connected?
107
+ data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
108
+
109
+ until forward.nil?
110
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
111
+ end
112
+
113
+ if resp.code_type != Net::HTTPOK
114
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
115
+ end
116
+
117
+ # parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
118
+ doc = Hpricot(data)
119
+ (doc/:input).each do |input|
120
+ postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
121
+ end
122
+
123
+ data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
124
+
125
+ until forward.nil?
126
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
127
+ end
128
+
129
+ if data.include?("error.gif")
130
+ raise AuthenticationError, "Account invalid"
131
+ end
132
+
133
+ parse data
134
+ end
135
+ end
136
+ private
137
+
138
+ def parse(data, options={})
139
+ data = FasterCSV.parse(data)
140
+ col_names = data.shift
141
+ @contacts = data.map do |person|
142
+ ["#{person[0]} #{person[1]}", person[4]] if person[4] && !person[4].empty?
143
+ end.compact
144
+ end
145
+
146
+ def h_to_query_string(hash)
147
+ u = ERB::Util.method(:u)
148
+ hash.map { |k, v|
149
+ u.call(k) + "=" + u.call(v)
150
+ }.join("&")
151
+ end
152
+ end
153
+
154
+ TYPES[:aol] = Aol
155
+ end
@@ -0,0 +1,224 @@
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
+
13
+ class Base
14
+ def initialize(login, password, options={})
15
+ @login = login
16
+ @password = password
17
+ @captcha_token = options[:captcha_token]
18
+ @captcha_response = options[:captcha_response]
19
+ @connections = {}
20
+ connect
21
+ end
22
+
23
+ def connect
24
+ raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @login.empty? || @password.nil? || @password.empty?
25
+ real_connect
26
+ end
27
+
28
+ def connected?
29
+ @cookies && !@cookies.empty?
30
+ end
31
+
32
+ def contacts(options = {})
33
+ return @contacts if @contacts
34
+ if connected?
35
+ url = URI.parse(contact_list_url)
36
+ http = open_http(url)
37
+ resp, data = http.get("#{url.path}?#{url.query}",
38
+ "Cookie" => @cookies
39
+ )
40
+
41
+ if resp.code_type != Net::HTTPOK
42
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
43
+ end
44
+
45
+ parse(data, options)
46
+ end
47
+ end
48
+
49
+ def login
50
+ @attempt ||= 0
51
+ @attempt += 1
52
+
53
+ if @attempt == 1
54
+ @login
55
+ else
56
+ if @login.include?("@#{domain}")
57
+ @login.sub("@#{domain}","")
58
+ else
59
+ "#{@login}@#{domain}"
60
+ end
61
+ end
62
+ end
63
+
64
+ def password
65
+ @password
66
+ end
67
+
68
+ def skip_gzip?
69
+ false
70
+ end
71
+
72
+ private
73
+
74
+ def domain
75
+ @d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
76
+ end
77
+
78
+ def contact_list_url
79
+ self.class.const_get(:CONTACT_LIST_URL)
80
+ end
81
+
82
+ def address_book_url
83
+ self.class.const_get(:ADDRESS_BOOK_URL)
84
+ end
85
+
86
+ def open_http(url)
87
+ c = @connections[Thread.current.object_id] ||= {}
88
+ http = c["#{url.host}:#{url.port}"]
89
+ unless http
90
+ http = Net::HTTP.new(url.host, url.port)
91
+ if url.port == 443
92
+ http.use_ssl = true
93
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
94
+ end
95
+ c["#{url.host}:#{url.port}"] = http
96
+ end
97
+ http.start unless http.started?
98
+ http
99
+ end
100
+
101
+ def cookie_hash_from_string(cookie_string)
102
+ cookie_string.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
103
+ end
104
+
105
+ def parse_cookies(data, existing="")
106
+ return existing if data.nil?
107
+
108
+ cookies = cookie_hash_from_string(existing)
109
+
110
+ data.gsub!(/ ?[\w]+=EXPIRED;/,'')
111
+ data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
112
+ data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
113
+ data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
114
+ data.gsub!(/(;\s*){2,}/,', ')
115
+ data.gsub!(/(,\s*){2,}/,', ')
116
+ data.sub!(/^,\s*/,'')
117
+ data.sub!(/\s*,$/,'')
118
+
119
+ data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
120
+ k, v = data.split("=", 2).map{|j|j.strip}
121
+ if cookies[k] && v.empty?
122
+ cookies.delete(k)
123
+ elsif v && !v.empty?
124
+ cookies[k] = v
125
+ end
126
+ end
127
+
128
+ cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
129
+ end
130
+
131
+ def remove_cookie(cookie, cookies)
132
+ parse_cookies("#{cookie}=", cookies)
133
+ end
134
+
135
+ def post(url, postdata, cookies="", referer="")
136
+ url = URI.parse(url)
137
+ http = open_http(url)
138
+ http_header = { "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
139
+ "Accept-Encoding" => "gzip",
140
+ "Cookie" => cookies,
141
+ "Referer" => referer,
142
+ "Content-Type" => 'application/x-www-form-urlencoded'
143
+ }
144
+ http_header.reject!{|k, v| k == 'Accept-Encoding'} if skip_gzip?
145
+ resp, data = http.post(url.path, postdata, http_header)
146
+ data = uncompress(resp, data)
147
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
148
+ forward = resp.response['Location']
149
+ forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
150
+ if (not forward.nil?) && URI.parse(forward).host.nil?
151
+ forward = url.scheme.to_s + "://" + url.host.to_s + forward
152
+ end
153
+ return data, resp, cookies, forward
154
+ end
155
+
156
+ def get(url, cookies="", referer="")
157
+ url = URI.parse(url)
158
+ http = open_http(url)
159
+ resp, data = http.get("#{url.path}?#{url.query}",
160
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
161
+ "Accept-Encoding" => "gzip",
162
+ "Cookie" => cookies,
163
+ "Referer" => referer
164
+ )
165
+ data = uncompress(resp, data)
166
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
167
+ forward = resp.response['Location']
168
+ if (not forward.nil?)
169
+ forward = forward.gsub(' ', '%20')
170
+ if URI.parse(forward).host.nil?
171
+ forward = url.scheme.to_s + "://" + url.host.to_s + forward
172
+ end
173
+ end
174
+ return data, resp, cookies, forward
175
+ end
176
+
177
+ def uncompress(resp, data)
178
+ case resp.response['content-encoding']
179
+ when 'gzip'
180
+ gz = Zlib::GzipReader.new(StringIO.new(data))
181
+ data = gz.read
182
+ gz.close
183
+ resp.response['content-encoding'] = nil
184
+ # FIXME: Not sure what Hotmail was feeding me with their 'deflate',
185
+ # but the headers definitely were not right
186
+ when 'deflate'
187
+ data = Zlib::Inflate.inflate(data)
188
+ resp.response['content-encoding'] = nil
189
+ end
190
+
191
+ data
192
+ end
193
+ end
194
+
195
+ class ContactsError < StandardError
196
+ end
197
+
198
+ class AuthenticationError < ContactsError
199
+ end
200
+
201
+ class ConnectionError < ContactsError
202
+ end
203
+
204
+ class TypeNotFound < ContactsError
205
+ end
206
+
207
+ def self.new(type, login, password, options={})
208
+ if TYPES.include?(type.to_s.intern)
209
+ TYPES[type.to_s.intern].new(login, password, options)
210
+ else
211
+ raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
212
+ end
213
+ end
214
+
215
+ def self.guess(login, password, options={})
216
+ TYPES.inject([]) do |a, t|
217
+ begin
218
+ a + t[1].new(login, password, options).contacts
219
+ rescue AuthenticationError
220
+ a
221
+ end
222
+ end.uniq
223
+ end
224
+ 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,93 @@
1
+ require 'fastercsv'
2
+ require 'rubygems'
3
+ require 'nokogiri'
4
+
5
+ class Contacts
6
+ class Hotmail < Base
7
+ URL = "https://login.live.com/login.srf?id=2"
8
+ CONTACT_LIST_URL = "https://mail.live.com/mail/GetContacts.aspx"
9
+ PROTOCOL_ERROR = "Hotmail has changed its protocols, please upgrade this library first. If that does not work, report this error at http://rubyforge.org/forum/?group_id=2693"
10
+ PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
11
+
12
+ def real_connect
13
+
14
+ data, resp, cookies, forward = get(URL)
15
+ old_url = URL
16
+ until forward.nil?
17
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
18
+ end
19
+
20
+ postdata = "PPSX=%s&PwdPad=%s&login=%s&passwd=%s&LoginOptions=2&PPFT=%s" % [
21
+ CGI.escape(data.split("><").grep(/PPSX/).first[/=\S+$/][2..-3]),
22
+ PWDPAD[0...(PWDPAD.length-@password.length)],
23
+ CGI.escape(login),
24
+ CGI.escape(password),
25
+ CGI.escape(data.split("><").grep(/PPFT/).first[/=\S+$/][2..-3])
26
+ ]
27
+
28
+ form_url = data.split("><").grep(/form/).first.split[5][8..-2]
29
+ data, resp, cookies, forward = post(form_url, postdata, cookies)
30
+
31
+ old_url = form_url
32
+ until cookies =~ /; PPAuth=/ || forward.nil?
33
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
34
+ end
35
+
36
+ if data.index("The e-mail address or password is incorrect")
37
+ raise AuthenticationError, "Username and password do not match"
38
+ elsif data != ""
39
+ raise AuthenticationError, "Required field must not be blank"
40
+ elsif cookies == ""
41
+ raise ConnectionError, PROTOCOL_ERROR
42
+ end
43
+
44
+ data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
45
+ until forward.nil?
46
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
47
+ end
48
+
49
+
50
+ @domain = URI.parse(old_url).host
51
+ @cookies = cookies
52
+ rescue AuthenticationError => m
53
+ if @attempt == 1
54
+ retry
55
+ else
56
+ raise m
57
+ end
58
+ end
59
+
60
+ def contacts(options = {})
61
+ if @contacts.nil? && connected?
62
+ url = URI.parse(contact_list_url)
63
+ data, resp, cookies, forward = get(get_contact_list_url, @cookies )
64
+ data.gsub!(";",",")
65
+ data.gsub!("'","")
66
+ data = data.gsub(/[\x80-\xff]/,"")
67
+
68
+ @contacts = FasterCSV.parse(data, {:headers => true, :col_sep => ','}).map do |row|
69
+ name = ""
70
+ name = row["First Name"] if !row["First Name"].nil?
71
+ name << " #{row["Last Name"]}" if !row["Last Name"].nil?
72
+ email = row["E-mail Address"] || ""
73
+ [name, email]
74
+ end
75
+ @contacts.delete_if{|x| x[1].blank?}
76
+ else
77
+ @contacts || []
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ TYPES[:hotmail] = Hotmail
84
+
85
+ # the contacts url is dynamic
86
+ # luckily it tells us where to find it
87
+ def get_contact_list_url
88
+ data = get(CONTACT_LIST_URL, @cookies)[0]
89
+ html_doc = Nokogiri::HTML(data)
90
+ html_doc.xpath("//a")[0]["href"]
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,16 @@
1
+ if !Object.const_defined?('ActiveSupport')
2
+ require 'json'
3
+ end
4
+
5
+ class Contacts
6
+ def self.parse_json( string )
7
+ if Object.const_defined?('ActiveSupport') and
8
+ ActiveSupport.const_defined?('JSON')
9
+ ActiveSupport::JSON.decode( string )
10
+ elsif Object.const_defined?('JSON')
11
+ JSON.parse( string )
12
+ else
13
+ raise 'Contacts requires JSON or Rails (with ActiveSupport::JSON)'
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,68 @@
1
+ require 'csv'
2
+
3
+ class Contacts
4
+ class Mailru < Base
5
+ LOGIN_URL = "https://auth.mail.ru/cgi-bin/auth"
6
+ ADDRESS_BOOK_URL = "http://win.mail.ru/cgi-bin/abexport/addressbook.csv"
7
+
8
+ attr_accessor :cookies
9
+
10
+ def real_connect
11
+ username = login
12
+
13
+ postdata = "Login=%s&Domain=%s&Password=%s" % [
14
+ CGI.escape(username),
15
+ CGI.escape(domain_param(username)),
16
+ CGI.escape(password)
17
+ ]
18
+
19
+ data, resp, self.cookies, forward = post(LOGIN_URL, postdata, "")
20
+
21
+ if data.index("fail=1")
22
+ raise AuthenticationError, "Username and password do not match"
23
+ elsif cookies == "" or data == ""
24
+ raise ConnectionError, PROTOCOL_ERROR
25
+ end
26
+
27
+ data, resp, cookies, forward = get(login_token_link(data), login_cookies.join(';'))
28
+ end
29
+
30
+ def contacts
31
+ postdata = "confirm=1&abtype=6"
32
+ data, resp, cookies, forward = post(ADDRESS_BOOK_URL, postdata, login_cookies.join(';'))
33
+
34
+ @contacts = []
35
+ CSV.parse(data) do |row|
36
+ @contacts << [row[0], row[4]] unless header_row?(row)
37
+ end
38
+
39
+ @contacts
40
+ end
41
+
42
+ def skip_gzip?
43
+ true
44
+ end
45
+
46
+ private
47
+ def login_token_link(data)
48
+ data.match(/url=(.+)\">/)[1]
49
+ end
50
+
51
+ def login_cookies
52
+ self.cookies.split(';').collect{|c| c if (c.include?('t=') or c.include?('Mpop='))}.compact.collect{|c| c.strip}
53
+ end
54
+
55
+ def header_row?(row)
56
+ row[0] == 'AB-Name'
57
+ end
58
+
59
+ def domain_param(login)
60
+ login.include?('@') ?
61
+ login.match(/.+@(.+)/)[1] :
62
+ 'mail.ru'
63
+ end
64
+
65
+ end
66
+
67
+ TYPES[:mailru] = Mailru
68
+ end
@@ -0,0 +1,130 @@
1
+ require 'rexml/document'
2
+
3
+ class Contacts
4
+ class Plaxo < Base
5
+ URL = "http://www.plaxo.com/"
6
+ LOGIN_URL = "https://www.plaxo.com/signin"
7
+ ADDRESS_BOOK_URL = "http://www.plaxo.com/po3/?module=ab&operation=viewFull&mode=normal"
8
+ CONTACT_LIST_URL = "http://www.plaxo.com/axis/soap/contact?_action=getContacts&_format=xml"
9
+ PROTOCOL_ERROR = "Plaxo 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"
10
+
11
+ def real_connect
12
+
13
+ end # real_connect
14
+
15
+ def contacts
16
+ getdata = "&authInfo.authByEmail.email=%s" % CGI.escape(login)
17
+ getdata += "&authInfo.authByEmail.password=%s" % CGI.escape(password)
18
+ data, resp, cookies, forward = get(CONTACT_LIST_URL + getdata)
19
+
20
+ if resp.code_type != Net::HTTPOK
21
+ raise ConnectionError, PROTOCOL_ERROR
22
+ end
23
+
24
+ parse data
25
+ end # contacts
26
+
27
+ private
28
+ def parse(data, options={})
29
+ doc = REXML::Document.new(data)
30
+ code = doc.elements['//response/code'].text
31
+
32
+ if code == '401'
33
+ raise AuthenticationError, "Username and password do not match"
34
+ elsif code == '200'
35
+ @contacts = []
36
+ doc.elements.each('//contact') do |cont|
37
+ name = if cont.elements['fullName']
38
+ cont.elements['fullName'].text
39
+ elsif cont.elements['displayName']
40
+ cont.elements['displayName'].text
41
+ end
42
+ email = if cont.elements['email1']
43
+ cont.elements['email1'].text
44
+ end
45
+ if name || email
46
+ @contacts << [name, email]
47
+ end
48
+ end
49
+ @contacts
50
+ else
51
+ raise ConnectionError, PROTOCOL_ERROR
52
+ end
53
+
54
+ end # parse
55
+
56
+ end # Plaxo
57
+
58
+ TYPES[:plaxo] = Plaxo
59
+
60
+ end # Contacts
61
+
62
+
63
+ # sample contacts responses
64
+ =begin
65
+ Bad email
66
+ =========
67
+ <?xml version="1.0" encoding="utf-8" ?>
68
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
69
+ <response>
70
+ <code>401</code>
71
+ <subCode>1</subCode>
72
+ <message>User not found.</message>
73
+ </response>
74
+ </ns1:GetContactsResponse>
75
+
76
+
77
+ Bad password
78
+ ============
79
+ <?xml version="1.0" encoding="utf-8" ?>
80
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
81
+ <response>
82
+ <code>401</code>
83
+ <subCode>4</subCode>
84
+ <message>Bad password or security token.</message>
85
+ </response>
86
+ </ns1:GetContactsResponse>
87
+
88
+
89
+ Success
90
+ =======
91
+ <?xml version="1.0" encoding="utf-8" ?>
92
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
93
+
94
+ <response>
95
+ <code>200</code>
96
+ <message>OK</message>
97
+ <userId>77311236242</userId>
98
+ </response>
99
+
100
+ <contacts>
101
+
102
+ <contact>
103
+ <itemId>61312569</itemId>
104
+ <displayName>Joe Blow1</displayName>
105
+ <fullName>Joe Blow1</fullName>
106
+ <firstName>Joe</firstName>
107
+ <lastName>Blow1</lastName>
108
+ <homeEmail1>joeblow1@mailinator.com</homeEmail1>
109
+ <email1>joeblow1@mailinator.com</email1>
110
+ <folderId>5291351</folderId>
111
+ </contact>
112
+
113
+ <contact>
114
+ <itemId>61313159</itemId>
115
+ <displayName>Joe Blow2</displayName>
116
+ <fullName>Joe Blow2</fullName>
117
+ <firstName>Joe</firstName>
118
+ <lastName>Blow2</lastName>
119
+ <homeEmail1>joeblow2@mailinator.com</homeEmail1>
120
+ <email1>joeblow2@mailinator.com</email1>
121
+ <folderId>5291351</folderId>
122
+ </contact>
123
+
124
+ </contacts>
125
+
126
+ <totalCount>2</totalCount>
127
+ <editCounter>3</editCounter>
128
+
129
+ </ns1:GetContactsResponse>
130
+ =end
@@ -0,0 +1,105 @@
1
+ class Contacts
2
+ class Yahoo < Base
3
+ URL = "http://mail.yahoo.com/"
4
+ LOGIN_URL = "https://login.yahoo.com/config/login"
5
+ ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?.rand=430244936"
6
+ CONTACT_LIST_URL = "http://address.mail.yahoo.com/?_src=&_crumb=crumb&sortfield=3&bucket=1&scroll=1&VPC=social_list&.r=time"
7
+ PROTOCOL_ERROR = "Yahoo 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"
8
+
9
+ def real_connect
10
+ postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
11
+ postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
12
+ postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
13
+ postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
14
+
15
+ data, resp, cookies, forward = post(LOGIN_URL, postdata)
16
+
17
+ if data.index("Invalid ID or password") || data.index("This ID is not yet taken")
18
+ raise AuthenticationError, "Username and password do not match"
19
+ elsif data.index("Sign in") && data.index("to Yahoo!")
20
+ raise AuthenticationError, "Required field must not be blank"
21
+ elsif !data.match(/uncompressed\/chunked/)
22
+ raise ConnectionError, PROTOCOL_ERROR
23
+ elsif cookies == ""
24
+ raise ConnectionError, PROTOCOL_ERROR
25
+ end
26
+
27
+ data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
28
+
29
+ if resp.code_type != Net::HTTPOK
30
+ raise ConnectionError, PROTOCOL_ERROR
31
+ end
32
+
33
+ @cookies = cookies
34
+ end
35
+
36
+ def contacts
37
+ return @contacts if @contacts
38
+ @contacts = []
39
+
40
+ if connected?
41
+ # first, get the addressbook site with the new crumb parameter
42
+ url = URI.parse(address_book_url)
43
+ http = open_http(url)
44
+ resp, data = http.get("#{url.path}?#{url.query}",
45
+ "Cookie" => @cookies
46
+ )
47
+
48
+ if resp.code_type != Net::HTTPOK
49
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
50
+ end
51
+
52
+ crumb = data.to_s[/dotCrumb: '(.*?)'/][13...-1]
53
+
54
+ # now proceed with the new ".crumb" parameter to get the csv data
55
+ url = URI.parse(contact_list_url.sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
56
+ http = open_http(url)
57
+ resp, more_data = http.get("#{url.path}?#{url.query}",
58
+ "Cookie" => @cookies,
59
+ "X-Requested-With" => "XMLHttpRequest",
60
+ "Referer" => address_book_url
61
+ )
62
+
63
+ if resp.code_type != Net::HTTPOK
64
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
65
+ end
66
+
67
+ if more_data =~ /"TotalABContacts":(\d+)/
68
+ total = $1.to_i
69
+ ((total / 50.0).ceil).times do |i|
70
+ # now proceed with the new ".crumb" parameter to get the csv data
71
+ url = URI.parse(contact_list_url.sub("bucket=1","bucket=#{i}").sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
72
+ http = open_http(url)
73
+ resp, more_data = http.get("#{url.path}?#{url.query}",
74
+ "Cookie" => @cookies,
75
+ "X-Requested-With" => "XMLHttpRequest",
76
+ "Referer" => address_book_url
77
+ )
78
+
79
+ if resp.code_type != Net::HTTPOK
80
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
81
+ end
82
+
83
+ parse more_data
84
+ end
85
+ end
86
+
87
+ @contacts
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def parse(data, options={})
94
+ @contacts ||= []
95
+ @contacts += Contacts.parse_json(data)["response"]["ResultSet"]["Contacts"].to_a.select{|contact|!contact["email"].to_s.empty?}.map do |contact|
96
+ name = contact["contactName"].split(",")
97
+ [[name.pop, name.join(",")].join(" ").strip, contact["email"]]
98
+ end if data =~ /^\{"response":/
99
+ @contacts
100
+ end
101
+
102
+ end
103
+
104
+ TYPES[:yahoo] = Yahoo
105
+ end
data/lib/contacts.rb ADDED
@@ -0,0 +1,14 @@
1
+ class Contacts
2
+ $:.unshift(File.dirname(__FILE__)+"/contacts/")
3
+
4
+ require 'rubygems'
5
+
6
+ require 'base'
7
+ require 'json_picker'
8
+ require 'gmail'
9
+ require 'hotmail'
10
+ require 'yahoo'
11
+ require 'plaxo'
12
+ require 'aol'
13
+ require 'mailru'
14
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yehezkielbs-contacts
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 2
9
+ - 11
10
+ version: 1.2.11
11
+ platform: ruby
12
+ authors:
13
+ - Lucas Carlson
14
+ - Brad Imbierowicz
15
+ - Wong Liang Zan
16
+ - Mateusz Konikowski
17
+ - Laurynas Butkus
18
+ autorequire:
19
+ bindir: bin
20
+ cert_chain: []
21
+
22
+ date: 2012-04-05 00:00:00 Z
23
+ dependencies:
24
+ - !ruby/object:Gem::Dependency
25
+ name: json
26
+ prerelease: false
27
+ requirement: &id001 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ hash: 5
33
+ segments:
34
+ - 1
35
+ - 6
36
+ - 5
37
+ version: 1.6.5
38
+ type: :runtime
39
+ version_requirements: *id001
40
+ - !ruby/object:Gem::Dependency
41
+ name: gdata
42
+ prerelease: false
43
+ requirement: &id002 !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ hash: 17
49
+ segments:
50
+ - 1
51
+ - 1
52
+ - 1
53
+ version: 1.1.1
54
+ type: :runtime
55
+ version_requirements: *id002
56
+ - !ruby/object:Gem::Dependency
57
+ name: nokogiri
58
+ prerelease: false
59
+ requirement: &id003 !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 1
67
+ - 5
68
+ - 0
69
+ version: 1.5.0
70
+ type: :runtime
71
+ version_requirements: *id003
72
+ - !ruby/object:Gem::Dependency
73
+ name: nokogiri
74
+ prerelease: false
75
+ requirement: &id004 !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ~>
79
+ - !ruby/object:Gem::Version
80
+ hash: 3
81
+ segments:
82
+ - 1
83
+ - 5
84
+ - 0
85
+ version: 1.5.0
86
+ type: :runtime
87
+ version_requirements: *id004
88
+ - !ruby/object:Gem::Dependency
89
+ name: fastercsv
90
+ prerelease: false
91
+ requirement: &id005 !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ hash: 11
97
+ segments:
98
+ - 1
99
+ - 5
100
+ - 4
101
+ version: 1.5.4
102
+ type: :runtime
103
+ version_requirements: *id005
104
+ description: A universal interface to grab contact list information from Yahoo, AOL, Gmail, Hotmail, Plaxo, GMX.net, Web.de, inbox.lt, seznam.cz, t-online.de.
105
+ email: yehezkielbs@gmail.com
106
+ executables: []
107
+
108
+ extensions: []
109
+
110
+ extra_rdoc_files: []
111
+
112
+ files:
113
+ - lib/contacts/aol.rb
114
+ - lib/contacts/base.rb
115
+ - lib/contacts/gmail.rb
116
+ - lib/contacts/hotmail.rb
117
+ - lib/contacts/json_picker.rb
118
+ - lib/contacts/mailru.rb
119
+ - lib/contacts/plaxo.rb
120
+ - lib/contacts/yahoo.rb
121
+ - lib/contacts.rb
122
+ - examples/grab_contacts.rb
123
+ - LICENSE
124
+ - README.rdoc
125
+ - Rakefile
126
+ homepage: https://github.com/yehezkielbs/contacts
127
+ licenses: []
128
+
129
+ post_install_message:
130
+ rdoc_options: []
131
+
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ hash: 3
140
+ segments:
141
+ - 0
142
+ version: "0"
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ hash: 3
149
+ segments:
150
+ - 0
151
+ version: "0"
152
+ requirements: []
153
+
154
+ rubyforge_project:
155
+ rubygems_version: 1.8.21
156
+ signing_key:
157
+ specification_version: 3
158
+ summary: grab contacts from Yahoo, AOL, Gmail, Hotmail, Plaxo, GMX.net, Web.de, inbox.lt, seznam.cz, t-online.de
159
+ test_files: []
160
+