contacts_cn_19 1.0.0 → 1.0.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/lib/contacts/aol.rb +154 -0
- data/lib/contacts/base.rb +232 -0
- data/lib/contacts/gmail.rb +35 -0
- data/lib/contacts/hash_ext.rb +9 -0
- data/lib/contacts/hotmail.rb +93 -0
- data/lib/contacts/json_picker.rb +17 -0
- data/lib/contacts/net_ease.rb +118 -0
- data/lib/contacts/plaxo.rb +130 -0
- data/lib/contacts/sina.rb +92 -0
- data/lib/contacts/sohu.rb +59 -0
- data/lib/contacts/yahoo.rb +103 -0
- data/lib/contacts_cn_19.rb +1 -5
- metadata +12 -1
data/lib/contacts/aol.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
class Contacts
|
2
|
+
require 'hpricot'
|
3
|
+
require 'csv'
|
4
|
+
class Aol < Base
|
5
|
+
URL = "http://www.aol.com/"
|
6
|
+
LOGIN_URL = "https://my.screenname.aol.com/_cqr/login/login.psp"
|
7
|
+
LOGIN_REFERER_URL = "http://webmail.aol.com/"
|
8
|
+
LOGIN_REFERER_PATH = "sitedomain=sns.webmail.aol.com&lang=en&locale=us&authLev=0&uitype=mini&loginId=&redirType=js&xchk=false"
|
9
|
+
AOL_NUM = "29970-343" # this seems to change each time they change the protocol
|
10
|
+
|
11
|
+
CONTACT_LIST_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ContactList.aspx?folder=Inbox&showUserFolders=False"
|
12
|
+
CONTACT_LIST_CSV_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ABExport.aspx?command=all"
|
13
|
+
PROTOCOL_ERROR = "AOL has changed its protocols, please upgrade this library first. If that does not work, dive into the code and submit a patch at http://github.com/cardmagic/contacts"
|
14
|
+
|
15
|
+
def real_connect
|
16
|
+
if login.strip =~ /^(.+)@aol\.com$/ # strip off the @aol.com for AOL logins
|
17
|
+
login = $1
|
18
|
+
end
|
19
|
+
|
20
|
+
postdata = {
|
21
|
+
"loginId" => login,
|
22
|
+
"password" => password,
|
23
|
+
"rememberMe" => "on",
|
24
|
+
"_sns_fg_color_" => "",
|
25
|
+
"_sns_err_color_" => "",
|
26
|
+
"_sns_link_color_" => "",
|
27
|
+
"_sns_width_" => "",
|
28
|
+
"_sns_height_" => "",
|
29
|
+
"offerId" => "mail-second-en-us",
|
30
|
+
"_sns_bg_color_" => "",
|
31
|
+
"sitedomain" => "sns.webmail.aol.com",
|
32
|
+
"regPromoCode" => "",
|
33
|
+
"mcState" => "initialized",
|
34
|
+
"uitype" => "std",
|
35
|
+
"siteId" => "",
|
36
|
+
"lang" => "en",
|
37
|
+
"locale" => "us",
|
38
|
+
"authLev" => "0",
|
39
|
+
"siteState" => "",
|
40
|
+
"isSiteStateEncoded" => "false",
|
41
|
+
"use_aam" => "0",
|
42
|
+
"seamless" => "novl",
|
43
|
+
"aolsubmit" => CGI.escape("Sign In"),
|
44
|
+
"idType" => "SN",
|
45
|
+
"usrd" => "",
|
46
|
+
"doSSL" => "",
|
47
|
+
"redirType" => "",
|
48
|
+
"xchk" => "false"
|
49
|
+
}
|
50
|
+
|
51
|
+
# Get this cookie and stick it in the form to confirm to Aol that your cookies work
|
52
|
+
data, resp, cookies, forward = get(URL)
|
53
|
+
postdata["stips"] = cookie_hash_from_string(cookies)["stips"]
|
54
|
+
postdata["tst"] = cookie_hash_from_string(cookies)["tst"]
|
55
|
+
|
56
|
+
data, resp, cookies, forward, old_url = get(LOGIN_REFERER_URL, cookies) + [URL]
|
57
|
+
until forward.nil?
|
58
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
59
|
+
end
|
60
|
+
|
61
|
+
data, resp, cookies, forward, old_url = get("#{LOGIN_URL}?#{LOGIN_REFERER_PATH}", cookies) + [LOGIN_REFERER_URL]
|
62
|
+
until forward.nil?
|
63
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
64
|
+
end
|
65
|
+
|
66
|
+
doc = Hpricot(data)
|
67
|
+
(doc/:input).each do |input|
|
68
|
+
postdata["usrd"] = input.attributes["value"] if input.attributes["name"] == "usrd"
|
69
|
+
end
|
70
|
+
# parse data for <input name="usrd" value="2726212" type="hidden"> and add it to the postdata
|
71
|
+
|
72
|
+
postdata["SNS_SC"] = cookie_hash_from_string(cookies)["SNS_SC"]
|
73
|
+
postdata["SNS_LDC"] = cookie_hash_from_string(cookies)["SNS_LDC"]
|
74
|
+
postdata["LTState"] = cookie_hash_from_string(cookies)["LTState"]
|
75
|
+
# raise data.inspect
|
76
|
+
|
77
|
+
data, resp, cookies, forward, old_url = post(LOGIN_URL, h_to_query_string(postdata), cookies, LOGIN_REFERER_URL) + [LOGIN_REFERER_URL]
|
78
|
+
|
79
|
+
until forward.nil?
|
80
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
81
|
+
end
|
82
|
+
|
83
|
+
if data.index("Invalid Username or Password. Please try again.")
|
84
|
+
raise AuthenticationError, "Username and password do not match"
|
85
|
+
elsif data.index("Required field must not be blank")
|
86
|
+
raise AuthenticationError, "Login and password must not be blank"
|
87
|
+
elsif data.index("errormsg_0_logincaptcha")
|
88
|
+
raise AuthenticationError, "Captcha error"
|
89
|
+
elsif data.index("Invalid request")
|
90
|
+
raise ConnectionError, PROTOCOL_ERROR
|
91
|
+
elsif cookies == ""
|
92
|
+
raise ConnectionError, PROTOCOL_ERROR
|
93
|
+
end
|
94
|
+
|
95
|
+
@cookies = cookies
|
96
|
+
end
|
97
|
+
|
98
|
+
def contacts
|
99
|
+
postdata = {
|
100
|
+
"file" => 'contacts',
|
101
|
+
"fileType" => 'csv'
|
102
|
+
}
|
103
|
+
|
104
|
+
return @contacts if @contacts
|
105
|
+
if connected?
|
106
|
+
data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
|
107
|
+
|
108
|
+
until forward.nil?
|
109
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
110
|
+
end
|
111
|
+
|
112
|
+
if resp.code_type != Net::HTTPOK
|
113
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
114
|
+
end
|
115
|
+
|
116
|
+
# parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
|
117
|
+
doc = Hpricot(data)
|
118
|
+
(doc/:input).each do |input|
|
119
|
+
postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
|
120
|
+
end
|
121
|
+
|
122
|
+
data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
|
123
|
+
|
124
|
+
until forward.nil?
|
125
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
126
|
+
end
|
127
|
+
|
128
|
+
if data.include?("error.gif")
|
129
|
+
raise AuthenticationError, "Account invalid"
|
130
|
+
end
|
131
|
+
|
132
|
+
parse data
|
133
|
+
end
|
134
|
+
end
|
135
|
+
private
|
136
|
+
|
137
|
+
def parse(data, options={})
|
138
|
+
data = CSV::Reader.parse(data)
|
139
|
+
col_names = data.shift
|
140
|
+
@contacts = data.map do |person|
|
141
|
+
["#{person[0]} #{person[1]}", person[4]] if person[4] && !person[4].empty?
|
142
|
+
end.compact
|
143
|
+
end
|
144
|
+
|
145
|
+
def h_to_query_string(hash)
|
146
|
+
u = ERB::Util.method(:u)
|
147
|
+
hash.map { |k, v|
|
148
|
+
u.call(k) + "=" + u.call(v)
|
149
|
+
}.join("&")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
TYPES[:aol] = Aol
|
154
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "net/http"
|
3
|
+
require "net/https"
|
4
|
+
require "uri"
|
5
|
+
require "zlib"
|
6
|
+
require "stringio"
|
7
|
+
require "thread"
|
8
|
+
require "erb"
|
9
|
+
|
10
|
+
class Contacts
|
11
|
+
TYPES = {}
|
12
|
+
VERSION = "1.2.8"
|
13
|
+
|
14
|
+
class Base
|
15
|
+
def initialize(login, password, options={})
|
16
|
+
@login = login
|
17
|
+
@password = password
|
18
|
+
@captcha_token = options[:captcha_token]
|
19
|
+
@captcha_response = options[:captcha_response]
|
20
|
+
@connections = {}
|
21
|
+
connect
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @login.empty? || @password.nil? || @password.empty?
|
26
|
+
real_connect
|
27
|
+
end
|
28
|
+
|
29
|
+
def connected?
|
30
|
+
@cookies && !@cookies.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def contacts(options = {})
|
34
|
+
return @contacts if @contacts
|
35
|
+
if connected?
|
36
|
+
url = URI.parse(contact_list_url)
|
37
|
+
http = open_http(url)
|
38
|
+
resp = http.get("#{url.path}?#{url.query}",
|
39
|
+
"Cookie" => @cookies
|
40
|
+
)
|
41
|
+
data = resp.body
|
42
|
+
|
43
|
+
if resp.code_type != Net::HTTPOK
|
44
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
45
|
+
end
|
46
|
+
|
47
|
+
parse(data, options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def login
|
52
|
+
@attempt ||= 0
|
53
|
+
@attempt += 1
|
54
|
+
|
55
|
+
if @attempt == 1
|
56
|
+
@login
|
57
|
+
else
|
58
|
+
if @login.include?("@#{domain}")
|
59
|
+
@login.sub("@#{domain}","")
|
60
|
+
else
|
61
|
+
"#{@login}@#{domain}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def login_with_domain
|
67
|
+
@login.include?("@#{domain}") ? "#{@login}" : "#{@login}@#{domain}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def login_without_domain
|
71
|
+
@login.include?("@#{domain}") ? @login.sub("@#{domain}",'') : "#{@login}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def password
|
75
|
+
@password
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def domain
|
81
|
+
@d ||= self.class.const_get(:DOMAIN) rescue nil
|
82
|
+
@d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
|
83
|
+
end
|
84
|
+
|
85
|
+
def contact_list_url
|
86
|
+
self.class.const_get(:CONTACT_LIST_URL)
|
87
|
+
end
|
88
|
+
|
89
|
+
def address_book_url
|
90
|
+
self.class.const_get(:ADDRESS_BOOK_URL)
|
91
|
+
end
|
92
|
+
|
93
|
+
def open_http(url)
|
94
|
+
c = @connections[Thread.current.object_id] ||= {}
|
95
|
+
http = c["#{url.host}:#{url.port}"]
|
96
|
+
unless http
|
97
|
+
http = Net::HTTP.new(url.host, url.port)
|
98
|
+
if url.port == 443
|
99
|
+
http.use_ssl = true
|
100
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
101
|
+
end
|
102
|
+
c["#{url.host}:#{url.port}"] = http
|
103
|
+
end
|
104
|
+
http.start unless http.started?
|
105
|
+
http
|
106
|
+
end
|
107
|
+
|
108
|
+
def cookie_hash_from_string(cookie_string)
|
109
|
+
cookie_string.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse_cookies(data, existing="")
|
113
|
+
return existing if data.nil?
|
114
|
+
|
115
|
+
cookies = cookie_hash_from_string(existing)
|
116
|
+
|
117
|
+
data.gsub!(/ ?[\w]+=EXPIRED;/,'')
|
118
|
+
data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
|
119
|
+
data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
|
120
|
+
data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
|
121
|
+
data.gsub!(/(;\s*){2,}/,', ')
|
122
|
+
data.gsub!(/(,\s*){2,}/,', ')
|
123
|
+
data.sub!(/^,\s*/,'')
|
124
|
+
data.sub!(/\s*,$/,'')
|
125
|
+
|
126
|
+
data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
|
127
|
+
k, v = data.split("=", 2).map{|j|j.strip}
|
128
|
+
if cookies[k] && v.empty?
|
129
|
+
cookies.delete(k)
|
130
|
+
elsif v && !v.empty?
|
131
|
+
cookies[k] = v
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
|
136
|
+
end
|
137
|
+
|
138
|
+
def remove_cookie(cookie, cookies)
|
139
|
+
parse_cookies("#{cookie}=", cookies)
|
140
|
+
end
|
141
|
+
|
142
|
+
def post(url, postdata, cookies="", referer="")
|
143
|
+
url = URI.parse(url)
|
144
|
+
http = open_http(url)
|
145
|
+
resp = http.post(url.path, postdata,
|
146
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
|
147
|
+
"Accept-Encoding" => "gzip",
|
148
|
+
"Cookie" => cookies,
|
149
|
+
"Referer" => referer,
|
150
|
+
"Content-Type" => 'application/x-www-form-urlencoded'
|
151
|
+
)
|
152
|
+
data = resp.body
|
153
|
+
data = uncompress(resp, data)
|
154
|
+
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
155
|
+
forward = resp.response['Location']
|
156
|
+
forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
|
157
|
+
if (not forward.nil?) && URI.parse(forward).host.nil?
|
158
|
+
forward = url.scheme.to_s + "://" + url.host.to_s + forward
|
159
|
+
end
|
160
|
+
return data, resp, cookies, forward
|
161
|
+
end
|
162
|
+
|
163
|
+
def get(url, cookies="", referer="")
|
164
|
+
url = URI.parse(url)
|
165
|
+
http = open_http(url)
|
166
|
+
resp = http.get("#{url.path}?#{url.query}",
|
167
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
|
168
|
+
"Accept-Encoding" => "gzip",
|
169
|
+
"Cookie" => cookies,
|
170
|
+
"Referer" => referer
|
171
|
+
)
|
172
|
+
data = resp.body
|
173
|
+
data = uncompress(resp, data)
|
174
|
+
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
175
|
+
forward = resp.response['Location']
|
176
|
+
if (not forward.nil?) && URI.parse(forward).host.nil?
|
177
|
+
forward = url.scheme.to_s + "://" + url.host.to_s + forward
|
178
|
+
end
|
179
|
+
return data, resp, cookies, forward
|
180
|
+
end
|
181
|
+
|
182
|
+
def uncompress(resp, data)
|
183
|
+
case resp.response['content-encoding']
|
184
|
+
when 'gzip'
|
185
|
+
gz = Zlib::GzipReader.new(StringIO.new(data))
|
186
|
+
data = gz.read
|
187
|
+
gz.close
|
188
|
+
resp.response['content-encoding'] = nil
|
189
|
+
# FIXME: Not sure what Hotmail was feeding me with their 'deflate',
|
190
|
+
# but the headers definitely were not right
|
191
|
+
when 'deflate'
|
192
|
+
data = Zlib::Inflate.inflate(data)
|
193
|
+
resp.response['content-encoding'] = nil
|
194
|
+
end
|
195
|
+
|
196
|
+
data
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
class ContactsError < StandardError
|
201
|
+
end
|
202
|
+
|
203
|
+
class AuthenticationError < ContactsError
|
204
|
+
end
|
205
|
+
|
206
|
+
class ConnectionError < ContactsError
|
207
|
+
end
|
208
|
+
|
209
|
+
class TypeNotFound < ContactsError
|
210
|
+
end
|
211
|
+
|
212
|
+
class MailServerError < ContactsError
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.new(type, login, password, options={})
|
216
|
+
if TYPES.include?(type.to_s.intern)
|
217
|
+
TYPES[type.to_s.intern].new(login, password, options)
|
218
|
+
else
|
219
|
+
raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.guess(login, password, options={})
|
224
|
+
TYPES.inject([]) do |a, t|
|
225
|
+
begin
|
226
|
+
a + t[1].new(login, password, options).contacts
|
227
|
+
rescue AuthenticationError
|
228
|
+
a
|
229
|
+
end
|
230
|
+
end.uniq
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'gdata'
|
2
|
+
|
3
|
+
class Contacts
|
4
|
+
class Gmail < Base
|
5
|
+
|
6
|
+
CONTACTS_SCOPE = 'http://www.google.com/m8/feeds/'
|
7
|
+
CONTACTS_FEED = CONTACTS_SCOPE + 'contacts/default/full/?max-results=1000'
|
8
|
+
|
9
|
+
def contacts
|
10
|
+
return @contacts if @contacts
|
11
|
+
end
|
12
|
+
|
13
|
+
def real_connect
|
14
|
+
@client = GData::Client::Contacts.new
|
15
|
+
@client.clientlogin(@login, @password, @captcha_token, @captcha_response)
|
16
|
+
|
17
|
+
feed = @client.get(CONTACTS_FEED).to_xml
|
18
|
+
|
19
|
+
@contacts = feed.elements.to_a('entry').collect do |entry|
|
20
|
+
title, email = entry.elements['title'].text, nil
|
21
|
+
entry.elements.each('gd:email') do |e|
|
22
|
+
email = e.attribute('address').value if e.attribute('primary')
|
23
|
+
end
|
24
|
+
[title, email] unless email.nil?
|
25
|
+
end
|
26
|
+
@contacts.compact!
|
27
|
+
rescue GData::Client::AuthorizationError => e
|
28
|
+
raise AuthenticationError, "Username or password are incorrect"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
TYPES[:gmail] = Gmail
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'iconv'
|
3
|
+
|
4
|
+
class Contacts
|
5
|
+
class Hotmail < Base
|
6
|
+
URL = "https://login.live.com/login.srf?id=2"
|
7
|
+
CONTACT_LIST_URL = "https://mail.live.com/mail/GetContacts.aspx"
|
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
|
+
|
11
|
+
def real_connect
|
12
|
+
|
13
|
+
data, resp, cookies, forward = get(URL)
|
14
|
+
old_url = URL
|
15
|
+
until forward.nil?
|
16
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
17
|
+
end
|
18
|
+
|
19
|
+
postdata = "PPSX=%s&PwdPad=%s&login=%s&passwd=%s&LoginOptions=2&PPFT=%s" % [
|
20
|
+
CGI.escape(data.split("><").grep(/PPSX/).first[/=\S+$/][2..-3]),
|
21
|
+
PWDPAD[0...(PWDPAD.length-@password.length)],
|
22
|
+
CGI.escape(login),
|
23
|
+
CGI.escape(password),
|
24
|
+
CGI.escape(data.split("><").grep(/PPFT/).first[/=\S+$/][2..-3])
|
25
|
+
]
|
26
|
+
|
27
|
+
form_url = data.split("><").grep(/form/).first.split[5][8..-2]
|
28
|
+
data, resp, cookies, forward = post(form_url, postdata, cookies)
|
29
|
+
|
30
|
+
old_url = form_url
|
31
|
+
until cookies =~ /; PPAuth=/ || forward.nil?
|
32
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
33
|
+
end
|
34
|
+
|
35
|
+
if data.index("The e-mail address or password is incorrect")
|
36
|
+
raise AuthenticationError, "Username and password do not match"
|
37
|
+
elsif data != ""
|
38
|
+
raise AuthenticationError, "Required field must not be blank"
|
39
|
+
elsif cookies == ""
|
40
|
+
raise ConnectionError, PROTOCOL_ERROR
|
41
|
+
end
|
42
|
+
|
43
|
+
data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
|
44
|
+
until forward.nil?
|
45
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
46
|
+
end
|
47
|
+
|
48
|
+
|
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 @contacts.nil? && connected?
|
61
|
+
url = URI.parse(contact_list_url)
|
62
|
+
data, resp, cookies, forward = get(get_contact_list_url, @cookies )
|
63
|
+
data = Iconv.conv('UTF-8//IGNORE','UTF-8',data)
|
64
|
+
data.gsub!(";",",")
|
65
|
+
data.gsub!("'","")
|
66
|
+
# data = data.gsub(/[\x80-\xff]/n,"")
|
67
|
+
|
68
|
+
@contacts = CSV.parse(data, {:headers => true, :col_sep => ','}).map do |row|
|
69
|
+
name = ""
|
70
|
+
name = row["First Name"] if !row["First Name"].nil?
|
71
|
+
name << " #{row["Last Name"]}" if !row["Last Name"].nil?
|
72
|
+
email = row["E-mail Address"] || ""
|
73
|
+
[name, email]
|
74
|
+
end
|
75
|
+
@contacts.delete_if{|x| x[1].blank?}
|
76
|
+
else
|
77
|
+
@contacts || []
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
TYPES[:hotmail] = Hotmail
|
84
|
+
|
85
|
+
# the contacts url is dynamic
|
86
|
+
# luckily it tells us where to find it
|
87
|
+
def get_contact_list_url
|
88
|
+
data = get(CONTACT_LIST_URL, @cookies)[0]
|
89
|
+
html_doc = Nokogiri::HTML(data)
|
90
|
+
html_doc.xpath("//a")[0]["href"]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
if !Object.const_defined?('ActiveSupport')
|
2
|
+
require 'json'
|
3
|
+
end
|
4
|
+
|
5
|
+
class Contacts
|
6
|
+
def self.parse_json( string )
|
7
|
+
string = string.gsub("'",'"')
|
8
|
+
if Object.const_defined?('ActiveSupport') and
|
9
|
+
ActiveSupport.const_defined?('JSON')
|
10
|
+
ActiveSupport::JSON.decode( string )
|
11
|
+
elsif Object.const_defined?('JSON')
|
12
|
+
JSON.parse( string )
|
13
|
+
else
|
14
|
+
raise 'Contacts requires JSON or Rails (with ActiveSupport::JSON)'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
class Contacts
|
2
|
+
class NetEase < Base
|
3
|
+
URL = "http://www.163.com"
|
4
|
+
LOGIN_URL = "https://reg.163.com/logins.jsp"
|
5
|
+
LoginData = {
|
6
|
+
:url2 => {
|
7
|
+
:wy163 => 'http://mail.163.com/errorpage/err_163.htm',
|
8
|
+
:wy126 => 'http://mail.126.com/errorpage/err_126.htm',
|
9
|
+
:yeah => 'http://mail.yeah.net/errorpage/err_yeah.htm'
|
10
|
+
},
|
11
|
+
:url => {
|
12
|
+
:wy163 => 'http://entry.mail.163.com/coremail/fcg/ntesdoor2?lightweight=1&verifycookie=1&language=-1&style=-1&username=%s',
|
13
|
+
:wy126 => 'http://entry.mail.126.com/cgi/ntesdoor?hid=10010102&lightweight=1&verifycookie=1&language=0&style=-1&username=%s',
|
14
|
+
:yeah => 'http://entry.mail.yeah.net/cgi/ntesdoor?lightweight=1&verifycookie=1&style=-1&username=%s'
|
15
|
+
},
|
16
|
+
:product => {
|
17
|
+
:wy163 => 'mail163',
|
18
|
+
:wy126 => 'mail126',
|
19
|
+
:yeah => 'mailyeah'
|
20
|
+
}
|
21
|
+
}
|
22
|
+
ENTER_MAIL_URL = {
|
23
|
+
:wy163 => "http://entry.mail.163.com/coremail/fcg/ntesdoor2?lightweight=1&verifycookie=1&language=-1&style=-1&username=%s",
|
24
|
+
:wy126 => "http://entry.mail.126.com/cgi/ntesdoor?hid=10010102&lightweight=1&verifycookie=1&language=0&style=-1&username=%s",
|
25
|
+
:yeah => "http://entry.mail.yeah.net/cgi/ntesdoor?lightweight=1&verifycookie=1&style=-1&username=%s"
|
26
|
+
}
|
27
|
+
|
28
|
+
CONTACT_LIST_URL = "%ss?sid=%s&func=global:sequential"
|
29
|
+
PROTOCOL_ERROR = "netease has changed its protocols, please upgrade this library first. you can also contact kamechb@gmail.com"
|
30
|
+
|
31
|
+
def initialize(login, password, options={})
|
32
|
+
@mail_type = get_mail_type(login)
|
33
|
+
super(login,password,options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def real_connect
|
37
|
+
login_for_cookies
|
38
|
+
enter_mail_server
|
39
|
+
end
|
40
|
+
|
41
|
+
def contacts
|
42
|
+
return @contacts if @contacts
|
43
|
+
if connected?
|
44
|
+
url = URI.parse(CONTACT_LIST_URL % [@mail_server,@sid])
|
45
|
+
http = open_http(url)
|
46
|
+
postdata = '<?xml version="1.0"?><object><array name="items"><object><string name="func">pab:searchContacts</string><object name="var"><array name="order"><object><string name="field">FN</string><boolean name="ignoreCase">true</boolean></object></array></object></object><object><string name="func">user:getSignatures</string></object><object><string name="func">pab:getAllGroups</string></object></array></object>'
|
47
|
+
set_header = {"Cookie" => @cookies,'Accept' => 'text/javascript','Content-Type' => 'application/xml; charset=UTF-8'}
|
48
|
+
resp = http.post("#{url.path}?#{url.query}",postdata,set_header)
|
49
|
+
data = resp.body
|
50
|
+
if resp.code_type != Net::HTTPOK
|
51
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
52
|
+
end
|
53
|
+
parse(data)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def get_mail_type(username)
|
60
|
+
if username.include?("@126.com")
|
61
|
+
:wy126
|
62
|
+
elsif username.include?("@163.com")
|
63
|
+
:wy163
|
64
|
+
elsif username.include?("@yeah.net")
|
65
|
+
:yeah
|
66
|
+
else
|
67
|
+
raise MailServerError, "there are only three mail servers that 126.com, 163.com and yeah.net. please add domain after username"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse(data)
|
72
|
+
json_data = Contacts.parse_json(data)
|
73
|
+
json_data['var'][0]['var'].map{|contactor|
|
74
|
+
[contactor['FN'],contactor['EMAIL;PREF']]
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def login_for_cookies
|
79
|
+
data = {
|
80
|
+
:type => '1',
|
81
|
+
:url => LoginData[:url][@mail_type],
|
82
|
+
:username => @login,
|
83
|
+
:password => @password,
|
84
|
+
:selType => '-1',
|
85
|
+
:remUser => '1',
|
86
|
+
:secure => 'on',
|
87
|
+
:verifycookie => '1',
|
88
|
+
:style => '-1',
|
89
|
+
:product => LoginData[:product][@mail_type],
|
90
|
+
:savelogin => '',
|
91
|
+
:url2 => LoginData[:url2][@mail_type]
|
92
|
+
}
|
93
|
+
postdata = data.to_query_string
|
94
|
+
#login and get cookie
|
95
|
+
data, resp, cookies, forward = post(LOGIN_URL,postdata)
|
96
|
+
@cookies = cookies
|
97
|
+
if data.index(LoginData[:url2][@mail_type])
|
98
|
+
raise AuthenticationError, "Username or password error"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def enter_mail_server
|
103
|
+
#get mail server and sid
|
104
|
+
enter_mail_url = ENTER_MAIL_URL[@mail_type] % @login
|
105
|
+
data, resp, cookies, forward = get(enter_mail_url,@cookies)
|
106
|
+
location = resp['Location']
|
107
|
+
data_reg = /<a.*?(http.*?)main.jsp\?sid=(.*?)\">/
|
108
|
+
location_reg = /(http.*?)main.jsp\?sid=(.*)/
|
109
|
+
unless data.match(data_reg) || location.match(location_reg)
|
110
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
111
|
+
end
|
112
|
+
@cookies = cookies
|
113
|
+
@mail_server = $1
|
114
|
+
@sid = $2
|
115
|
+
end
|
116
|
+
TYPES[:net_ease] = NetEase
|
117
|
+
end
|
118
|
+
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,92 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Sina < Base
|
3
|
+
URL = "http://mail.sina.com.cn"
|
4
|
+
LOGIN_URL = {
|
5
|
+
:sina_cn => "https://mail.sina.com.cn/cgi-bin/cnlogin.php",
|
6
|
+
:sina_com => "https://mail.sina.com.cn/cgi-bin/login.php"
|
7
|
+
}
|
8
|
+
LOGIN_COOKIE = {
|
9
|
+
:sina_cn => "sina_cn_mail_recid=true",
|
10
|
+
:sina_com => "sina_free_mail_recid=true; sina_free_mail_ltype=uid; sina_vip_mail_recid=false"
|
11
|
+
}
|
12
|
+
DOMAIN = {
|
13
|
+
:sina_cn => 'sina.cn',
|
14
|
+
:sina_com => 'sina.com'
|
15
|
+
}
|
16
|
+
PROTOCOL_ERROR = "sina has changed its protocols, please upgrade this library first. you can also contact kamechb@gmail.com"
|
17
|
+
|
18
|
+
def initialize(login, password, options={})
|
19
|
+
@mail_type = get_mail_type(login)
|
20
|
+
super(login,password,options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def real_connect
|
24
|
+
login_for_cookies
|
25
|
+
redirect_for_location
|
26
|
+
end
|
27
|
+
|
28
|
+
def contacts
|
29
|
+
return @contacts if @contacts
|
30
|
+
if connected?
|
31
|
+
data, resp, cookies, forward = get(@mail_url,@cookies)
|
32
|
+
if resp.code_type != Net::HTTPOK
|
33
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
34
|
+
end
|
35
|
+
parse(data)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def get_mail_type(username)
|
42
|
+
if username.include?("@sina.com")
|
43
|
+
:sina_com
|
44
|
+
elsif username.include?("@sina.cn")
|
45
|
+
:sina_cn
|
46
|
+
else
|
47
|
+
raise MailServerError, "there are only two mail servers that sina.com and sina.cn. please add domain after username"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse(data)
|
52
|
+
#data =~ /conf.*?contacts:.*?(\{.*?\}),\s*groups:/m
|
53
|
+
data =~ /conf.*?contacts.*?(\{.*\}).*?groups/m
|
54
|
+
contacts = $1.gsub(""",'')
|
55
|
+
contacts = ActiveSupport::JSON.decode(contacts)
|
56
|
+
contacts['contact'].map{|contactor|
|
57
|
+
[contactor['name'],contactor['email']]
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def login_for_cookies
|
62
|
+
data = {
|
63
|
+
:domain => DOMAIN[@mail_type],
|
64
|
+
:logintype => 'uid',
|
65
|
+
:u => @login,
|
66
|
+
:psw => @password,
|
67
|
+
:savelogin => 'on',
|
68
|
+
:sshchk => 'on',
|
69
|
+
:ssl => 'on'
|
70
|
+
}
|
71
|
+
data, resp, cookies, forward = post(LOGIN_URL[@mail_type],data.to_query_string,LOGIN_COOKIE[@mail_type])
|
72
|
+
login_faile_flag = %r{form.*?action.*?http.*?mail.sina.com.cn/cgi-bin/.*?login.php}m
|
73
|
+
if data.match(login_faile_flag)
|
74
|
+
raise AuthenticationError, "Username or password error"
|
75
|
+
end
|
76
|
+
data.match(/URL=(http:\/\/.*?)'>/)
|
77
|
+
@redirect_url = $1
|
78
|
+
@mail_server = @redirect_url.match(/(http:\/\/.*\..*?)\//)
|
79
|
+
@cookies = cookies
|
80
|
+
end
|
81
|
+
|
82
|
+
def redirect_for_location
|
83
|
+
data, resp, cookies, forward = get(@redirect_url,@cookies)
|
84
|
+
location = resp['Location']
|
85
|
+
@mail_url = location.index("http://") ? location : "#{@mail_server}#{location}"
|
86
|
+
@cookies = cookies
|
87
|
+
end
|
88
|
+
|
89
|
+
TYPES[:sina] = Sina
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Sohu < Base
|
3
|
+
URL = "http://mail.sohu.com"
|
4
|
+
DOMAIN = "sohu.com"
|
5
|
+
LOGIN_URL = "https://passport.sohu.com/sso/login.jsp"
|
6
|
+
LOGIN_COOKIE = "IPLOC=CN3301; SUV=1008301317090277"
|
7
|
+
MAIL_URL = "http://mail.sohu.com/bapp/117/main"
|
8
|
+
PROTOCOL_ERROR = "sohu has changed its protocols, please upgrade this library first. you can also contact kamechb@gmail.com"
|
9
|
+
|
10
|
+
def real_connect
|
11
|
+
login_for_cookies
|
12
|
+
end
|
13
|
+
|
14
|
+
def contacts
|
15
|
+
return @contacts if @contacts
|
16
|
+
if connected?
|
17
|
+
data, resp, cookies, forward = get(MAIL_URL,@cookies)
|
18
|
+
if resp.code_type != Net::HTTPOK
|
19
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
20
|
+
end
|
21
|
+
parse(data)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
|
28
|
+
def parse(data)
|
29
|
+
data.match(/ADDRESSES.*?'(\{.*?\})';/m)
|
30
|
+
contacts = ActiveSupport::JSON.decode($1)
|
31
|
+
contacts['contact'].map{|contactor|
|
32
|
+
[contactor['nickname'],contactor['email']]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def login_for_cookies
|
37
|
+
data = {
|
38
|
+
:userid => @login,
|
39
|
+
:password => @password,
|
40
|
+
:appid => '1000',
|
41
|
+
:persistentcookie => '0',
|
42
|
+
:s => '1283173792650',
|
43
|
+
:b => '2',
|
44
|
+
:w => '1280',
|
45
|
+
:pwdtype => '0',
|
46
|
+
:v => '26'
|
47
|
+
}
|
48
|
+
data, resp, cookies, forward = get("#{LOGIN_URL}?#{data.to_query_string}",LOGIN_COOKIE)
|
49
|
+
login_faile_flag = %r{login_status.*?error}
|
50
|
+
if data.match(login_faile_flag)
|
51
|
+
raise AuthenticationError, "Username or password error"
|
52
|
+
end
|
53
|
+
@cookies = cookies
|
54
|
+
end
|
55
|
+
|
56
|
+
TYPES[:sohu] = Sohu
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Yahoo < Base
|
3
|
+
URL = "http://mail.yahoo.com/"
|
4
|
+
LOGIN_URL = "https://login.yahoo.com/config/login"
|
5
|
+
ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?.rand=430244936"
|
6
|
+
CONTACT_LIST_URL = "http://address.mail.yahoo.com/?_src=&_crumb=crumb&sortfield=3&bucket=1&scroll=1&VPC=social_list&.r=time"
|
7
|
+
PROTOCOL_ERROR = "Yahoo has changed its protocols, please upgrade this library first. If that does not work, dive into the code and submit a patch at http://github.com/cardmagic/contacts"
|
8
|
+
|
9
|
+
def real_connect
|
10
|
+
postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
|
11
|
+
postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
|
12
|
+
postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
|
13
|
+
postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
|
14
|
+
|
15
|
+
data, resp, cookies, forward = post(LOGIN_URL, postdata)
|
16
|
+
|
17
|
+
if data.index("Invalid ID or password") || data.index("This ID is not yet taken")
|
18
|
+
raise AuthenticationError, "Username and password do not match"
|
19
|
+
elsif data.index("Sign in") && data.index("to Yahoo!")
|
20
|
+
raise AuthenticationError, "Required field must not be blank"
|
21
|
+
elsif !data.match(/uncompressed\/chunked/)
|
22
|
+
raise ConnectionError, PROTOCOL_ERROR
|
23
|
+
elsif cookies == ""
|
24
|
+
raise ConnectionError, PROTOCOL_ERROR
|
25
|
+
end
|
26
|
+
|
27
|
+
data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
|
28
|
+
|
29
|
+
if resp.code_type != Net::HTTPOK
|
30
|
+
raise ConnectionError, PROTOCOL_ERROR
|
31
|
+
end
|
32
|
+
|
33
|
+
@cookies = cookies
|
34
|
+
end
|
35
|
+
|
36
|
+
def contacts
|
37
|
+
return @contacts if @contacts
|
38
|
+
if connected?
|
39
|
+
# first, get the addressbook site with the new crumb parameter
|
40
|
+
url = URI.parse(address_book_url)
|
41
|
+
http = open_http(url)
|
42
|
+
resp = http.get("#{url.path}?#{url.query}",
|
43
|
+
"Cookie" => @cookies
|
44
|
+
)
|
45
|
+
data = resp.body
|
46
|
+
if resp.code_type != Net::HTTPOK
|
47
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
48
|
+
end
|
49
|
+
|
50
|
+
crumb = data.to_s[/dotCrumb: '(.*?)'/][13...-1]
|
51
|
+
|
52
|
+
# now proceed with the new ".crumb" parameter to get the csv data
|
53
|
+
url = URI.parse(contact_list_url.sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
|
54
|
+
http = open_http(url)
|
55
|
+
resp = http.get("#{url.path}?#{url.query}",
|
56
|
+
"Cookie" => @cookies,
|
57
|
+
"X-Requested-With" => "XMLHttpRequest",
|
58
|
+
"Referer" => address_book_url
|
59
|
+
)
|
60
|
+
data = resp.body
|
61
|
+
if resp.code_type != Net::HTTPOK
|
62
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
63
|
+
end
|
64
|
+
|
65
|
+
if data =~ /"TotalABContacts":(\d+)/
|
66
|
+
total = $1.to_i
|
67
|
+
((total / 50.0).ceil).times do |i|
|
68
|
+
# now proceed with the new ".crumb" parameter to get the csv data
|
69
|
+
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]))
|
70
|
+
http = open_http(url)
|
71
|
+
resp = http.get("#{url.path}?#{url.query}",
|
72
|
+
"Cookie" => @cookies,
|
73
|
+
"X-Requested-With" => "XMLHttpRequest",
|
74
|
+
"Referer" => address_book_url
|
75
|
+
)
|
76
|
+
data = resp.body
|
77
|
+
if resp.code_type != Net::HTTPOK
|
78
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
79
|
+
end
|
80
|
+
|
81
|
+
parse data
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
@contacts
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def parse(data, options={})
|
92
|
+
@contacts ||= []
|
93
|
+
@contacts += Contacts.parse_json(data)["response"]["ResultSet"]["Contacts"].to_a.select{|contact|!contact["email"].to_s.empty?}.map do |contact|
|
94
|
+
name = contact["contactName"].split(",")
|
95
|
+
[[name.pop, name.join(",")].join(" ").strip, contact["email"]]
|
96
|
+
end if data =~ /^\{"response":/
|
97
|
+
@contacts
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
TYPES[:yahoo] = Yahoo
|
103
|
+
end
|
data/lib/contacts_cn_19.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__)+"/contacts/")
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
-
unless Object.const_defined?('ActiveSupport')
|
5
|
-
require 'activesupport'
|
6
|
-
end
|
7
4
|
require 'base'
|
8
5
|
require 'gmail'
|
9
6
|
require 'hotmail'
|
@@ -14,5 +11,4 @@ require 'net_ease'
|
|
14
11
|
require 'sina'
|
15
12
|
require 'sohu'
|
16
13
|
require 'json_picker'
|
17
|
-
require 'hash_ext'
|
18
|
-
require 'iconv'
|
14
|
+
require 'hash_ext'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contacts_cn_19
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -18,6 +18,17 @@ extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
20
|
- lib/contacts_cn_19.rb
|
21
|
+
- lib/contacts/aol.rb
|
22
|
+
- lib/contacts/sohu.rb
|
23
|
+
- lib/contacts/yahoo.rb
|
24
|
+
- lib/contacts/json_picker.rb
|
25
|
+
- lib/contacts/base.rb
|
26
|
+
- lib/contacts/gmail.rb
|
27
|
+
- lib/contacts/sina.rb
|
28
|
+
- lib/contacts/hash_ext.rb
|
29
|
+
- lib/contacts/hotmail.rb
|
30
|
+
- lib/contacts/plaxo.rb
|
31
|
+
- lib/contacts/net_ease.rb
|
21
32
|
homepage: https://github.com/wxluckly/contacts_cn_19
|
22
33
|
licenses: []
|
23
34
|
post_install_message:
|