contacts 1.0.18 → 1.0.19

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.
@@ -5,4 +5,5 @@ require 'base'
5
5
  require 'gmail'
6
6
  require 'hotmail'
7
7
  require 'yahoo'
8
- require 'plaxo'
8
+ require 'plaxo'
9
+ require 'aol'
@@ -0,0 +1,148 @@
1
+ class Hash
2
+ def to_query_string
3
+ u = ERB::Util.method(:u)
4
+ map { |k, v|
5
+ u.call(k) + "=" + u.call(v)
6
+ }.join("&")
7
+ end
8
+ end
9
+
10
+ class Contacts
11
+ require 'hpricot'
12
+ class Aol < Base
13
+ URL = "http://www.aol.com/"
14
+ LOGIN_URL = "https://my.screenname.aol.com/_cqr/login/login.psp"
15
+ LOGIN_REFERER_URL = "http://webmail.aol.com/"
16
+ LOGIN_REFERER_PATH = "sitedomain=sns.webmail.aol.com&lang=en&locale=us&authLev=0&uitype=mini&loginId=&redirType=js&xchk=false"
17
+ AOL_NUM = "29970-343" # this seems to change each time they change the protocol
18
+
19
+ CONTACT_LIST_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ContactList.aspx?folder=Inbox&showUserFolders=False"
20
+ CONTACT_LIST_CSV_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ABExport.aspx?command=all"
21
+ 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"
22
+
23
+ def real_connect
24
+
25
+ postdata = {
26
+ "loginId" => login,
27
+ "password" => password,
28
+ "rememberMe" => "on",
29
+ "_sns_fg_color_" => "",
30
+ "_sns_err_color_" => "",
31
+ "_sns_link_color_" => "",
32
+ "_sns_width_" => "",
33
+ "_sns_height_" => "",
34
+ "offerId" => "mail-second-en-us",
35
+ "_sns_bg_color_" => "",
36
+ "sitedomain" => "sns.webmail.aol.com",
37
+ "regPromoCode" => "",
38
+ "mcState" => "initialized",
39
+ "uitype" => "std",
40
+ "siteId" => "",
41
+ "lang" => "en",
42
+ "locale" => "us",
43
+ "authLev" => "0",
44
+ "siteState" => "",
45
+ "isSiteStateEncoded" => "false",
46
+ "use_aam" => "0",
47
+ "seamless" => "novl",
48
+ "aolsubmit" => CGI.escape("Sign In"),
49
+ "idType" => "SN",
50
+ "usrd" => "",
51
+ "doSSL" => "",
52
+ "redirType" => "",
53
+ "xchk" => "false"
54
+ }
55
+
56
+ # Get this cookie and stick it in the form to confirm to Aol that your cookies work
57
+ data, resp, cookies, forward = get(URL)
58
+ postdata["stips"] = cookie_hash_from_string(cookies)["stips"]
59
+ postdata["tst"] = cookie_hash_from_string(cookies)["tst"]
60
+
61
+ data, resp, cookies, forward, old_url = get(LOGIN_REFERER_URL, cookies) + [URL]
62
+ until forward.nil?
63
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
64
+ end
65
+
66
+ data, resp, cookies, forward, old_url = get("#{LOGIN_URL}?#{LOGIN_REFERER_PATH}", cookies) + [LOGIN_REFERER_URL]
67
+ until forward.nil?
68
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
69
+ end
70
+
71
+ doc = Hpricot(data)
72
+ (doc/:input).each do |input|
73
+ postdata["usrd"] = input.attributes["value"] if input.attributes["name"] == "usrd"
74
+ end
75
+ # parse data for <input name="usrd" value="2726212" type="hidden"> and add it to the postdata
76
+
77
+ postdata["SNS_SC"] = cookie_hash_from_string(cookies)["SNS_SC"]
78
+ postdata["SNS_LDC"] = cookie_hash_from_string(cookies)["SNS_LDC"]
79
+ postdata["LTState"] = cookie_hash_from_string(cookies)["LTState"]
80
+ # raise data.inspect
81
+
82
+ data, resp, cookies, forward, old_url = post(LOGIN_URL, postdata.to_query_string, cookies, LOGIN_REFERER_URL) + [LOGIN_REFERER_URL]
83
+
84
+ until forward.nil?
85
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
86
+ end
87
+
88
+ if data.index("Invalid Screen Name or Password.")
89
+ raise AuthenticationError, "Username and password do not match"
90
+ elsif data.index("Required field must not be blank")
91
+ raise AuthenticationError, "Login and password must not be blank"
92
+ elsif data.index("errormsg_0_logincaptcha")
93
+ raise AuthenticationError, "Captcha error"
94
+ elsif data.index("Invalid request")
95
+ raise ConnectionError, PROTOCOL_ERROR
96
+ elsif cookies == ""
97
+ raise ConnectionError, PROTOCOL_ERROR
98
+ end
99
+
100
+ @cookies = cookies
101
+ end
102
+
103
+ def contacts
104
+ postdata = {
105
+ "file" => 'contacts',
106
+ "fileType" => 'csv'
107
+ }
108
+
109
+ return @contacts if @contacts
110
+ if connected?
111
+ data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
112
+
113
+ until forward.nil?
114
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
115
+ end
116
+
117
+ if resp.code_type != Net::HTTPOK
118
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
119
+ end
120
+
121
+ # parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
122
+ doc = Hpricot(data)
123
+ (doc/:input).each do |input|
124
+ postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
125
+ end
126
+
127
+ data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
128
+
129
+ if forward.nil?
130
+ parse data
131
+ else
132
+ raise AuthenticationError, "Account cancelled"
133
+ end
134
+ end
135
+ end
136
+ private
137
+
138
+ def parse(data, options={})
139
+ data = CSV.parse(data)
140
+ col_names = data.shift
141
+ @contacts = data.map do |person|
142
+ ["#{person[0]} #{person[1]}", person[4]] unless person[4].empty?
143
+ end.compact
144
+ end
145
+ end
146
+
147
+ TYPES[:aol] = Aol
148
+ end
@@ -9,7 +9,7 @@ require "erb"
9
9
 
