heyzap-contacts 1.2.2

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 ADDED
@@ -0,0 +1,47 @@
1
+ == Welcome to Contacts
2
+
3
+ Contacts is a universal interface to grab contact list information from various providers including Hotmail, AOL, Gmail, Plaxo and Yahoo.
4
+
5
+ == Download
6
+
7
+ * gem install contacts
8
+ * http://github.com/cardmagic/contacts
9
+ * git clone git://github.com/cardmagic/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
+
21
+ Contacts.new(:gmail, login, password).contacts
22
+ Contacts.new(:hotmail, login, password).contacts
23
+ Contacts.new(:yahoo, login, password).contacts
24
+
25
+ Contacts.guess(login, password).contacts
26
+
27
+ 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.
28
+
29
+ == Examples
30
+
31
+ See the examples/ directory.
32
+
33
+ == Authors
34
+
35
+ * Lucas Carlson from MOG (mailto:lucas@rufy.com) - http://mog.com
36
+
37
+ == Contributors
38
+
39
+ * Britt Selvitelle from Twitter (mailto:anotherbritt@gmail.com) - http://twitter.com
40
+ * Tony Targonski from GigPark (mailto:tony@gigpark.com) - http://gigpark.com
41
+ * Waheed Barghouthi from Watwet (mailto:waheed.barghouthi@gmail.com) - http://watwet.com
42
+ * Glenn Sidney from Glenn Fu (mailto:glenn@glennfu.com) - http://glennfu.com
43
+ * Brian McQuay from Onomojo (mailto:brian@onomojo.com) - http://onomojo.com
44
+ * Adam Hunter (mailto:adamhunter@me.com) - http://adamhunter.me/
45
+
46
+ This library is released under the terms of the BSD.
47
+
@@ -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/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 = 'heyzap-contacts'
47
+ s.version = PKG_VERSION
48
+ s.summary = <<-EOF
49
+ Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
50
+ EOF
51
+ s.description = <<-EOF
52
+ Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
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('gdata', '= 1.1.1')
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.author = "Lucas Carlson"
75
+ s.email = "lucas@rufy.com"
76
+ s.homepage = "http://rubyforge.org/projects/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,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,11 @@
1
+ $:.unshift(File.dirname(__FILE__)+"/contacts/")
2
+
3
+ require 'rubygems'
4
+
5
+ require 'base'
6
+ require 'gmail'
7
+ require 'hotmail'
8
+ require 'yahoo'
9
+ require 'plaxo'
10
+ require 'aol'
11
+ require 'json_picker'
@@ -0,0 +1,151 @@
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
+
17
+ postdata = {
18
+ "loginId" => login,
19
+ "password" => password,
20
+ "rememberMe" => "on",
21
+ "_sns_fg_color_" => "",
22
+ "_sns_err_color_" => "",
23
+ "_sns_link_color_" => "",
24
+ "_sns_width_" => "",
25
+ "_sns_height_" => "",
26
+ "offerId" => "mail-second-en-us",
27
+ "_sns_bg_color_" => "",
28
+ "sitedomain" => "sns.webmail.aol.com",
29
+ "regPromoCode" => "",
30
+ "mcState" => "initialized",
31
+ "uitype" => "std",
32
+ "siteId" => "",
33
+ "lang" => "en",
34
+ "locale" => "us",
35
+ "authLev" => "0",
36
+ "siteState" => "",
37
+ "isSiteStateEncoded" => "false",
38
+ "use_aam" => "0",
39
+ "seamless" => "novl",
40
+ "aolsubmit" => CGI.escape("Sign In"),
41
+ "idType" => "SN",
42
+ "usrd" => "",
43
+ "doSSL" => "",
44
+ "redirType" => "",
45
+ "xchk" => "false"
46
+ }
47
+
48
+ # Get this cookie and stick it in the form to confirm to Aol that your cookies work
49
+ data, resp, cookies, forward = get(URL)
50
+ postdata["stips"] = cookie_hash_from_string(cookies)["stips"]
51
+ postdata["tst"] = cookie_hash_from_string(cookies)["tst"]
52
+
53
+ data, resp, cookies, forward, old_url = get(LOGIN_REFERER_URL, cookies) + [URL]
54
+ until forward.nil?
55
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
56
+ end
57
+
58
+ data, resp, cookies, forward, old_url = get("#{LOGIN_URL}?#{LOGIN_REFERER_PATH}", cookies) + [LOGIN_REFERER_URL]
59
+ until forward.nil?
60
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
61
+ end
62
+
63
+ doc = Hpricot(data)
64
+ (doc/:input).each do |input|
65
+ postdata["usrd"] = input.attributes["value"] if input.attributes["name"] == "usrd"
66
+ end
67
+ # parse data for <input name="usrd" value="2726212" type="hidden"> and add it to the postdata
68
+
69
+ postdata["SNS_SC"] = cookie_hash_from_string(cookies)["SNS_SC"]
70
+ postdata["SNS_LDC"] = cookie_hash_from_string(cookies)["SNS_LDC"]
71
+ postdata["LTState"] = cookie_hash_from_string(cookies)["LTState"]
72
+ # raise data.inspect
73
+
74
+ data, resp, cookies, forward, old_url = post(LOGIN_URL, h_to_query_string(postdata), cookies, LOGIN_REFERER_URL) + [LOGIN_REFERER_URL]
75
+
76
+ until forward.nil?
77
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
78
+ end
79
+
80
+ if data.index("Invalid Screen Name or Password.")
81
+ raise AuthenticationError, "Username and password do not match"
82
+ elsif data.index("Required field must not be blank")
83
+ raise AuthenticationError, "Login and password must not be blank"
84
+ elsif data.index("errormsg_0_logincaptcha")
85
+ raise AuthenticationError, "Captcha error"
86
+ elsif data.index("Invalid request")
87
+ raise ConnectionError, PROTOCOL_ERROR
88
+ elsif cookies == ""
89
+ raise ConnectionError, PROTOCOL_ERROR
90
+ end
91
+
92
+ @cookies = cookies
93
+ end
94
+
95
+ def contacts
96
+ postdata = {
97
+ "file" => 'contacts',
98
+ "fileType" => 'csv'
99
+ }
100
+
101
+ return @contacts if @contacts
102
+ if connected?
103
+ data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
104
+
105
+ until forward.nil?
106
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
107
+ end
108
+
109
+ if resp.code_type != Net::HTTPOK
110
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
111
+ end
112
+
113
+ # parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
114
+ doc = Hpricot(data)
115
+ (doc/:input).each do |input|
116
+ postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
117
+ end
118
+
119
+ data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
120
+
121
+ until forward.nil?
122
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
123
+ end
124
+
125
+ if data.include?("error.gif")
126
+ raise AuthenticationError, "Account invalid"
127
+ end
128
+
129
+ parse data
130
+ end
131
+ end
132
+ private
133
+
134
+ def parse(data, options={})
135
+ data = CSV::Reader.parse(data)
136
+ col_names = data.shift
137
+ @contacts = data.map do |person|
138
+ ["#{person[0]} #{person[1]}", person[4]] if person[4] && !person[4].empty?
139
+ end.compact
140
+ end
141
+
142
+ def h_to_query_string(hash)
143
+ u = ERB::Util.method(:u)
144
+ hash.map { |k, v|
145
+ u.call(k) + "=" + u.call(v)
146
+ }.join("&")
147
+ end
148
+ end
149
+
150
+ TYPES[:aol] = Aol
151
+ end
@@ -0,0 +1,215 @@
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.2"
13
+
14
+ class Base
15
+ def initialize(login, password)
16
+ @login = login
17
+ @password = password
18
+ @connections = {}
19
+ connect
20
+ end
21
+
22
+ def connect
23
+ raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @login.empty? || @password.nil? || @password.empty?
24
+ real_connect
25
+ end
26
+
27
+ def connected?
28
+ @cookies && !@cookies.empty?
29
+ end
30
+
31
+ def contacts(options = {})
32
+ return @contacts if @contacts
33
+ if connected?
34
+ url = URI.parse(contact_list_url)
35
+ http = open_http(url)
36
+ resp, data = http.get("#{url.path}?#{url.query}",
37
+ "Cookie" => @cookies
38
+ )
39
+
40
+ if resp.code_type != Net::HTTPOK
41
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
42
+ end
43
+
44
+ parse(data, options)
45
+ end
46
+ end
47
+
48
+ def login
49
+ @attempt ||= 0
50
+ @attempt += 1
51
+
52
+ if @attempt == 1
53
+ @login
54
+ else
55
+ if @login.include?("@#{domain}")
56
+ @login.sub("@#{domain}","")
57
+ else
58
+ "#{@login}@#{domain}"
59
+ end
60
+ end
61
+ end
62
+
63
+ def password
64
+ @password
65
+ end
66
+
67
+ private
68
+
69
+ def domain
70
+ @d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
71
+ end
72
+
73
+ def contact_list_url
74
+ self.class.const_get(:CONTACT_LIST_URL)
75
+ end
76
+
77
+ def address_book_url
78
+ self.class.const_get(:ADDRESS_BOOK_URL)
79
+ end
80
+
81
+ def open_http(url)
82
+ c = @connections[Thread.current.object_id] ||= {}
83
+ http = c["#{url.host}:#{url.port}"]
84
+ unless http
85
+ http = Net::HTTP.new(url.host, url.port)
86
+ if url.port == 443
87
+ http.use_ssl = true
88
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
89
+ end
90
+ c["#{url.host}:#{url.port}"] = http
91
+ end
92
+ http.start unless http.started?
93
+ http
94
+ end
95
+
96
+ def cookie_hash_from_string(cookie_string)
97
+ cookie_string.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
98
+ end
99
+
100
+ def parse_cookies(data, existing="")
101
+ return existing if data.nil?
102
+
103
+ cookies = cookie_hash_from_string(existing)
104
+
105
+ data.gsub!(/ ?[\w]+=EXPIRED;/,'')
106
+ data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
107
+ data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
108
+ data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
109
+ data.gsub!(/(;\s*){2,}/,', ')
110
+ data.gsub!(/(,\s*){2,}/,', ')
111
+ data.sub!(/^,\s*/,'')
112
+ data.sub!(/\s*,$/,'')
113
+
114
+ data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
115
+ k, v = data.split("=", 2).map{|j|j.strip}
116
+ if cookies[k] && v.empty?
117
+ cookies.delete(k)
118
+ elsif v && !v.empty?
119
+ cookies[k] = v
120
+ end
121
+ end
122
+
123
+ cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
124
+ end
125
+
126
+ def remove_cookie(cookie, cookies)
127
+ parse_cookies("#{cookie}=", cookies)
128
+ end
129
+
130
+ def post(url, postdata, cookies="", referer="")
131
+ url = URI.parse(url)
132
+ http = open_http(url)
133
+ resp, data = http.post(url.path, postdata,
134
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
135
+ "Accept-Encoding" => "gzip",
136
+ "Cookie" => cookies,
137
+ "Referer" => referer,
138
+ "Content-Type" => 'application/x-www-form-urlencoded'
139
+ )
140
+ data = uncompress(resp, data)
141
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
142
+ forward = resp.response['Location']
143
+ forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
144
+ if (not forward.nil?) && URI.parse(forward).host.nil?
145
+ forward = url.scheme.to_s + "://" + url.host.to_s + forward
146
+ end
147
+ return data, resp, cookies, forward
148
+ end
149
+
150
+ def get(url, cookies="", referer="")
151
+ url = URI.parse(url)
152
+ http = open_http(url)
153
+ resp, data = http.get("#{url.path}?#{url.query}",
154
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
155
+ "Accept-Encoding" => "gzip",
156
+ "Cookie" => cookies,
157
+ "Referer" => referer
158
+ )
159
+ data = uncompress(resp, data)
160
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
161
+ forward = resp.response['Location']
162
+ if (not forward.nil?) && URI.parse(forward).host.nil?
163
+ forward = url.scheme.to_s + "://" + url.host.to_s + forward
164
+ end
165
+ return data, resp, cookies, forward
166
+ end
167
+
168
+ def uncompress(resp, data)
169
+ case resp.response['content-encoding']
170
+ when 'gzip'
171
+ gz = Zlib::GzipReader.new(StringIO.new(data))
172
+ data = gz.read
173
+ gz.close
174
+ resp.response['content-encoding'] = nil
175
+ # FIXME: Not sure what Hotmail was feeding me with their 'deflate',
176
+ # but the headers definitely were not right
177
+ when 'deflate'
178
+ data = Zlib::Inflate.inflate(data)
179
+ resp.response['content-encoding'] = nil
180
+ end
181
+
182
+ data
183
+ end
184
+ end
185
+
186
+ class ContactsError < StandardError
187
+ end
188
+
189
+ class AuthenticationError < ContactsError
190
+ end
191
+
192
+ class ConnectionError < ContactsError
193
+ end
194
+
195
+ class TypeNotFound < ContactsError
196
+ end
197
+
198
+ def self.new(type, login, password)
199
+ if TYPES.include?(type.to_s.intern)
200
+ TYPES[type.to_s.intern].new(login, password)
201
+ else
202
+ raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
203
+ end
204
+ end
205
+
206
+ def self.guess(login, password)
207
+ TYPES.inject([]) do |a, t|
208
+ begin
209
+ a + t[1].new(login, password).contacts
210
+ rescue AuthenticationError
211
+ a
212
+ end
213
+ end.uniq
214
+ end
215
+ 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)
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,124 @@
1
+ class Contacts
2
+ class Hotmail < Base
3
+ URL = "https://login.live.com/login.srf?id=2"
4
+ OLD_CONTACT_LIST_URL = "http://%s/cgi-bin/addresses"
5
+ NEW_CONTACT_LIST_URL = "http://%s/mail/GetContacts.aspx"
6
+ CONTACT_LIST_URL = "http://mpeople.live.com/default.aspx?pg=0"
7
+ COMPOSE_URL = "http://%s/cgi-bin/compose?"
8
+ 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"
9
+ PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
10
+ MAX_HTTP_THREADS = 8
11
+
12
+ def real_connect
13
+ data, resp, cookies, forward = get(URL)
14
+ old_url = URL
15
+ until forward.nil?
16
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
17
+ end
18
+
19
+ postdata = "PPSX=%s&PwdPad=%s&login=%s&passwd=%s&LoginOptions=2&PPFT=%s" % [
20
+ CGI.escape(data.split("><").grep(/PPSX/).first[/=\S+$/][2..-3]),
21
+ PWDPAD[0...(PWDPAD.length-@password.length)],
22
+ CGI.escape(login),
23
+ CGI.escape(password),
24
+ CGI.escape(data.split("><").grep(/PPFT/).first[/=\S+$/][2..-3])
25
+ ]
26
+
27
+ form_url = data.split("><").grep(/form/).first.split[5][8..-2]
28
+ data, resp, cookies, forward = post(form_url, postdata, cookies)
29
+
30
+ old_url = form_url
31
+ until cookies =~ /; PPAuth=/ || forward.nil?
32
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
33
+ end
34
+
35
+ if data.index("The e-mail address or password is incorrect")
36
+ raise AuthenticationError, "Username and password do not match"
37
+ elsif data != ""
38
+ raise AuthenticationError, "Required field must not be blank"
39
+ elsif cookies == ""
40
+ raise ConnectionError, PROTOCOL_ERROR
41
+ end
42
+
43
+ data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
44
+ until forward.nil?
45
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
46
+ end
47
+
48
+ @domain = URI.parse(old_url).host
49
+ @cookies = cookies
50
+ rescue AuthenticationError => m
51
+ if @attempt == 1
52
+ retry
53
+ else
54
+ raise m
55
+ end
56
+ end
57
+
58
+ def contacts(options = {})
59
+ if connected?
60
+ url = URI.parse(contact_list_url)
61
+ data, resp, cookies, forward = get( contact_list_url, @cookies )
62
+
63
+ if resp.code_type != Net::HTTPOK
64
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
65
+ end
66
+
67
+ @contacts = []
68
+ build_contacts = []
69
+ go = true
70
+ index = 0
71
+
72
+ while(go) do
73
+ go = false
74
+ url = URI.parse(get_contact_list_url(index))
75
+ http = open_http(url)
76
+ resp, data = http.get(get_contact_list_url(index), "Cookie" => @cookies)
77
+
78
+ email_match_text_beginning = Regexp.escape("http://m.mail.live.com/?rru=compose&amp;to=")
79
+ email_match_text_end = Regexp.escape("&amp;")
80
+
81
+ raw_html = resp.body.grep(/(?:e|dn)lk[0-9]+/)
82
+ raw_html.delete_at 0
83
+ raw_html.inject do |memo, row|
84
+ c_info = row.match(/(e|dn)lk([0-9])+/)
85
+
86
+ # Same contact, or different?
87
+ build_contacts << [] if memo != c_info[2]
88
+
89
+ # Grab info
90
+ case c_info[1]
91
+ when "e" # Email
92
+ build_contacts.last[1] = row.match(/#{email_match_text_beginning}(.*)#{email_match_text_end}/)[1]
93
+ when "dn" # Name
94
+ build_contacts.last[0] = row.match(/<a[^>]*>(.+)<\/a>/)[1]
95
+ end
96
+
97
+ # Set memo to contact id
98
+ c_info[2]
99
+ end
100
+
101
+ go = resp.body.include?("Next page")
102
+ index += 1
103
+ end
104
+
105
+ build_contacts.each do |contact|
106
+ unless contact[1].nil?
107
+ # Only return contacts with email addresses
108
+ contact[1] = CGI::unescape(contact[1])
109
+ @contacts << contact
110
+ end
111
+ end
112
+ return @contacts
113
+ end
114
+ end
115
+
116
+ def get_contact_list_url(index)
117
+ "http://mpeople.live.com/default.aspx?pg=#{index}"
118
+ end
119
+
120
+ private
121
+
122
+ TYPES[:hotmail] = Hotmail
123
+ end
124
+ 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,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,109 @@
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
+ if connected?
39
+ # first, get the addressbook site with the new crumb parameter
40
+ url = URI.parse(address_book_url)
41
+ http = open_http(url)
42
+ resp, data = http.get("#{url.path}?#{url.query}",
43
+ "Cookie" => @cookies
44
+ )
45
+
46
+ if resp.code_type != Net::HTTPOK
47
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
48
+ end
49
+
50
+ crumb = data.to_s[/dotCrumb: '(.*?)'/][13...-1]
51
+
52
+ # now proceed with the new ".crumb" parameter to get the csv data
53
+ url = URI.parse(contact_list_url.sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
54
+ http = open_http(url)
55
+ resp, more_data = http.get("#{url.path}?#{url.query}",
56
+ "Cookie" => @cookies,
57
+ "X-Requested-With" => "XMLHttpRequest",
58
+ "Referer" => address_book_url
59
+ )
60
+
61
+ if resp.code_type != Net::HTTPOK
62
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
63
+ end
64
+
65
+ parse data
66
+
67
+ parse more_data
68
+
69
+ if more_data =~ /"TotalABContacts":(\d+)/
70
+ total = $1.to_i
71
+ ((total / 50)).times do |i|
72
+ # now proceed with the new ".crumb" parameter to get the csv data
73
+ url = URI.parse(contact_list_url.sub("bucket=1","bucket=#{i+2}").sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
74
+ http = open_http(url)
75
+ resp, more_data = http.get("#{url.path}?#{url.query}",
76
+ "Cookie" => @cookies,
77
+ "X-Requested-With" => "XMLHttpRequest",
78
+ "Referer" => address_book_url
79
+ )
80
+
81
+ if resp.code_type != Net::HTTPOK
82
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
83
+ end
84
+
85
+ parse more_data
86
+ end
87
+ end
88
+
89
+ @contacts
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def parse(data, options={})
96
+ @contacts ||= []
97
+ if data =~ /var InitialContacts = (\[.*?\])/
98
+ @contacts += Contacts.parse_json($1).select{|contact|!contact["email"].to_s.empty?}.map{|contact|[contact["contactName"], contact["email"]]}
99
+ elsif data =~ /^\{"response":/
100
+ @contacts += Contacts.parse_json(data)["response"]["ResultSet"]["Contacts"].to_a.select{|contact|!contact["email"].to_s.empty?}.map{|contact|[contact["contactName"], contact["email"]]}
101
+ else
102
+ @contacts
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ TYPES[:yahoo] = Yahoo
109
+ end
@@ -0,0 +1,40 @@
1
+ gmail:
2
+ username: <changeme>
3
+ password: <changeme>
4
+ contacts:
5
+ -
6
+ name: "FirstName1 LastName1"
7
+ email_address: "firstname1@example.com"
8
+ -
9
+ name: "FirstName2 LastName2"
10
+ email_address: "firstname2@example.com"
11
+ yahoo:
12
+ username: <changeme>
13
+ password: <changeme>
14
+ contacts:
15
+ -
16
+ name: "FirstName1 LastName1"
17
+ email_address: "firstname1@example.com"
18
+ -
19
+ name: "FirstName2 LastName2"
20
+ email_address: "firstname2@example.com"
21
+ hotmail:
22
+ username: <changeme>
23
+ password: <changeme>
24
+ contacts:
25
+ -
26
+ name: "FirstName1 LastName1"
27
+ email_address: "firstname1@example.com"
28
+ -
29
+ name: "FirstName2 LastName2"
30
+ email_address: "firstname2@example.com"
31
+ aol:
32
+ username: <changeme>
33
+ password: <changeme>
34
+ contacts:
35
+ -
36
+ name: "FirstName1 LastName1"
37
+ email_address: "firstname1@example.com"
38
+ -
39
+ name: "FirstName2 LastName2"
40
+ email_address: "firstname2@example.com"
@@ -0,0 +1,30 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(dir + "/../lib/")
3
+ require 'test/unit'
4
+ require 'contacts'
5
+
6
+ class ContactImporterTestCase < Test::Unit::TestCase
7
+ # Add more helper methods to be used by all tests here...
8
+ def default_test
9
+ assert true
10
+ end
11
+ end
12
+
13
+ class TestAccounts
14
+ def self.[](type)
15
+ load[type]
16
+ end
17
+
18
+ def self.load(file = File.dirname(__FILE__) + "/accounts.yml")
19
+ raise "/test/accounts.yml file not found, please create, see /test/example_accounts.yml for information" unless File.exist?(file)
20
+
21
+ accounts = {}
22
+ YAML::load(File.open(file)).each do |type, contents|
23
+ contacts = contents["contacts"].collect {|contact| [contact["name"], contact["email_address"]]}
24
+ accounts[type.to_sym] = Account.new(type.to_sym, contents["username"], contents["password"], contacts)
25
+ end
26
+ accounts
27
+ end
28
+
29
+ Account = Struct.new :type, :username, :password, :contacts
30
+ end
@@ -0,0 +1,4 @@
1
+ dir = File.dirname(__FILE__)
2
+ Dir["#{dir}/**/*_test.rb"].each do |file|
3
+ require file
4
+ end
@@ -0,0 +1,39 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class AolContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:aol]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:aol, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:aol, @account.username, "wrong_password")
18
+ end
19
+ end
20
+
21
+ def test_importer_fails_with_blank_password
22
+ assert_raise(Contacts::AuthenticationError) do
23
+ Contacts.new(:aol, @account.username, "")
24
+ end
25
+ end
26
+
27
+ def test_importer_fails_with_blank_username
28
+ assert_raise(Contacts::AuthenticationError) do
29
+ Contacts.new(:aol, "", @account.password)
30
+ end
31
+ end
32
+
33
+ def test_fetch_contacts
34
+ contacts = Contacts.new(:aol, @account.username, @account.password).contacts
35
+ @account.contacts.each do |contact|
36
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,39 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class GmailContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:gmail]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:gmail, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:gmail, @account.username, "wrong_password")
18
+ end
19
+ end
20
+
21
+ def test_importer_fails_with_blank_password
22
+ assert_raise(Contacts::AuthenticationError) do
23
+ Contacts.new(:gmail, @account.username, "")
24
+ end
25
+ end
26
+
27
+ def test_importer_fails_with_blank_username
28
+ assert_raise(Contacts::AuthenticationError) do
29
+ Contacts.new(:gmail, "", @account.password)
30
+ end
31
+ end
32
+
33
+ def test_fetch_contacts
34
+ contacts = Contacts.new(:gmail, @account.username, @account.password).contacts
35
+ @account.contacts.each do |contact|
36
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class HotmailContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:hotmail]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:hotmail, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:hotmail, @account.username,"wrong_password")
18
+ end
19
+ end
20
+
21
+ def test_fetch_contacts
22
+ contacts = Contacts.new(:hotmail, @account.username, @account.password).contacts
23
+ @account.contacts.each do |contact|
24
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
25
+ end
26
+ end
27
+
28
+ def test_importer_fails_with_invalid_msn_password
29
+ assert_raise(Contacts::AuthenticationError) do
30
+ Contacts.new(:hotmail, "test@msn.com","wrong_password")
31
+ end
32
+ end
33
+
34
+ # Since the hotmail scraper doesn't read names, test email
35
+ def test_fetch_email
36
+ contacts = Contacts.new(:hotmail, @account.username, @account.password).contacts
37
+ @account.contacts.each do |contact|
38
+ assert contacts.any?{|book_contact| book_contact.last == contact.last }, "Could not find: #{contact.inspect} in #{contacts.inspect}"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+
4
+ class TestAccountsTest < ContactImporterTestCase
5
+ def test_test_accounts_loads_data_from_example_accounts_file
6
+ account = TestAccounts.load(File.dirname(__FILE__) + "/../example_accounts.yml")[:gmail]
7
+
8
+ assert_equal :gmail, account.type
9
+ assert_equal "<changeme>", account.username
10
+ assert_equal "<changeme>", account.password
11
+ assert_equal [["FirstName1 LastName1", "firstname1@example.com"], ["FirstName2 LastName2", "firstname2@example.com"]], account.contacts
12
+ end
13
+
14
+ def test_test_accounts_blows_up_if_file_doesnt_exist
15
+ assert_raise(RuntimeError) do
16
+ TestAccounts.load("file_that_does_not_exist.yml")
17
+ end
18
+ end
19
+
20
+ def test_we_can_load_from_account_file
21
+ assert_not_nil TestAccounts[:gmail].username
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class YahooContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:yahoo]
9
+ end
10
+
11
+ def test_a_successful_login
12
+ Contacts.new(:yahoo, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:yahoo, @account.username, "wrong_password")
18
+ end
19
+ # run the "successful" login test to ensure we reset yahoo's failed login lockout counter
20
+ # See http://www.pivotaltracker.com/story/show/138210
21
+ assert_nothing_raised do
22
+ Contacts.new(:yahoo, @account.username, @account.password)
23
+ end
24
+ end
25
+
26
+ def test_a_fetch_contacts
27
+ contacts = Contacts.new(:yahoo, @account.username, @account.password).contacts
28
+ @account.contacts.each do |contact|
29
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
30
+ end
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heyzap-contacts
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.2
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Carlson
8
+ autorequire: contacts
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-12 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.4.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: gdata
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.1
34
+ version:
35
+ description: " Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail\n"
36
+ email: lucas@rufy.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/contacts/hotmail.rb
45
+ - lib/contacts/plaxo.rb
46
+ - lib/contacts/aol.rb
47
+ - lib/contacts/json_picker.rb
48
+ - lib/contacts/base.rb
49
+ - lib/contacts/yahoo.rb
50
+ - lib/contacts/gmail.rb
51
+ - lib/contacts.rb
52
+ - test/test_helper.rb
53
+ - test/example_accounts.yml
54
+ - test/unit/test_accounts_test.rb
55
+ - test/unit/hotmail_contact_importer_test.rb
56
+ - test/unit/yahoo_csv_contact_importer_test.rb
57
+ - test/unit/aol_contact_importer_test.rb
58
+ - test/unit/gmail_contact_importer_test.rb
59
+ - test/test_suite.rb
60
+ - Rakefile
61
+ - LICENSE
62
+ - README
63
+ - examples/grab_contacts.rb
64
+ has_rdoc: true
65
+ homepage: http://rubyforge.org/projects/contacts
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options: []
70
+
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements:
86
+ - A json parser, the gdata ruby gem
87
+ rubyforge_project:
88
+ rubygems_version: 1.3.5
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
92
+ test_files: []
93
+