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.
@@ -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,9 @@
1
+ require 'erb'
2
+ class Hash
3
+ def to_query_string
4
+ u = ERB::Util.method(:u)
5
+ map { |k, v|
6
+ u.call(k) + "=" + u.call(v)
7
+ }.join("&")
8
+ end
9
+ 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("&quot;",'')
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
@@ -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.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: