foco-contacts 1.2.18
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/LICENSE +10 -0
- data/README.rdoc +68 -0
- data/Rakefile +41 -0
- data/examples/grab_contacts.rb +12 -0
- data/lib/contacts.rb +18 -0
- data/lib/contacts/aol.rb +157 -0
- data/lib/contacts/base.rb +249 -0
- data/lib/contacts/gmail.rb +45 -0
- data/lib/contacts/gmx.rb +63 -0
- data/lib/contacts/hotmail.rb +92 -0
- data/lib/contacts/inbox_lt.rb +90 -0
- data/lib/contacts/json_picker.rb +16 -0
- data/lib/contacts/mailru.rb +69 -0
- data/lib/contacts/onelt.rb +78 -0
- data/lib/contacts/plaxo.rb +130 -0
- data/lib/contacts/seznam.rb +78 -0
- data/lib/contacts/tonline_de.rb +79 -0
- data/lib/contacts/web_de.rb +79 -0
- data/lib/contacts/yahoo.rb +106 -0
- metadata +118 -0
@@ -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,78 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Seznam < Base
|
3
|
+
DETECTED_DOMAINS = [ /seznam\.cz/i, /email\.cz/i, /post\.cz/i, /spoluzaci\.cz/i, /stream\.cz/i, /firmy\.cz/i, ]
|
4
|
+
LOGIN_URL = "https://login.szn.cz/loginProcess"
|
5
|
+
ADDRESS_BOOK_URL = "http://email.seznam.cz/abookCsvExport?sessionId=&charset=utf-8&eof=windows&export=nameLast&export=nameFirst&export=nick&export=email"
|
6
|
+
|
7
|
+
attr_accessor :cookies
|
8
|
+
|
9
|
+
def real_connect
|
10
|
+
postdata = "disableSSL=0&domain=%s&forceRelogin=0&forceSSL=0&lang=cz&loginType=seznam&returnURL=%s&serviceId=email&username=%s&password=%s" % [
|
11
|
+
CGI.escape(domain),
|
12
|
+
CGI.escape('http://email.seznam.cz/ticket'),
|
13
|
+
CGI.escape(username),
|
14
|
+
CGI.escape(password)
|
15
|
+
]
|
16
|
+
|
17
|
+
data, resp, self.cookies, forward = post(LOGIN_URL, postdata, "")
|
18
|
+
|
19
|
+
if !forward.nil? && forward.match("badLogin")
|
20
|
+
raise AuthenticationError, "Username and password do not match"
|
21
|
+
end
|
22
|
+
|
23
|
+
doc = Nokogiri(data)
|
24
|
+
|
25
|
+
a = doc.at('body>a')
|
26
|
+
forward = a['href'].to_s
|
27
|
+
|
28
|
+
data, resp, self.cookies, forward = get(forward, self.cookies)
|
29
|
+
|
30
|
+
doc = Nokogiri(data)
|
31
|
+
|
32
|
+
a = doc.at('body>a')
|
33
|
+
forward = a['href'].to_s
|
34
|
+
|
35
|
+
data, resp, self.cookies, forward = get(forward, self.cookies)
|
36
|
+
|
37
|
+
doc = Nokogiri(data)
|
38
|
+
|
39
|
+
a = doc.at('body>a')
|
40
|
+
forward = a['href'].to_s
|
41
|
+
|
42
|
+
data, resp, self.cookies, forward = get(forward, self.cookies)
|
43
|
+
end
|
44
|
+
|
45
|
+
def contacts
|
46
|
+
@contacts = []
|
47
|
+
|
48
|
+
data, resp, self.cookies, forward = get(ADDRESS_BOOK_URL, self.cookies)
|
49
|
+
|
50
|
+
CSV.parse(data, { :col_sep => ';' }) do |row|
|
51
|
+
last_name, first_name, unknown, email = row
|
52
|
+
|
53
|
+
name = "#{first_name} #{last_name}".strip
|
54
|
+
email.strip!
|
55
|
+
|
56
|
+
@contacts << [name, email] unless email.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
@contacts
|
60
|
+
end
|
61
|
+
|
62
|
+
def skip_gzip?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def username
|
69
|
+
@login.split('@').first
|
70
|
+
end
|
71
|
+
|
72
|
+
def domain
|
73
|
+
@login.split('@').last
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
TYPES[:seznam] = Seznam
|
78
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Contacts
|
2
|
+
class TonlineDe < Base
|
3
|
+
DETECTED_DOMAINS = [ /t-mobile\.de/i, /t-online\.de/i ]
|
4
|
+
URL = "https://email.t-online.de/V4-0-4-0/srv-bin/aaa?method=deliverLoginBox"
|
5
|
+
ADDRESS_BOOK_URL = "https://email.t-online.de/V4-0-4-0/srv-bin/addressbook?method=exportAdressbook&p%5Bformat%5D=CSV&p%5Bliid%5D="
|
6
|
+
PROTOCOL_ERROR = "t-online.de has changed its protocols"
|
7
|
+
|
8
|
+
attr_accessor :cookies, :tid
|
9
|
+
|
10
|
+
def real_connect
|
11
|
+
data, resp, self.cookies, forward = get(URL, "")
|
12
|
+
|
13
|
+
doc = Nokogiri(data)
|
14
|
+
meta = doc.at('meta[http-equiv=refresh]')
|
15
|
+
|
16
|
+
if meta.nil?
|
17
|
+
raise ConnectionError, PROTOCOL_ERROR
|
18
|
+
end
|
19
|
+
|
20
|
+
forward = meta['content'].split('URL=').last
|
21
|
+
|
22
|
+
data, resp, self.cookies, forward = get(forward, self.cookies)
|
23
|
+
|
24
|
+
doc = Nokogiri(data)
|
25
|
+
|
26
|
+
self.tid = doc.at('input[name=tid]')['value']
|
27
|
+
url = doc.at('form[name=login]')['action']
|
28
|
+
|
29
|
+
postdata = "appid=0158&lang=de&login=Login&pwd=%s&skinid=30&tid=%s&usr=%s" % [
|
30
|
+
CGI.escape(password),
|
31
|
+
CGI.escape(self.tid),
|
32
|
+
CGI.escape(username)
|
33
|
+
]
|
34
|
+
|
35
|
+
data, resp, self.cookies, forward = post(url, postdata, self.cookies)
|
36
|
+
|
37
|
+
if forward.nil? || !forward.match("loadUser")
|
38
|
+
raise AuthenticationError, "Username and password do not match"
|
39
|
+
end
|
40
|
+
|
41
|
+
data, resp, self.cookies, forward = get(forward, self.cookies)
|
42
|
+
end
|
43
|
+
|
44
|
+
def contacts
|
45
|
+
@contacts = []
|
46
|
+
|
47
|
+
data, resp, self.cookies, forward = get(ADDRESS_BOOK_URL, self.cookies)
|
48
|
+
|
49
|
+
CSV.parse(data) do |row|
|
50
|
+
other, first_name, last_name, email = row
|
51
|
+
|
52
|
+
name = "#{first_name} #{last_name}".strip
|
53
|
+
email.strip!
|
54
|
+
|
55
|
+
next unless email.include?('@')
|
56
|
+
|
57
|
+
@contacts << [name, email]
|
58
|
+
end
|
59
|
+
|
60
|
+
@contacts
|
61
|
+
end
|
62
|
+
|
63
|
+
def skip_gzip?
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def username
|
70
|
+
@login.split('@').first
|
71
|
+
end
|
72
|
+
|
73
|
+
def domain
|
74
|
+
@login.split('@').last
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
TYPES[:tonline_de] = TonlineDe
|
79
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Contacts
|
2
|
+
class WebDe < Base
|
3
|
+
DETECTED_DOMAINS = [ /web\.de/i ]
|
4
|
+
LOGIN_URL = "https://uas2.uilogin.de/centrallogin-3.1/login"
|
5
|
+
ADDRESS_BOOK_URL = "https://mm.web.de/contacts"
|
6
|
+
|
7
|
+
|
8
|
+
attr_accessor :cookies
|
9
|
+
|
10
|
+
def real_connect
|
11
|
+
postdata = "serviceID=%s&username=%s&password=%s" % [
|
12
|
+
CGI.escape('mobile.web.mail.webde.live'),
|
13
|
+
CGI.escape(login),
|
14
|
+
CGI.escape(password)
|
15
|
+
]
|
16
|
+
|
17
|
+
data, resp, self.cookies, forward = post(LOGIN_URL, postdata, "")
|
18
|
+
|
19
|
+
if !forward.index("/success")
|
20
|
+
raise AuthenticationError, "Username and password do not match"
|
21
|
+
end
|
22
|
+
|
23
|
+
data, resp, self.cookies, forward = get(forward, self.cookies)
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def contacts
|
28
|
+
url = ADDRESS_BOOK_URL
|
29
|
+
@contacts = []
|
30
|
+
|
31
|
+
begin
|
32
|
+
data, resp, self.cookies, forward = get(url, self.cookies)
|
33
|
+
data, resp, cookies, forward = get(forward, self.cookies)
|
34
|
+
|
35
|
+
doc = Nokogiri(data)
|
36
|
+
|
37
|
+
(doc/'ul[id=addressLines]/li').each do |li|
|
38
|
+
links = (li/'a')
|
39
|
+
|
40
|
+
name = links[0].text.strip
|
41
|
+
name = name.split(', ').reverse.join(' ')
|
42
|
+
|
43
|
+
next if links[1].nil?
|
44
|
+
|
45
|
+
match = links[1]['href'].match('to=([^&]+)')
|
46
|
+
|
47
|
+
next if !match
|
48
|
+
|
49
|
+
email = match[1].strip
|
50
|
+
|
51
|
+
@contacts << [ name, email ]
|
52
|
+
end
|
53
|
+
|
54
|
+
a_next = doc.at('a[id=go-next]')
|
55
|
+
|
56
|
+
unless a_next.nil?
|
57
|
+
url = ADDRESS_BOOK_URL + '?' + a_next[:href].split('?')[1]
|
58
|
+
else
|
59
|
+
url = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
end while !url.nil?
|
63
|
+
|
64
|
+
@contacts
|
65
|
+
end
|
66
|
+
|
67
|
+
def skip_gzip?
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def header_row?(row)
|
74
|
+
row[0] == 'Last Name'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
TYPES[:web_de] = WebDe
|
79
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Yahoo < Base
|
3
|
+
DETECTED_DOMAINS = [ /yahoo/i, /ymail/i, /rocketmail/i ]
|
4
|
+
URL = "http://mail.yahoo.com/"
|
5
|
+
LOGIN_URL = "https://login.yahoo.com/config/login"
|
6
|
+
ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?.rand=430244936"
|
7
|
+
CONTACT_LIST_URL = "http://address.mail.yahoo.com/?_src=&_crumb=crumb&sortfield=3&bucket=1&scroll=1&VPC=social_list&.r=time"
|
8
|
+
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"
|
9
|
+
|
10
|
+
def real_connect
|
11
|
+
postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
|
12
|
+
postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
|
13
|
+
postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
|
14
|
+
postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
|
15
|
+
|
16
|
+
data, resp, cookies, forward = post(LOGIN_URL, postdata)
|
17
|
+
|
18
|
+
if data.index("Invalid ID or password") || data.index("This ID is not yet taken")
|
19
|
+
# raise AuthenticationError, "Username and password do not match"
|
20
|
+
elsif data.index("Sign in") && data.index("to Yahoo!")
|
21
|
+
# raise AuthenticationError, "Required field must not be blank"
|
22
|
+
elsif !data.match(/uncompressed\/chunked/)
|
23
|
+
# raise ConnectionError, PROTOCOL_ERROR
|
24
|
+
elsif cookies == ""
|
25
|
+
# raise ConnectionError, PROTOCOL_ERROR
|
26
|
+
end
|
27
|
+
|
28
|
+
data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
|
29
|
+
|
30
|
+
if resp.code_type != Net::HTTPOK
|
31
|
+
# raise ConnectionError, PROTOCOL_ERROR
|
32
|
+
end
|
33
|
+
|
34
|
+
@cookies = cookies
|
35
|
+
end
|
36
|
+
|
37
|
+
def contacts
|
38
|
+
return @contacts if @contacts
|
39
|
+
@contacts = []
|
40
|
+
|
41
|
+
if connected?
|
42
|
+
# first, get the addressbook site with the new crumb parameter
|
43
|
+
url = URI.parse(address_book_url)
|
44
|
+
http = open_http(url)
|
45
|
+
resp = http.get("#{url.path}?#{url.query}",
|
46
|
+
"Cookie" => @cookies
|
47
|
+
)
|
48
|
+
|
49
|
+
if resp.code_type != Net::HTTPOK
|
50
|
+
# raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
51
|
+
end
|
52
|
+
|
53
|
+
crumb = resp.body.to_s[/dotCrumb: '(.*?)'/][13...-1]
|
54
|
+
|
55
|
+
# now proceed with the new ".crumb" parameter to get the csv data
|
56
|
+
url = URI.parse(contact_list_url.sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
|
57
|
+
http = open_http(url)
|
58
|
+
resp = http.get("#{url.path}?#{url.query}",
|
59
|
+
"Cookie" => @cookies,
|
60
|
+
"X-Requested-With" => "XMLHttpRequest",
|
61
|
+
"Referer" => address_book_url
|
62
|
+
)
|
63
|
+
|
64
|
+
if resp.code_type != Net::HTTPOK
|
65
|
+
# raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
66
|
+
end
|
67
|
+
|
68
|
+
if resp.body =~ /"TotalABContacts":(\d+)/
|
69
|
+
total = $1.to_i
|
70
|
+
((total / 50.0).ceil).times do |i|
|
71
|
+
# now proceed with the new ".crumb" parameter to get the csv data
|
72
|
+
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]))
|
73
|
+
http = open_http(url)
|
74
|
+
resp = http.get("#{url.path}?#{url.query}",
|
75
|
+
"Cookie" => @cookies,
|
76
|
+
"X-Requested-With" => "XMLHttpRequest",
|
77
|
+
"Referer" => address_book_url
|
78
|
+
)
|
79
|
+
|
80
|
+
if resp.code_type != Net::HTTPOK
|
81
|
+
# raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
82
|
+
end
|
83
|
+
|
84
|
+
parse resp.body
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@contacts
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def parse(data, options={})
|
95
|
+
@contacts ||= []
|
96
|
+
@contacts += Contacts.parse_json(data)["response"]["ResultSet"]["Contacts"].to_a.select{|contact|!contact["email"].to_s.empty?}.map do |contact|
|
97
|
+
name = contact["contactName"].split(",")
|
98
|
+
[[name.pop, name.join(",")].join(" ").strip, contact["email"]]
|
99
|
+
end if data =~ /^\{"response":/
|
100
|
+
@contacts
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
TYPES[:yahoo] = Yahoo
|
106
|
+
end
|