jwhitmire-contacts 1.0.17
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/LICENSE +10 -0
- data/README +59 -0
- data/Rakefile +95 -0
- data/VERSION +1 -0
- data/contacts.gemspec +78 -0
- data/cruise_config.rb +22 -0
- data/examples/grab_contacts.rb +12 -0
- data/geminstaller.yml +6 -0
- data/lib/contacts.rb +8 -0
- data/lib/contacts/base.rb +209 -0
- data/lib/contacts/gmail.rb +94 -0
- data/lib/contacts/hotmail.rb +125 -0
- data/lib/contacts/plaxo.rb +130 -0
- data/lib/contacts/yahoo.rb +82 -0
- data/spec/gemtest_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- data/test/example_accounts.yml +40 -0
- data/test/test_helper.rb +30 -0
- data/test/test_suite.rb +4 -0
- data/test/unit/gmail_contact_importer_test.rb +39 -0
- data/test/unit/hotmail_contact_importer_test.rb +27 -0
- data/test/unit/test_accounts_test.rb +23 -0
- data/test/unit/yahoo_csv_contact_importer_test.rb +32 -0
- metadata +96 -0
data/.gitignore
ADDED
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,59 @@
|
|
1
|
+
== Welcome to Contacts
|
2
|
+
|
3
|
+
Contacts is a universal interface to grab contact list information from various providers including Hotmail, Gmail and Yahoo.
|
4
|
+
|
5
|
+
== Download
|
6
|
+
|
7
|
+
* gem install contacts
|
8
|
+
* http://rubyforge.org/projects/contacts
|
9
|
+
* svn co svn://rubyforge.org/var/svn/contacts
|
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
|
+
|
43
|
+
== Forked by jwhitmire
|
44
|
+
|
45
|
+
The purpose of my fork is to use the simple structure of the existing contacts gem and expand it to support the new OAuth based authentication APIs provided by email carriers. It would also be nice to have this support all Portable Contact providers (great initiative led by Plaxo). It is also my intention to expand the test coverage and not require real accounts with the providers in order to test the behavior, though some level of connection test is still useful in case they change their API.
|
46
|
+
|
47
|
+
My Todo List:
|
48
|
+
|
49
|
+
* Support Gmail Contacts API
|
50
|
+
* Support Plaxo Portable Contacts
|
51
|
+
* Support Yahoo API
|
52
|
+
* Support Microsoft Live API
|
53
|
+
|
54
|
+
* Add support for Facebook?
|
55
|
+
* Add support for LinkedIn?
|
56
|
+
* Add support for Twitter?
|
57
|
+
|
58
|
+
This library is released under the terms of the BSD.
|
59
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'lib/contacts'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "contacts"
|
9
|
+
|
10
|
+
gem.summary = %Q{Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail}
|
11
|
+
gem.description = <<-DESC
|
12
|
+
Seamless importing of contact data from Yahoo, Gmail, Hotmail, and Plaxo. OAuth support is coming.
|
13
|
+
DESC
|
14
|
+
gem.homepage = "http://rubyforge.org/projects/contacts"
|
15
|
+
|
16
|
+
gem.email = "jeff@jwhitmire.com"
|
17
|
+
gem.homepage = "http://github.com/jwhitmire/contacts"
|
18
|
+
gem.authors = ["Lucas Carlson", "Jeff Whitmire"]
|
19
|
+
|
20
|
+
gem.add_dependency('json', '>= 0.4.1')
|
21
|
+
gem.requirements << "A json parser"
|
22
|
+
|
23
|
+
gem.require_path = 'lib'
|
24
|
+
|
25
|
+
gem.has_rdoc = true
|
26
|
+
gem.files.exclude 'test/accounts.yml'
|
27
|
+
gem.files.include 'spec/**/*'
|
28
|
+
|
29
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rake/testtask'
|
36
|
+
Rake::TestTask.new(:test) do |test|
|
37
|
+
test.libs << 'lib' << 'test'
|
38
|
+
test.pattern = 'test/**/*_test.rb'
|
39
|
+
test.verbose = true
|
40
|
+
end
|
41
|
+
|
42
|
+
begin
|
43
|
+
require 'rcov/rcovtask'
|
44
|
+
Rcov::RcovTask.new do |test|
|
45
|
+
test.libs << 'test'
|
46
|
+
test.pattern = 'test/**/*_test.rb'
|
47
|
+
test.verbose = true
|
48
|
+
end
|
49
|
+
rescue LoadError
|
50
|
+
task :rcov do
|
51
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
task :test => :check_dependencies
|
56
|
+
|
57
|
+
require 'spec/rake/spectask'
|
58
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
59
|
+
spec.libs << 'lib' << 'spec'
|
60
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
61
|
+
end
|
62
|
+
|
63
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
64
|
+
spec.libs << 'lib' << 'spec'
|
65
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
66
|
+
spec.rcov = true
|
67
|
+
end
|
68
|
+
|
69
|
+
task :spec => :check_dependencies
|
70
|
+
|
71
|
+
# task :default => :spec
|
72
|
+
task :default => :test
|
73
|
+
|
74
|
+
require 'rake/rdoctask'
|
75
|
+
Rake::RDocTask.new do |rdoc|
|
76
|
+
if File.exist?('VERSION')
|
77
|
+
version = File.read('VERSION')
|
78
|
+
else
|
79
|
+
version = ""
|
80
|
+
end
|
81
|
+
|
82
|
+
rdoc.rdoc_dir = 'rdoc'
|
83
|
+
rdoc.title = "gemtest #{version}"
|
84
|
+
rdoc.rdoc_files.include('README*')
|
85
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "Report code statistics (KLOCs, etc) from the application"
|
89
|
+
task :stats do
|
90
|
+
require 'code_statistics'
|
91
|
+
CodeStatistics.new(
|
92
|
+
["Library", "lib"],
|
93
|
+
["Units", "test"]
|
94
|
+
).to_s
|
95
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.17
|
data/contacts.gemspec
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{contacts}
|
8
|
+
s.version = "1.0.17"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Lucas Carlson", "Jeff Whitmire"]
|
12
|
+
s.date = %q{2009-08-10}
|
13
|
+
s.description = %q{ Seamless importing of contact data from Yahoo, Gmail, Hotmail, and Plaxo. OAuth support is coming.
|
14
|
+
}
|
15
|
+
s.email = %q{jeff@jwhitmire.com}
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE",
|
18
|
+
"README"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"contacts.gemspec",
|
27
|
+
"cruise_config.rb",
|
28
|
+
"examples/grab_contacts.rb",
|
29
|
+
"geminstaller.yml",
|
30
|
+
"lib/contacts.rb",
|
31
|
+
"lib/contacts/base.rb",
|
32
|
+
"lib/contacts/gmail.rb",
|
33
|
+
"lib/contacts/hotmail.rb",
|
34
|
+
"lib/contacts/plaxo.rb",
|
35
|
+
"lib/contacts/yahoo.rb",
|
36
|
+
"spec/gemtest_spec.rb",
|
37
|
+
"spec/gemtest_spec.rb",
|
38
|
+
"spec/spec_helper.rb",
|
39
|
+
"spec/spec_helper.rb",
|
40
|
+
"test/example_accounts.yml",
|
41
|
+
"test/test_helper.rb",
|
42
|
+
"test/test_suite.rb",
|
43
|
+
"test/unit/gmail_contact_importer_test.rb",
|
44
|
+
"test/unit/hotmail_contact_importer_test.rb",
|
45
|
+
"test/unit/test_accounts_test.rb",
|
46
|
+
"test/unit/yahoo_csv_contact_importer_test.rb"
|
47
|
+
]
|
48
|
+
s.homepage = %q{http://github.com/jwhitmire/contacts}
|
49
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.requirements = ["A json parser"]
|
52
|
+
s.rubygems_version = %q{1.3.4}
|
53
|
+
s.summary = %q{Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail}
|
54
|
+
s.test_files = [
|
55
|
+
"spec/gemtest_spec.rb",
|
56
|
+
"spec/spec_helper.rb",
|
57
|
+
"test/test_helper.rb",
|
58
|
+
"test/test_suite.rb",
|
59
|
+
"test/unit/gmail_contact_importer_test.rb",
|
60
|
+
"test/unit/hotmail_contact_importer_test.rb",
|
61
|
+
"test/unit/test_accounts_test.rb",
|
62
|
+
"test/unit/yahoo_csv_contact_importer_test.rb",
|
63
|
+
"examples/grab_contacts.rb"
|
64
|
+
]
|
65
|
+
|
66
|
+
if s.respond_to? :specification_version then
|
67
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
68
|
+
s.specification_version = 3
|
69
|
+
|
70
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
71
|
+
s.add_runtime_dependency(%q<json>, [">= 0.4.1"])
|
72
|
+
else
|
73
|
+
s.add_dependency(%q<json>, [">= 0.4.1"])
|
74
|
+
end
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<json>, [">= 0.4.1"])
|
77
|
+
end
|
78
|
+
end
|
data/cruise_config.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Project-specific configuration for CruiseControl.rb
|
2
|
+
|
3
|
+
Project.configure do |project|
|
4
|
+
|
5
|
+
# Send email notifications about broken and fixed builds to email1@your.site, email2@your.site (default: send to nobody)
|
6
|
+
# if building this on your own CI box, please remove!
|
7
|
+
project.email_notifier.emails = ['opensource@pivotallabs.com']
|
8
|
+
|
9
|
+
# Set email 'from' field to john@doe.com:
|
10
|
+
# project.email_notifier.from = 'john@doe.com'
|
11
|
+
|
12
|
+
# Build the project by invoking rake task 'custom'
|
13
|
+
# project.rake_task = 'custom'
|
14
|
+
|
15
|
+
# Build the project by invoking shell script "build_my_app.sh". Keep in mind that when the script is invoked, current working directory is
|
16
|
+
# [cruise]/projects/your_project/work, so if you do not keep build_my_app.sh in version control, it should be '../build_my_app.sh' instead
|
17
|
+
# project.build_command = 'build_my_app.sh'
|
18
|
+
|
19
|
+
# Ping Subversion for new revisions every 5 minutes (default: 30 seconds)
|
20
|
+
# project.scheduler.polling_interval = 5.minutes
|
21
|
+
|
22
|
+
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
|
data/geminstaller.yml
ADDED
data/lib/contacts.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "net/http"
|
3
|
+
require "net/https"
|
4
|
+
require "uri"
|
5
|
+
require "zlib"
|
6
|
+
require "stringio"
|
7
|
+
require "thread"
|
8
|
+
|
9
|
+
class Contacts
|
10
|
+
TYPES = {}
|
11
|
+
|
12
|
+
class Base
|
13
|
+
def initialize(login, password)
|
14
|
+
@login = login
|
15
|
+
@password = password
|
16
|
+
@connections = {}
|
17
|
+
connect
|
18
|
+
end
|
19
|
+
|
20
|
+
def connect
|
21
|
+
raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @password.nil?
|
22
|
+
real_connect
|
23
|
+
end
|
24
|
+
|
25
|
+
def connected?
|
26
|
+
@cookies && !@cookies.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
def contacts(options = {})
|
30
|
+
return @contacts if @contacts
|
31
|
+
if connected?
|
32
|
+
url = URI.parse(contact_list_url)
|
33
|
+
http = open_http(url)
|
34
|
+
resp, data = http.get("#{url.path}?#{url.query}",
|
35
|
+
"Cookie" => @cookies
|
36
|
+
)
|
37
|
+
|
38
|
+
if resp.code_type != Net::HTTPOK
|
39
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
40
|
+
end
|
41
|
+
|
42
|
+
parse(data, options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def login
|
47
|
+
@attempt ||= 0
|
48
|
+
@attempt += 1
|
49
|
+
|
50
|
+
if @attempt == 1
|
51
|
+
@login
|
52
|
+
else
|
53
|
+
if @login.include?("@#{domain}")
|
54
|
+
@login.sub("@#{domain}","")
|
55
|
+
else
|
56
|
+
"#{@login}@#{domain}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def password
|
62
|
+
@password
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def domain
|
68
|
+
@d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
|
69
|
+
end
|
70
|
+
|
71
|
+
def contact_list_url
|
72
|
+
self.class.const_get(:CONTACT_LIST_URL)
|
73
|
+
end
|
74
|
+
|
75
|
+
def address_book_url
|
76
|
+
self.class.const_get(:ADDRESS_BOOK_URL)
|
77
|
+
end
|
78
|
+
|
79
|
+
def open_http(url)
|
80
|
+
c = @connections[Thread.current.object_id] ||= {}
|
81
|
+
http = c["#{url.host}:#{url.port}"]
|
82
|
+
unless http
|
83
|
+
http = Net::HTTP.new(url.host, url.port)
|
84
|
+
if url.port == 443
|
85
|
+
http.use_ssl = true
|
86
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
87
|
+
end
|
88
|
+
c["#{url.host}:#{url.port}"] = http
|
89
|
+
end
|
90
|
+
http.start unless http.started?
|
91
|
+
http
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_cookies(data, existing="")
|
95
|
+
return existing if data.nil?
|
96
|
+
|
97
|
+
cookies = existing.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
|
98
|
+
|
99
|
+
data.gsub!(/ ?[\w]+=EXPIRED;/,'')
|
100
|
+
data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
|
101
|
+
data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
|
102
|
+
data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
|
103
|
+
data.gsub!(/(;\s*){2,}/,', ')
|
104
|
+
data.gsub!(/(,\s*){2,}/,', ')
|
105
|
+
data.sub!(/^,\s*/,'')
|
106
|
+
data.sub!(/\s*,$/,'')
|
107
|
+
|
108
|
+
data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
|
109
|
+
k, v = data.split("=", 2).map{|j|j.strip}
|
110
|
+
if cookies[k] && v.empty?
|
111
|
+
cookies.delete(k)
|
112
|
+
elsif v && !v.empty?
|
113
|
+
cookies[k] = v
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
|
118
|
+
end
|
119
|
+
|
120
|
+
def remove_cookie(cookie, cookies)
|
121
|
+
parse_cookies("#{cookie}=", cookies)
|
122
|
+
end
|
123
|
+
|
124
|
+
def post(url, postdata, cookies="", referer="")
|
125
|
+
url = URI.parse(url)
|
126
|
+
http = open_http(url)
|
127
|
+
resp, data = http.post(url.path, postdata,
|
128
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
|
129
|
+
"Accept-Encoding" => "gzip",
|
130
|
+
"Cookie" => cookies,
|
131
|
+
"Referer" => referer,
|
132
|
+
"Content-Type" => 'application/x-www-form-urlencoded'
|
133
|
+
)
|
134
|
+
data = uncompress(resp, data)
|
135
|
+
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
136
|
+
forward = resp.response['Location']
|
137
|
+
forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
|
138
|
+
if (not forward.nil?) && URI.parse(forward).host.nil?
|
139
|
+
forward = url.scheme.to_s + "://" + url.host.to_s + forward
|
140
|
+
end
|
141
|
+
return data, resp, cookies, forward
|
142
|
+
end
|
143
|
+
|
144
|
+
def get(url, cookies="", referer="")
|
145
|
+
url = URI.parse(url)
|
146
|
+
http = open_http(url)
|
147
|
+
resp, data = http.get("#{url.path}?#{url.query}",
|
148
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
|
149
|
+
"Accept-Encoding" => "gzip",
|
150
|
+
"Cookie" => cookies,
|
151
|
+
"Referer" => referer
|
152
|
+
)
|
153
|
+
data = uncompress(resp, data)
|
154
|
+
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
155
|
+
forward = resp.response['Location']
|
156
|
+
if (not forward.nil?) && URI.parse(forward).host.nil?
|
157
|
+
forward = url.scheme.to_s + "://" + url.host.to_s + forward
|
158
|
+
end
|
159
|
+
return data, resp, cookies, forward
|
160
|
+
end
|
161
|
+
|
162
|
+
def uncompress(resp, data)
|
163
|
+
case resp.response['content-encoding']
|
164
|
+
when 'gzip':
|
165
|
+
gz = Zlib::GzipReader.new(StringIO.new(data))
|
166
|
+
data = gz.read
|
167
|
+
gz.close
|
168
|
+
resp.response['content-encoding'] = nil
|
169
|
+
# FIXME: Not sure what Hotmail was feeding me with their 'deflate',
|
170
|
+
# but the headers definitely were not right
|
171
|
+
when 'deflate':
|
172
|
+
data = Zlib::Inflate.inflate(data)
|
173
|
+
resp.response['content-encoding'] = nil
|
174
|
+
end
|
175
|
+
|
176
|
+
data
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class ContactsError < StandardError
|
181
|
+
end
|
182
|
+
|
183
|
+
class AuthenticationError < ContactsError
|
184
|
+
end
|
185
|
+
|
186
|
+
class ConnectionError < ContactsError
|
187
|
+
end
|
188
|
+
|
189
|
+
class TypeNotFound < ContactsError
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.new(type, login, password)
|
193
|
+
if TYPES.include?(type.to_s.intern)
|
194
|
+
TYPES[type.to_s.intern].new(login, password)
|
195
|
+
else
|
196
|
+
raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.guess(login, password)
|
201
|
+
TYPES.inject([]) do |a, t|
|
202
|
+
begin
|
203
|
+
a + t[1].new(login, password).contacts
|
204
|
+
rescue AuthenticationError
|
205
|
+
a
|
206
|
+
end
|
207
|
+
end.uniq
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
begin
|
2
|
+
# If the json gem is available, use it
|
3
|
+
require "json/add/rails"
|
4
|
+
rescue MissingSourceFile
|
5
|
+
# Otherwise wrap the ActiveSupport JSON implementation for our simple use case
|
6
|
+
class JSON
|
7
|
+
def self.parse(i)
|
8
|
+
ActiveSupport::JSON.decode(i)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Contacts
|
14
|
+
class Gmail < Base
|
15
|
+
URL = "https://mail.google.com/mail/"
|
16
|
+
LOGIN_URL = "https://www.google.com/accounts/ServiceLoginAuth"
|
17
|
+
LOGIN_REFERER_URL = "https://www.google.com/accounts/ServiceLogin?service=mail&passive=true&rm=false&continue=http%3A%2F%2Fmail.google.com%2Fmail%3Fui%3Dhtml%26zy%3Dl<mpl=yj_blanco<mplcache=2&hl=en"
|
18
|
+
CONTACT_LIST_URL = "https://mail.google.com/mail/contacts/data/contacts?thumb=true&show=ALL&enums=true&psort=Name&max=10000&out=js&rf=&jsx=true"
|
19
|
+
PROTOCOL_ERROR = "Gmail 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"
|
20
|
+
|
21
|
+
def real_connect
|
22
|
+
postdata = "ltmpl=yj_blanco"
|
23
|
+
postdata += "&continue=%s" % CGI.escape(URL)
|
24
|
+
postdata += "<mplcache=2"
|
25
|
+
postdata += "&service=mail"
|
26
|
+
postdata += "&rm=false"
|
27
|
+
postdata += "<mpl=yj_blanco"
|
28
|
+
postdata += "&hl=en"
|
29
|
+
postdata += "&Email=%s" % CGI.escape(login)
|
30
|
+
postdata += "&Passwd=%s" % CGI.escape(password)
|
31
|
+
postdata += "&rmShown=1"
|
32
|
+
postdata += "&null=Sign+in"
|
33
|
+
|
34
|
+
time = Time.now.to_i
|
35
|
+
time_past = Time.now.to_i - 8 - rand(12)
|
36
|
+
cookie = "GMAIL_LOGIN=T#{time_past}/#{time_past}/#{time}"
|
37
|
+
|
38
|
+
data, resp, cookies, forward, old_url = post(LOGIN_URL, postdata, cookie, LOGIN_REFERER_URL) + [LOGIN_URL]
|
39
|
+
|
40
|
+
cookies = remove_cookie("GMAIL_LOGIN", cookies)
|
41
|
+
|
42
|
+
if data.index("Username and password do not match") || data.index("New to Gmail? It's free and easy")
|
43
|
+
raise AuthenticationError, "Username and password do not match"
|
44
|
+
elsif data.index("The username or password you entered is incorrect")
|
45
|
+
raise AuthenticationError, "Username and password do not match"
|
46
|
+
elsif data.index("Required field must not be blank")
|
47
|
+
raise AuthenticationError, "Login and password must not be blank"
|
48
|
+
elsif data.index("errormsg_0_logincaptcha")
|
49
|
+
raise AuthenticationError, "Captcha error"
|
50
|
+
elsif data.index("Invalid request")
|
51
|
+
raise ConnectionError, PROTOCOL_ERROR
|
52
|
+
elsif cookies == ""
|
53
|
+
raise ConnectionError, PROTOCOL_ERROR
|
54
|
+
end
|
55
|
+
|
56
|
+
cookies = remove_cookie("LSID", cookies)
|
57
|
+
cookies = remove_cookie("GV", cookies)
|
58
|
+
|
59
|
+
@cookies = cookies
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def parse(data, options)
|
65
|
+
data.gsub!(/^while \(true\); &&&START&&&/, '')
|
66
|
+
data.gsub!(/ &&&END&&&$/, '')
|
67
|
+
data.gsub!(/\t/, ' ') # tabs in the note field cause errors with JSON.parse
|
68
|
+
data.gsub!(/[\t\x00-\x1F]/, " ") # strip control characters
|
69
|
+
|
70
|
+
@contacts = JSON.parse(data)['Body']['Contacts'] || {}
|
71
|
+
|
72
|
+
# Determine in which format to return the data.
|
73
|
+
|
74
|
+
# Return the full JSON Hash.
|
75
|
+
return @contacts if(options[:details])
|
76
|
+
|
77
|
+
# Default format.
|
78
|
+
# ['Name', 'Email1', 'Email2', ...]
|
79
|
+
if @contacts != nil
|
80
|
+
@contacts = @contacts.delete_if {|c| c["Emails"].nil?}.map do |c|
|
81
|
+
name, emails = c.values_at "Name", "Emails"
|
82
|
+
# emails are returned in a form of
|
83
|
+
# [{"Address"=>"home.email@gmail.com"}, {"Type"=>{"Id"=>"WORK"}, "Address"=>"work.email@gmail.com"}]
|
84
|
+
emails = emails.collect{|a| a.values_at("Address")}
|
85
|
+
[name, emails].flatten
|
86
|
+
end
|
87
|
+
else
|
88
|
+
[]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
TYPES[:gmail] = Gmail
|
94
|
+
end
|
@@ -0,0 +1,125 @@
|
|
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
|
+
if data.index("The e-mail address or password is incorrect")
|
31
|
+
raise AuthenticationError, "Username and password do not match"
|
32
|
+
elsif data != ""
|
33
|
+
raise AuthenticationError, "Required field must not be blank"
|
34
|
+
elsif cookies == ""
|
35
|
+
raise ConnectionError, PROTOCOL_ERROR
|
36
|
+
end
|
37
|
+
|
38
|
+
old_url = form_url
|
39
|
+
|
40
|
+
until forward.nil?
|
41
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
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
|
+
@domain = URI.parse(old_url).host
|
50
|
+
@cookies = cookies
|
51
|
+
rescue AuthenticationError => m
|
52
|
+
if @attempt == 1
|
53
|
+
retry
|
54
|
+
else
|
55
|
+
raise m
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def contacts(options = {})
|
60
|
+
if connected?
|
61
|
+
url = URI.parse(contact_list_url)
|
62
|
+
data, resp, cookies, forward = get( contact_list_url, @cookies )
|
63
|
+
|
64
|
+
if resp.code_type != Net::HTTPOK
|
65
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
66
|
+
end
|
67
|
+
|
68
|
+
@contacts = []
|
69
|
+
build_contacts = []
|
70
|
+
go = true
|
71
|
+
index = 0
|
72
|
+
|
73
|
+
while(go) do
|
74
|
+
go = false
|
75
|
+
url = URI.parse(get_contact_list_url(index))
|
76
|
+
http = open_http(url)
|
77
|
+
resp, data = http.get(get_contact_list_url(index), "Cookie" => @cookies)
|
78
|
+
|
79
|
+
email_match_text_beginning = Regexp.escape("http://m.mail.live.com/?rru=compose&to=")
|
80
|
+
email_match_text_end = Regexp.escape("&")
|
81
|
+
|
82
|
+
raw_html = resp.body.grep(/(?:e|dn)lk[0-9]+/)
|
83
|
+
raw_html.delete_at 0
|
84
|
+
raw_html.inject do |memo, row|
|
85
|
+
c_info = row.match(/(e|dn)lk([0-9])+/)
|
86
|
+
|
87
|
+
# Same contact, or different?
|
88
|
+
build_contacts << [] if memo != c_info[2]
|
89
|
+
|
90
|
+
# Grab info
|
91
|
+
case c_info[1]
|
92
|
+
when "e" # Email
|
93
|
+
build_contacts.last[1] = row.match(/#{email_match_text_beginning}(.*)#{email_match_text_end}/)[1]
|
94
|
+
when "dn" # Name
|
95
|
+
build_contacts.last[0] = row.match(/<a[^>]*>(.+)<\/a>/)[1]
|
96
|
+
end
|
97
|
+
|
98
|
+
# Set memo to contact id
|
99
|
+
c_info[2]
|
100
|
+
end
|
101
|
+
|
102
|
+
go = resp.body.include?("Next page")
|
103
|
+
index += 1
|
104
|
+
end
|
105
|
+
|
106
|
+
build_contacts.each do |contact|
|
107
|
+
unless contact[1].nil?
|
108
|
+
# Only return contacts with email addresses
|
109
|
+
contact[1] = CGI::unescape(contact[1])
|
110
|
+
@contacts << contact
|
111
|
+
end
|
112
|
+
end
|
113
|
+
return @contacts
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_contact_list_url(index)
|
118
|
+
"http://mpeople.live.com/default.aspx?pg=#{index}"
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
TYPES[:hotmail] = Hotmail
|
124
|
+
end
|
125
|
+
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,82 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
class Contacts
|
4
|
+
class Yahoo < Base
|
5
|
+
URL = "http://mail.yahoo.com/"
|
6
|
+
LOGIN_URL = "https://login.yahoo.com/config/login"
|
7
|
+
ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?1&VPC=import_export"
|
8
|
+
CONTACT_LIST_URL = "http://address.yahoo.com/index.php?VPC=import_export&A=B&submit[action_export_yahoo]=Export%20Now"
|
9
|
+
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"
|
10
|
+
|
11
|
+
def real_connect
|
12
|
+
postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
|
13
|
+
postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
|
14
|
+
postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
|
15
|
+
postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
|
16
|
+
|
17
|
+
data, resp, cookies, forward = post(LOGIN_URL, postdata)
|
18
|
+
|
19
|
+
if data.index("Invalid ID or password") || data.index("This ID is not yet taken")
|
20
|
+
raise AuthenticationError, "Username and password do not match"
|
21
|
+
elsif data.index("Sign in") && data.index("to Yahoo!")
|
22
|
+
raise AuthenticationError, "Required field must not be blank"
|
23
|
+
elsif !data.match(/uncompressed\/chunked/)
|
24
|
+
raise ConnectionError, PROTOCOL_ERROR
|
25
|
+
elsif cookies == ""
|
26
|
+
raise ConnectionError, PROTOCOL_ERROR
|
27
|
+
end
|
28
|
+
|
29
|
+
data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
|
30
|
+
|
31
|
+
if resp.code_type != Net::HTTPOK
|
32
|
+
raise ConnectionError, PROTOCOL_ERROR
|
33
|
+
end
|
34
|
+
|
35
|
+
@cookies = cookies
|
36
|
+
end
|
37
|
+
|
38
|
+
def contacts
|
39
|
+
return @contacts if @contacts
|
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[/id="crumb2" value="(.*?)"/][19...-1]
|
53
|
+
|
54
|
+
# now proceed with the new ".crumb" parameter to get the csv data
|
55
|
+
url = URI.parse("#{contact_list_url}&.crumb=#{crumb}")
|
56
|
+
http = open_http(url)
|
57
|
+
resp, data = http.get("#{url.path}?#{url.query}",
|
58
|
+
"Cookie" => @cookies
|
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
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def parse(data, options={})
|
72
|
+
data = CSV.parse(data)
|
73
|
+
col_names = data.shift
|
74
|
+
@contacts = data.map do |person|
|
75
|
+
[[person[0], person[1], person[2]].delete_if{|i|i.empty?}.join(" "), person[4]] unless person[4].empty?
|
76
|
+
end.compact
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
TYPES[:yahoo] = Yahoo
|
82
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -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"
|
data/test/test_helper.rb
ADDED
@@ -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
|
data/test/test_suite.rb
ADDED
@@ -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,27 @@
|
|
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
|
+
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_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_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,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jwhitmire-contacts
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.17
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lucas Carlson
|
8
|
+
- Jeff Whitmire
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-08-10 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: json
|
18
|
+
type: :runtime
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.4.1
|
25
|
+
version:
|
26
|
+
description: Seamless importing of contact data from Yahoo, Gmail, Hotmail, and Plaxo. OAuth support is coming.
|
27
|
+
email: jeff@jwhitmire.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- LICENSE
|
34
|
+
- README
|
35
|
+
files:
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- contacts.gemspec
|
42
|
+
- cruise_config.rb
|
43
|
+
- examples/grab_contacts.rb
|
44
|
+
- geminstaller.yml
|
45
|
+
- lib/contacts.rb
|
46
|
+
- lib/contacts/base.rb
|
47
|
+
- lib/contacts/gmail.rb
|
48
|
+
- lib/contacts/hotmail.rb
|
49
|
+
- lib/contacts/plaxo.rb
|
50
|
+
- lib/contacts/yahoo.rb
|
51
|
+
- spec/gemtest_spec.rb
|
52
|
+
- spec/spec_helper.rb
|
53
|
+
- test/example_accounts.yml
|
54
|
+
- test/test_helper.rb
|
55
|
+
- test/test_suite.rb
|
56
|
+
- test/unit/gmail_contact_importer_test.rb
|
57
|
+
- test/unit/hotmail_contact_importer_test.rb
|
58
|
+
- test/unit/test_accounts_test.rb
|
59
|
+
- test/unit/yahoo_csv_contact_importer_test.rb
|
60
|
+
has_rdoc: false
|
61
|
+
homepage: http://github.com/jwhitmire/contacts
|
62
|
+
licenses:
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options:
|
65
|
+
- --charset=UTF-8
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: "0"
|
79
|
+
version:
|
80
|
+
requirements:
|
81
|
+
- A json parser
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 1.3.5
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
|
87
|
+
test_files:
|
88
|
+
- spec/gemtest_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
- test/test_helper.rb
|
91
|
+
- test/test_suite.rb
|
92
|
+
- test/unit/gmail_contact_importer_test.rb
|
93
|
+
- test/unit/hotmail_contact_importer_test.rb
|
94
|
+
- test/unit/test_accounts_test.rb
|
95
|
+
- test/unit/yahoo_csv_contact_importer_test.rb
|
96
|
+
- examples/grab_contacts.rb
|