10
10
  class Contacts
11
11
  TYPES = {}
12
- VERSION = "1.0.18"
12
+ VERSION = "1.0.19"
13
13
 
14
14
  class Base
15
15
  def initialize(login, password)
@@ -1,6 +1,5 @@
1
1
  # Use ActiveSupport's version of JSON if available
2
2
  if Object.const_defined?('ActiveSupport') && ActiveSupport.const_defined?('JSON') && ActiveSupport::JSON.is_a?(Class)
3
- puts JSON.class
4
3
  class JSON
5
4
  def self.parse(i)
6
5
  ActiveSupport::JSON.decode(i)
@@ -81,7 +80,7 @@ class Contacts
81
80
  # Default format.
82
81
  # ['Name', 'Email1', 'Email2', ...]
83
82
  if @contacts != nil
84
- @contacts = @contacts.delete_if {|c| c["Emails"].nil?}.map do |c|
83
+ @contacts = @contacts.select {|c| !c["Emails"].nil?}.map do |c|
85
84
  name, emails = c.values_at "Name", "Emails"
86
85
  # emails are returned in a form of
87
86
  # [{"Address"=>"home.email@gmail.com"}, {"Type"=>{"Id"=>"WORK"}, "Address"=>"work.email@gmail.com"}]
@@ -27,6 +27,11 @@ class Contacts
27
27
  form_url = data.split("><").grep(/form/).first.split[5][8..-2]
28
28
  data, resp, cookies, forward = post(form_url, postdata, cookies)
29
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
+
30
35
  if data.index("The e-mail address or password is incorrect")
31
36
  raise AuthenticationError, "Username and password do not match"
32
37
  elsif data != ""
@@ -34,13 +39,7 @@ class Contacts
34
39
  elsif cookies == ""
35
40
  raise ConnectionError, PROTOCOL_ERROR
36
41
  end
37
-
38
- old_url = form_url
39
-
40
- until forward.nil?
41
- data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
42
- end
43
-
42
+
44
43
  data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
45
44
  until forward.nil?
46
45
  data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
@@ -1,14 +1,23 @@
1
- require 'csv'
1
+ # Use ActiveSupport's version of JSON if available
2
+ if Object.const_defined?('ActiveSupport') && ActiveSupport.const_defined?('JSON') && ActiveSupport::JSON.is_a?(Class)
3
+ class JSON
4
+ def self.parse(i)
5
+ ActiveSupport::JSON.decode(i)
6
+ end
7
+ end
8
+ else
9
+ require 'json/add/rails'
10
+ end
2
11
 
