muck-contacts 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +24 -0
- data/LICENSE +10 -0
- data/README +58 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/contacts.gemspec +106 -0
- data/cruise_config.rb +22 -0
- data/examples/grab_contacts.rb +12 -0
- data/geminstaller.yml +8 -0
- data/lib/contacts.rb +37 -0
- data/lib/contacts/aol_importer.rb +149 -0
- data/lib/contacts/base.rb +226 -0
- data/lib/contacts/facebook.rb +24 -0
- data/lib/contacts/gmail.rb +32 -0
- data/lib/contacts/hotmail.rb +122 -0
- data/lib/contacts/json_picker.rb +16 -0
- data/lib/contacts/linked_in.rb +31 -0
- data/lib/contacts/mailru.rb +68 -0
- data/lib/contacts/outlook.rb +59 -0
- data/lib/contacts/plaxo.rb +130 -0
- data/lib/contacts/vcf.rb +25 -0
- data/lib/contacts/yahoo.rb +104 -0
- data/test/example_accounts.yml +73 -0
- data/test/test_helper.rb +37 -0
- data/test/unit/aol_contact_importer_test.rb +34 -0
- data/test/unit/facebook_contact_importer_test.rb +39 -0
- data/test/unit/gmail_contact_importer_test.rb +39 -0
- data/test/unit/hotmail_contact_importer_test.rb +41 -0
- data/test/unit/linked_in_contact_importer_test.rb +39 -0
- data/test/unit/mailru_contact_importer_test.rb +40 -0
- data/test/unit/outlook_test.rb +24 -0
- data/test/unit/test_accounts_test.rb +23 -0
- data/test/unit/vcf_test.rb +18 -0
- data/test/unit/yahoo_csv_contact_importer_test.rb +35 -0
- metadata +202 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
class Contacts
|
4
|
+
class Mailru < Base
|
5
|
+
LOGIN_URL = "https://auth.mail.ru/cgi-bin/auth"
|
6
|
+
ADDRESS_BOOK_URL = "http://win.mail.ru/cgi-bin/abexport/addressbook.csv"
|
7
|
+
|
8
|
+
attr_accessor :cookies
|
9
|
+
|
10
|
+
def real_connect
|
11
|
+
username = login
|
12
|
+
|
13
|
+
postdata = "Login=%s&Domain=%s&Password=%s" % [
|
14
|
+
CGI.escape(username),
|
15
|
+
CGI.escape(domain_param(username)),
|
16
|
+
CGI.escape(password)
|
17
|
+
]
|
18
|
+
|
19
|
+
data, resp, self.cookies, forward = post(LOGIN_URL, postdata, "")
|
20
|
+
|
21
|
+
if data.index("fail=1")
|
22
|
+
raise AuthenticationError, "Username and password do not match"
|
23
|
+
elsif cookies == "" or data == ""
|
24
|
+
raise ConnectionError, PROTOCOL_ERROR
|
25
|
+
end
|
26
|
+
|
27
|
+
data, resp, cookies, forward = get(login_token_link(data), login_cookies.join(';'))
|
28
|
+
end
|
29
|
+
|
30
|
+
def contacts
|
31
|
+
postdata = "confirm=1&abtype=6"
|
32
|
+
data, resp, cookies, forward = post(ADDRESS_BOOK_URL, postdata, login_cookies.join(';'))
|
33
|
+
|
34
|
+
@contacts = []
|
35
|
+
CSV.parse(data) do |row|
|
36
|
+
@contacts << [row[0], row[4]] unless header_row?(row)
|
37
|
+
end
|
38
|
+
|
39
|
+
@contacts
|
40
|
+
end
|
41
|
+
|
42
|
+
def skip_gzip?
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def login_token_link(data)
|
48
|
+
data.match(/url=(.+)\">/)[1]
|
49
|
+
end
|
50
|
+
|
51
|
+
def login_cookies
|
52
|
+
self.cookies.split(';').collect{|c| c if (c.include?('t=') or c.include?('Mpop='))}.compact.collect{|c| c.strip}
|
53
|
+
end
|
54
|
+
|
55
|
+
def header_row?(row)
|
56
|
+
row[0] == 'AB-Name'
|
57
|
+
end
|
58
|
+
|
59
|
+
def domain_param(login)
|
60
|
+
login.include?('@') ?
|
61
|
+
login.match(/.+@(.+)/)[1] :
|
62
|
+
'mail.ru'
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
TYPES[:mailru] = Mailru
|
68
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
class Contacts
|
4
|
+
class Outlook < Base
|
5
|
+
|
6
|
+
def initialize(file)
|
7
|
+
@contact_file = Array.new
|
8
|
+
file = file.respond_to?(:read) ? file.read : file
|
9
|
+
file.each_line do |line|
|
10
|
+
@contact_file << CSV.parse(line)[0]
|
11
|
+
end
|
12
|
+
@full_name = false
|
13
|
+
@header_indexes = Hash.new
|
14
|
+
@header_indexes[:email_address] = Array.new
|
15
|
+
|
16
|
+
headers = @contact_file[0]
|
17
|
+
|
18
|
+
@contact_file = @contact_file[1, @contact_file.length]
|
19
|
+
|
20
|
+
headers.each_with_index do |header, i|
|
21
|
+
if header.match(/^Name$/)
|
22
|
+
@full_name = true
|
23
|
+
@header_indexes[:full_name] = i
|
24
|
+
elsif header.match(/^First Name/)
|
25
|
+
@header_indexes[:first_name] = i
|
26
|
+
elsif header.match(/^Last Name/)
|
27
|
+
@header_indexes[:last_name] = i
|
28
|
+
elsif header.match(/E-mail/)
|
29
|
+
@header_indexes[:email_address] << i
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def contacts
|
35
|
+
|
36
|
+
contacts = Array.new
|
37
|
+
|
38
|
+
@contact_file.each_with_index do |line, i|
|
39
|
+
contacts[i] = Array.new unless contacts[i]
|
40
|
+
if(@full_name)
|
41
|
+
contacts[i][0] = line[@header_indexes[:full_name]]
|
42
|
+
else
|
43
|
+
contacts[i][0] = "#{line[@header_indexes[:first_name]]} #{line[@header_indexes[:last_name]]}"
|
44
|
+
end
|
45
|
+
@header_indexes[:email_address].each do |index|
|
46
|
+
if line[index] && !contacts[i][1]
|
47
|
+
contacts[i][1] = line[index]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
contacts
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
FILETYPES[:outlook] = Outlook
|
59
|
+
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
|
data/lib/contacts/vcf.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Vcf < Base
|
3
|
+
|
4
|
+
def initialize(file)
|
5
|
+
@contact_file = file
|
6
|
+
end
|
7
|
+
|
8
|
+
def contacts
|
9
|
+
contacts = Array.new
|
10
|
+
i = 0
|
11
|
+
@contact_file.each do |line|
|
12
|
+
contacts[i] = Array.new unless contacts[i]
|
13
|
+
if line.match(/FN:/)
|
14
|
+
contacts[i] << line.gsub(/FN:/, '').strip
|
15
|
+
elsif line.match(/EMAIL;/)
|
16
|
+
contacts[i] << line.gsub(/^.*:/, '').strip
|
17
|
+
elsif line.match(/END:VCARD/)
|
18
|
+
i += 1
|
19
|
+
end
|
20
|
+
end
|
21
|
+
contacts
|
22
|
+
end
|
23
|
+
end
|
24
|
+
FILETYPES[:vcf] = Vcf
|
25
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Yahoo < Base
|
3
|
+
URL = "http://mail.yahoo.com/"
|
4
|
+
LOGIN_URL = "https://login.yahoo.com/config/login"
|
5
|
+
ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?.rand=430244936"
|
6
|
+
CONTACT_LIST_URL = "http://address.mail.yahoo.com/?_src=&_crumb=crumb&sortfield=3&bucket=1&scroll=1&VPC=social_list&.r=time"
|
7
|
+
PROTOCOL_ERROR = "Yahoo has changed its protocols, please upgrade this library first. If that does not work, dive into the code and submit a patch at http://github.com/cardmagic/contacts"
|
8
|
+
|
9
|
+
def real_connect
|
10
|
+
postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
|
11
|
+
postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
|
12
|
+
postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
|
13
|
+
postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
|
14
|
+
|
15
|
+
data, resp, cookies, forward = post(LOGIN_URL, postdata)
|
16
|
+
|
17
|
+
if data.index("Invalid ID or password") || data.index("This ID is not yet taken")
|
18
|
+
raise AuthenticationError, "Username and password do not match"
|
19
|
+
elsif data.index("Sign in") && data.index("to Yahoo!")
|
20
|
+
raise AuthenticationError, "Required field must not be blank"
|
21
|
+
elsif !data.match(/uncompressed\/chunked/)
|
22
|
+
raise ConnectionError, PROTOCOL_ERROR
|
23
|
+
elsif cookies == ""
|
24
|
+
raise ConnectionError, PROTOCOL_ERROR
|
25
|
+
end
|
26
|
+
|
27
|
+
data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
|
28
|
+
|
29
|
+
if resp.code_type != Net::HTTPOK
|
30
|
+
raise ConnectionError, PROTOCOL_ERROR
|
31
|
+
end
|
32
|
+
|
33
|
+
@cookies = cookies
|
34
|
+
end
|
35
|
+
|
36
|
+
def contacts
|
37
|
+
return @contacts if @contacts
|
38
|
+
@contacts = []
|
39
|
+
|
40
|
+
if connected?
|
41
|
+
# first, get the addressbook site with the new crumb parameter
|
42
|
+
url = URI.parse(address_book_url)
|
43
|
+
http = open_http(url)
|
44
|
+
resp, data = http.get("#{url.path}?#{url.query}",
|
45
|
+
"Cookie" => @cookies
|
46
|
+
)
|
47
|
+
|
48
|
+
if resp.code_type != Net::HTTPOK
|
49
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
50
|
+
end
|
51
|
+
|
52
|
+
crumb = data.to_s[/dotCrumb: '(.*?)'/][13...-1]
|
53
|
+
|
54
|
+
# now proceed with the new ".crumb" parameter to get the csv data
|
55
|
+
url = URI.parse(contact_list_url.sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
|
56
|
+
http = open_http(url)
|
57
|
+
resp, more_data = http.get("#{url.path}?#{url.query}",
|
58
|
+
"Cookie" => @cookies,
|
59
|
+
"X-Requested-With" => "XMLHttpRequest",
|
60
|
+
"Referer" => address_book_url
|
61
|
+
)
|
62
|
+
|
63
|
+
if resp.code_type != Net::HTTPOK
|
64
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
65
|
+
end
|
66
|
+
|
67
|
+
if more_data =~ /"TotalABContacts":(\d+)/
|
68
|
+
total = $1.to_i
|
69
|
+
((total / 50.0).ceil).times do |i|
|
70
|
+
# now proceed with the new ".crumb" parameter to get the csv data
|
71
|
+
url = URI.parse(contact_list_url.sub("bucket=1","bucket=#{i}").sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
|
72
|
+
http = open_http(url)
|
73
|
+
resp, more_data = http.get("#{url.path}?#{url.query}",
|
74
|
+
"Cookie" => @cookies,
|
75
|
+
"X-Requested-With" => "XMLHttpRequest",
|
76
|
+
"Referer" => address_book_url
|
77
|
+
)
|
78
|
+
|
79
|
+
if resp.code_type != Net::HTTPOK
|
80
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
81
|
+
end
|
82
|
+
|
83
|
+
parse more_data
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
@contacts
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def parse(data, options={})
|
94
|
+
@contacts ||= []
|
95
|
+
@contacts += Contacts.parse_json(data)["response"]["ResultSet"]["Contacts"].to_a.select{|contact|!contact["email"].to_s.empty?}.map do |contact|
|
96
|
+
name = contact["contactName"].split(",")
|
97
|
+
[[name.pop, name.join(",")].join(" ").strip, contact["email"]]
|
98
|
+
end if data =~ /^\{"response":/
|
99
|
+
@contacts
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
TYPES[:yahoo] = Yahoo
|
104
|
+
end
|
@@ -0,0 +1,73 @@
|
|
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"
|
41
|
+
mailru:
|
42
|
+
username: <changeme>
|
43
|
+
password: <changeme>
|
44
|
+
contacts:
|
45
|
+
-
|
46
|
+
name: "FirstName1 LastName1"
|
47
|
+
email_address: "firstname1@example.com"
|
48
|
+
-
|
49
|
+
name: "FirstName2 LastName2"
|
50
|
+
email_address: "firstname2@example.com"
|
51
|
+
facebook:
|
52
|
+
username: <facebook uid>
|
53
|
+
password: <access token from facebook app oauth2>
|
54
|
+
contacts:
|
55
|
+
-
|
56
|
+
name: "FirstName1 LastName1"
|
57
|
+
account_id: "facebook_uid"
|
58
|
+
-
|
59
|
+
name: "FirstName2 LastName2"
|
60
|
+
account_id: "facebook_uid"
|
61
|
+
linked_in:
|
62
|
+
username: <oauth token for user>
|
63
|
+
password: <oauth secret for user>
|
64
|
+
app_id: <consumer token from linked_in app>
|
65
|
+
app_secret: <consumer secret from linked_in app>
|
66
|
+
contacts:
|
67
|
+
-
|
68
|
+
name: "FirstName1 LastName1"
|
69
|
+
account_id: "linked_in_uid"
|
70
|
+
-
|
71
|
+
name: "FirstName2 LastName2"
|
72
|
+
account_id: "linked_in_uid"
|
73
|
+
|