contacts 1.0.13 → 1.0.15
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/contacts/base.rb +1 -1
- data/lib/contacts/gmail.rb +16 -4
- data/lib/contacts/hotmail.rb +55 -109
- data/lib/contacts/plaxo.rb +4 -5
- 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 +10 -2
data/Rakefile
CHANGED
data/lib/contacts/base.rb
CHANGED
data/lib/contacts/gmail.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
-
|
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
|
2
12
|
|
3
13
|
class Contacts
|
4
14
|
class Gmail < Base
|
@@ -28,8 +38,10 @@ class Contacts
|
|
28
38
|
data, resp, cookies, forward, old_url = post(LOGIN_URL, postdata, cookie, LOGIN_REFERER_URL) + [LOGIN_URL]
|
29
39
|
|
30
40
|
cookies = remove_cookie("GMAIL_LOGIN", cookies)
|
31
|
-
|
32
|
-
if data.index("Username and password do not match")
|
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")
|
33
45
|
raise AuthenticationError, "Username and password do not match"
|
34
46
|
elsif data.index("Required field must not be blank")
|
35
47
|
raise AuthenticationError, "Login and password must not be blank"
|
@@ -79,4 +91,4 @@ class Contacts
|
|
79
91
|
end
|
80
92
|
|
81
93
|
TYPES[:gmail] = Gmail
|
82
|
-
end
|
94
|
+
end
|
data/lib/contacts/hotmail.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
class Contacts
|
2
2
|
class Hotmail < Base
|
3
|
-
URL = "
|
3
|
+
URL = "https://login.live.com/login.srf?id=2"
|
4
4
|
OLD_CONTACT_LIST_URL = "http://%s/cgi-bin/addresses"
|
5
5
|
NEW_CONTACT_LIST_URL = "http://%s/mail/GetContacts.aspx"
|
6
|
-
|
6
|
+
CONTACT_LIST_URL = "http://mpeople.live.com/default.aspx?pg=0"
|
7
7
|
COMPOSE_URL = "http://%s/cgi-bin/compose?"
|
8
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
9
|
PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
|
@@ -11,7 +11,6 @@ class Contacts
|
|
11
11
|
|
12
12
|
def real_connect
|
13
13
|
data, resp, cookies, forward = get(URL)
|
14
|
-
|
15
14
|
old_url = URL
|
16
15
|
until forward.nil?
|
17
16
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
@@ -37,55 +36,16 @@ class Contacts
|
|
37
36
|
end
|
38
37
|
|
39
38
|
old_url = form_url
|
39
|
+
|
40
40
|
until forward.nil?
|
41
41
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
42
42
|
end
|
43
|
-
|
44
|
-
=begin
|
45
|
-
if data =~ %r{action="(.*?)"}
|
46
|
-
forward = $1
|
47
|
-
puts forward
|
48
|
-
napexp = CGI.escape(data.to_s[/id="NAPExp" value="(.*?)"/][19...-1])
|
49
|
-
nap = CGI.escape(data.to_s[/id="NAP" value="(.*?)"/][16...-1])
|
50
|
-
anon = CGI.escape(data.to_s[/id="ANON" value="(.*?)"/][17...-1])
|
51
|
-
anonexp = CGI.escape(data.to_s[/id="ANONExp" value="(.*?)"/][20...-1])
|
52
|
-
t = CGI.escape(data.to_s[/id="t" value="(.*?)"/][14...-1])
|
53
|
-
|
54
|
-
postdata = "NAPExp=%s&NAP=%s&ANON=%s&ANONExp=%s&t=%s" % [ napexp, nap, anon, anonexp, t ]
|
55
|
-
puts postdata
|
56
|
-
data, resp, cookies, forward, old_url = post(forward, postdata, cookies, old_url) + [forward]
|
57
|
-
end
|
58
|
-
|
59
|
-
until forward.nil?
|
60
|
-
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
61
|
-
end
|
62
|
-
=end
|
63
43
|
|
64
44
|
data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
|
65
45
|
until forward.nil?
|
66
46
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
67
47
|
end
|
68
|
-
|
69
|
-
# click on 'Contiune' if presented with the Hotmail Listened page
|
70
|
-
# look for the Submit button with a "TakeMeToInbox" name (this should work for other languages)
|
71
|
-
if (not old_url.grep(/MessageAtLogin.aspx/).first.nil?)
|
72
|
-
|
73
|
-
viewState = data.split(/>\s*?</).grep(/__VIEWSTATE/).first[/value=\".+?\"/][7..-2]
|
74
|
-
eventValidation = data.split(/>\s*?</).grep(/__EVENTVALIDATION/).first[/value=\".+?\"/][7..-2]
|
75
|
-
continueValue = data.split(/>\s*?</).grep(/TakeMeToInbox/).first[/value=\".+?\"/][7..-2]
|
76
|
-
|
77
|
-
# post back to the same url
|
78
|
-
postdata = "%s=%s&%s=%s&%s=%s" % [
|
79
|
-
"__VIEWSTATE", CGI.escape(viewState),
|
80
|
-
"__EVENTVALIDATION", CGI.escape(eventValidation),
|
81
|
-
CGI.escape("TakeMeToInbox"), CGI.escape(continueValue)
|
82
|
-
]
|
83
|
-
data, resp, cookies, forward = post( old_url, postdata, cookies, old_url )
|
84
|
-
until forward.nil?
|
85
|
-
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
48
|
+
|
89
49
|
@domain = URI.parse(old_url).host
|
90
50
|
@cookies = cookies
|
91
51
|
rescue AuthenticationError => m
|
@@ -97,7 +57,6 @@ class Contacts
|
|
97
57
|
end
|
98
58
|
|
99
59
|
def contacts(options = {})
|
100
|
-
return @contacts if @contacts
|
101
60
|
if connected?
|
102
61
|
url = URI.parse(contact_list_url)
|
103
62
|
data, resp, cookies, forward = get( contact_list_url, @cookies )
|
@@ -105,75 +64,62 @@ class Contacts
|
|
105
64
|
if resp.code_type != Net::HTTPOK
|
106
65
|
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
107
66
|
end
|
108
|
-
|
109
|
-
# we have to click on the Export Contacts button to get the csv:
|
110
|
-
# Search the content for __VIEWSTATE or __EVENTVALIDATION
|
111
|
-
viewState = data.split(/>\s*?</).grep(/__VIEWSTATE/).first[/value=\".+?\"/][7..-2]
|
112
|
-
eventValidation = data.split(/>\s*?</).grep(/__EVENTVALIDATION/).first[/value=\".+?\"/][7..-2]
|
113
|
-
exportValue = data.split(/>\s*?</).grep(/ctl02\$ExportButton/).first[/value=\".+?\"/][7..-2]
|
114
|
-
mt = cookies.split("; ").grep(/mt=/).first[3..-1]
|
115
67
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
136
114
|
end
|
137
115
|
end
|
138
|
-
|
139
|
-
|
140
|
-
private
|
141
116
|
|
142
|
-
def
|
143
|
-
|
117
|
+
def get_contact_list_url(index)
|
118
|
+
"http://mpeople.live.com/default.aspx?pg=#{index}"
|
144
119
|
end
|
145
120
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
postdata += "&curmbox=00000000-0000-0000-0000-000000000001"
|
150
|
-
|
151
|
-
a = data.split(/>\s*<input\s+/i).grep(/\s+name="a"/i)
|
152
|
-
return nil if a.empty?
|
153
|
-
|
154
|
-
a = a[0].match(/\s+value="([a-f0-9]+)"/i) or return nil
|
155
|
-
postdata += "&a=#{a[1]}"
|
156
|
-
|
157
|
-
data, resp, @cookies, forward = post(compose_url, postdata, @cookies)
|
158
|
-
e = data.split(/>\s*<input\s+/i).grep(/\s+name="to"/i)
|
159
|
-
return nil if e.empty?
|
160
|
-
|
161
|
-
e = e[0].match(/\s+value="([^"]+)"/i) or return nil
|
162
|
-
@contacts[contacts_slot][1] = e[1] if e[1].match(/@/)
|
163
|
-
end
|
164
|
-
|
165
|
-
def parse(data, options={})
|
166
|
-
data = data.split("\r\n")
|
167
|
-
data = CSV.parse(data.join("\r\n").gsub('"', '').gsub(';', ','), ';')
|
168
|
-
col_names = data.shift
|
169
|
-
|
170
|
-
@contacts = data.delete_if{|person|person[0].nil?}.map do |person|
|
171
|
-
person = person[0].split(",")
|
172
|
-
next unless (idx = person.index('SMTP'))
|
173
|
-
[[person[1], person[2], person[3]].delete_if{|i|i.empty?}.join(" "), person[idx - 1]] unless person[idx - 1].nil?
|
174
|
-
end.compact
|
175
|
-
end
|
121
|
+
private
|
122
|
+
|
123
|
+
TYPES[:hotmail] = Hotmail
|
176
124
|
end
|
177
|
-
|
178
|
-
TYPES[:hotmail] = Hotmail
|
179
125
|
end
|
data/lib/contacts/plaxo.rb
CHANGED
@@ -34,8 +34,8 @@ class Contacts
|
|
34
34
|
elsif code == '200'
|
35
35
|
@contacts = []
|
36
36
|
doc.elements.each('//contact') do |cont|
|
37
|
-
name = cont.elements['fullName'].text
|
38
|
-
email = cont.elements['email1'].text
|
37
|
+
name = cont.elements['fullName'].nil? ? cont.elements['displayName'].text : cont.elements['fullName'].text
|
38
|
+
email = cont.elements['email1'].text
|
39
39
|
@contacts << [name, email]
|
40
40
|
end.compact
|
41
41
|
@contacts
|
@@ -53,7 +53,7 @@ end # Contacts
|
|
53
53
|
|
54
54
|
|
55
55
|
# sample contacts responses
|
56
|
-
|
56
|
+
=begin
|
57
57
|
Bad email
|
58
58
|
=========
|
59
59
|
<?xml version="1.0" encoding="utf-8" ?>
|
@@ -119,5 +119,4 @@ Success
|
|
119
119
|
<editCounter>3</editCounter>
|
120
120
|
|
121
121
|
</ns1:GetContactsResponse>
|
122
|
-
|
123
|
-
'
|
122
|
+
=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"
|
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contacts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lucas Carlson
|
@@ -9,7 +9,7 @@ autorequire: contacts
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-07-11 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -38,6 +38,14 @@ files:
|
|
38
38
|
- lib/contacts/plaxo.rb
|
39
39
|
- lib/contacts/yahoo.rb
|
40
40
|
- lib/contacts.rb
|
41
|
+
- test/example_accounts.yml
|
42
|
+
- test/test_helper.rb
|
43
|
+
- test/test_suite.rb
|
44
|
+
- test/unit
|
45
|
+
- test/unit/gmail_contact_importer_test.rb
|
46
|
+
- test/unit/hotmail_contact_importer_test.rb
|
47
|
+
- test/unit/test_accounts_test.rb
|
48
|
+
- test/unit/yahoo_csv_contact_importer_test.rb
|
41
49
|
- LICENSE
|
42
50
|
- Rakefile
|
43
51
|
- README
|