3
12
  class Contacts
4
13
  class Yahoo < Base
5
14
  URL = "http://mail.yahoo.com/"
6
15
  LOGIN_URL = "https://login.yahoo.com/config/login"
7
- ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?1&VPC=import_export"
8
- CONTACT_LIST_URL = "http://address.yahoo.com/index.php?VPC=import_export&A=B&submit[action_export_yahoo]=Export%20Now"
16
+ ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?.rand=430244936"
17
+ CONTACT_LIST_URL = "http://address.mail.yahoo.com/?_src=&_crumb=crumb&sortfield=3&bucket=1&scroll=1&VPC=social_list&.r=time"
9
18
  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"
10
19
 
11
- def real_connect
20
+ def real_connect
12
21
  postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
13
22
  postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
14
23
  postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
@@ -48,32 +57,61 @@ class Contacts
48
57
  if resp.code_type != Net::HTTPOK
49
58
  raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
50
59
  end
51
-
52
- crumb = data.to_s[/id="crumb2" value="(.*?)"/][19...-1]
60
+
61
+ crumb = data.to_s[/dotCrumb: '(.*?)'/][13...-1]
53
62
 
54
63
  # now proceed with the new ".crumb" parameter to get the csv data
55
- url = URI.parse("#{contact_list_url}&.crumb=#{crumb}")
64
+ url = URI.parse(contact_list_url.sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
56
65
  http = open_http(url)
57
- resp, data = http.get("#{url.path}?#{url.query}",
58
- "Cookie" => @cookies
66
+ resp, more_data = http.get("#{url.path}?#{url.query}",
67
+ "Cookie" => @cookies,
68
+ "X-Requested-With" => "XMLHttpRequest",
69
+ "Referer" => address_book_url
59
70
  )
60
71
 
61
72
  if resp.code_type != Net::HTTPOK
62
73
  raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
63
74
  end
64
-
75
+
65
76
  parse data
77
+
78
+ parse more_data
79
+
80
+ if more_data =~ /"TotalABContacts":(\d+)/
81
+ total = $1.to_i
82
+ ((total / 50)).times do |i|
83
+ # now proceed with the new ".crumb" parameter to get the csv data
84
+ url = URI.parse(contact_list_url.sub("bucket=1","bucket=#{i+2}").sub("_crumb=crumb","_crumb=#{crumb}").sub("time", Time.now.to_f.to_s.sub(".","")[0...-2]))
85
+ http = open_http(url)
86
+ resp, more_data = http.get("#{url.path}?#{url.query}",
87
+ "Cookie" => @cookies,
88
+ "X-Requested-With" => "XMLHttpRequest",
89
+ "Referer" => address_book_url
90
+ )
91
+
92
+ if resp.code_type != Net::HTTPOK
93
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
94
+ end
95
+
96
+ parse more_data
97
+ end
98
+ end
99
+
100
+ @contacts
66
101
  end
67
102
  end
68
103
 
69
104
  private
70
105
 
71
106
  def parse(data, options={})
72
- data = CSV.parse(data)
73
- col_names = data.shift
74
- @contacts = data.map do |person|
75
- [[person[0], person[1], person[2]].delete_if{|i|i.empty?}.join(" "), person[4]] unless person[4].empty?
76
- end.compact
107
+ @contacts ||= []
108
+ if data =~ /var InitialContacts = (\[.*?\])/
109
+ @contacts += JSON.parse($1).select{|contact|!contact["email"].to_s.empty?}.map{|contact|[contact["contactName"], contact["email"]]}
110
+ elsif data =~ /^\{"response":/
111
+ @contacts += JSON.parse(data)["response"]["ResultSet"]["Contacts"].to_a.select{|contact|!contact["email"].to_s.empty?}.map{|contact|[contact["contactName"], contact["email"]]}
112
+ else
113
+ @contacts
114
+ end
77
115
  end
78
116
 
79
117
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contacts
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.18
4
+ version: 1.0.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Carlson
@@ -38,6 +38,7 @@ files:
38
38
  - lib/contacts.rb
39
39
  - lib/contacts/base.rb
40
40
  - lib/contacts/gmail.rb
41
+ - lib/contacts/aol.rb
41
42
  - lib/contacts/hotmail.rb
42
43
  - lib/contacts/plaxo.rb
43
44
  - lib/contacts/yahoo.rb