toferboy-contacts 1.2.3 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +5 -55
- data/Rakefile +2 -2
- data/lib/contacts/{aol.rb → aol_importer.rb} +66 -67
- data/lib/contacts/base.rb +39 -33
- data/lib/contacts/gmail.rb +2 -5
- data/lib/contacts/hotmail.rb +26 -28
- data/lib/contacts/yahoo.rb +11 -18
- data/lib/contacts.rb +21 -3
- metadata +34 -32
data/README
CHANGED
@@ -1,57 +1,7 @@
|
|
1
|
-
|
1
|
+
=== Slimmed-down version of Contacts
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
* gem install contacts
|
8
|
-
* http://github.com/cardmagic/contacts
|
9
|
-
* git clone git://github.com/cardmagic/contacts.git
|
10
|
-
|
11
|
-
== Background
|
12
|
-
|
13
|
-
For a long time, the only way to get a list of contacts from your free online email accounts was with proprietary PHP scripts that would cost you $50. The act of grabbing that list is a simple matter of screen scrapping and this library gives you all the functionality you need. Thanks to the generosity of the highly popular Rails website MOG (http://mog.com) for allowing this library to be released open-source to the world. It is easy to extend this library to add new free email providers, so please contact the author if you would like to help.
|
14
|
-
|
15
|
-
== Usage
|
16
|
-
|
17
|
-
Contacts::Hotmail.new(login, password).contacts # => [["name", "foo@bar.com"], ["another name", "bow@wow.com"]]
|
18
|
-
Contacts::Yahoo.new(login, password).contacts
|
19
|
-
Contacts::Gmail.new(login, password).contacts
|
20
|
-
|
21
|
-
Contacts.new(:gmail, login, password).contacts
|
22
|
-
Contacts.new(:hotmail, login, password).contacts
|
23
|
-
Contacts.new(:yahoo, login, password).contacts
|
24
|
-
|
25
|
-
Contacts.guess(login, password).contacts
|
26
|
-
|
27
|
-
Notice there are three ways to use this library so that you can limit the use as much as you would like in your particular application. The Contacts.guess method will automatically concatenate all the address book contacts from each of the successful logins in the case that a username password works across multiple services.
|
28
|
-
|
29
|
-
== Captcha error
|
30
|
-
|
31
|
-
If there are too many failed attempts with the gmail login info, Google will raise a captcha response. To integrate the captcha handling, pass in the token and response via:
|
32
|
-
|
33
|
-
Contacts::Gmail.new(login, password, :captcha_token => params[:captcha_token], :captcha_response => params[:captcha_response]).contacts
|
34
|
-
|
35
|
-
== Examples
|
36
|
-
|
37
|
-
See the examples/ directory.
|
38
|
-
|
39
|
-
== Authors
|
40
|
-
|
41
|
-
* Lucas Carlson from MOG (mailto:lucas@rufy.com) - http://mog.com
|
42
|
-
|
43
|
-
== Contributors
|
44
|
-
|
45
|
-
* Britt Selvitelle from Twitter (mailto:anotherbritt@gmail.com) - http://twitter.com
|
46
|
-
* Tony Targonski from GigPark (mailto:tony@gigpark.com) - http://gigpark.com
|
47
|
-
* Waheed Barghouthi from Watwet (mailto:waheed.barghouthi@gmail.com) - http://watwet.com
|
48
|
-
* Glenn Sidney from Glenn Fu (mailto:glenn@glennfu.com) - http://glennfu.com
|
49
|
-
* Brian McQuay from Onomojo (mailto:brian@onomojo.com) - http://onomojo.com
|
50
|
-
* Adam Hunter (mailto:adamhunter@me.com) - http://adamhunter.me/
|
51
|
-
* Glenn Ford (mailto:glenn@glennfu.com) - http://www.glennfu.com/
|
52
|
-
* Leonardo Wong (mailto:mac@boy.name)
|
53
|
-
* Rusty Burchfield
|
54
|
-
* justintv
|
55
|
-
|
56
|
-
This library is released under the terms of the BSD.
|
3
|
+
Andreas: Install this gem by cloning down the source, and then running
|
4
|
+
gem build GEMNAME.gemspec
|
5
|
+
gem install gemname-version.gem
|
57
6
|
|
7
|
+
Contacts::Gmail.new(login, password).contacts
|
data/Rakefile
CHANGED
@@ -43,7 +43,7 @@ spec = Gem::Specification.new do |s|
|
|
43
43
|
|
44
44
|
#### Basic information.
|
45
45
|
|
46
|
-
s.name = '
|
46
|
+
s.name = 'contacts19'
|
47
47
|
s.version = PKG_VERSION
|
48
48
|
s.summary = <<-EOF
|
49
49
|
Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
|
@@ -88,4 +88,4 @@ task :stats do
|
|
88
88
|
["Library", "lib"],
|
89
89
|
["Units", "test"]
|
90
90
|
).to_s
|
91
|
-
end
|
91
|
+
end
|
@@ -1,19 +1,59 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
require 'csv'
|
3
|
+
|
1
4
|
class Contacts
|
2
|
-
|
3
|
-
require 'csv'
|
4
|
-
class Aol < Base
|
5
|
+
class AolImporter < Base
|
5
6
|
URL = "http://www.aol.com/"
|
6
7
|
LOGIN_URL = "https://my.screenname.aol.com/_cqr/login/login.psp"
|
7
8
|
LOGIN_REFERER_URL = "http://webmail.aol.com/"
|
8
9
|
LOGIN_REFERER_PATH = "sitedomain=sns.webmail.aol.com&lang=en&locale=us&authLev=0&uitype=mini&loginId=&redirType=js&xchk=false"
|
9
|
-
AOL_NUM = "
|
10
|
-
|
11
|
-
CONTACT_LIST_URL = "http://
|
12
|
-
CONTACT_LIST_CSV_URL = "http://
|
10
|
+
AOL_NUM = "32319-211" # this seems to change each time they change the protocol
|
11
|
+
|
12
|
+
CONTACT_LIST_URL = "http://mail.aol.com/#{AOL_NUM}/aol-6/en-us/Lite/ContactList.aspx?folder=Inbox&showUserFolders=False"
|
13
|
+
CONTACT_LIST_CSV_URL = "http://mail.aol.com/#{AOL_NUM}/aol-6/en-us/Lite/ABExport.aspx?command=all"
|
13
14
|
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
|
+
|
16
|
+
def contacts
|
17
|
+
postdata = {
|
18
|
+
"file" => 'contacts',
|
19
|
+
"fileType" => 'csv'
|
20
|
+
}
|
21
|
+
|
22
|
+
return @contacts if @contacts
|
23
|
+
if connected?
|
24
|
+
data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
|
25
|
+
|
26
|
+
until forward.nil?
|
27
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
28
|
+
end
|
29
|
+
|
30
|
+
if resp.code_type != Net::HTTPOK
|
31
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
32
|
+
end
|
33
|
+
|
34
|
+
# parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
|
35
|
+
doc = Hpricot(data)
|
36
|
+
(doc/:input).each do |input|
|
37
|
+
postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
|
38
|
+
end
|
39
|
+
|
40
|
+
data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
|
41
|
+
|
42
|
+
until forward.nil?
|
43
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
44
|
+
end
|
45
|
+
|
46
|
+
if data.include?("error.gif")
|
47
|
+
raise AuthenticationError, "Account invalid"
|
48
|
+
end
|
49
|
+
|
50
|
+
parse data
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
15
55
|
def real_connect
|
16
|
-
|
56
|
+
|
17
57
|
postdata = {
|
18
58
|
"loginId" => login,
|
19
59
|
"password" => password,
|
@@ -44,40 +84,40 @@ class Contacts
|
|
44
84
|
"redirType" => "",
|
45
85
|
"xchk" => "false"
|
46
86
|
}
|
47
|
-
|
87
|
+
|
48
88
|
# Get this cookie and stick it in the form to confirm to Aol that your cookies work
|
49
89
|
data, resp, cookies, forward = get(URL)
|
50
90
|
postdata["stips"] = cookie_hash_from_string(cookies)["stips"]
|
51
91
|
postdata["tst"] = cookie_hash_from_string(cookies)["tst"]
|
52
|
-
|
92
|
+
|
53
93
|
data, resp, cookies, forward, old_url = get(LOGIN_REFERER_URL, cookies) + [URL]
|
54
94
|
until forward.nil?
|
55
95
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
56
96
|
end
|
57
|
-
|
97
|
+
|
58
98
|
data, resp, cookies, forward, old_url = get("#{LOGIN_URL}?#{LOGIN_REFERER_PATH}", cookies) + [LOGIN_REFERER_URL]
|
59
99
|
until forward.nil?
|
60
100
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
61
101
|
end
|
62
|
-
|
102
|
+
|
63
103
|
doc = Hpricot(data)
|
64
104
|
(doc/:input).each do |input|
|
65
105
|
postdata["usrd"] = input.attributes["value"] if input.attributes["name"] == "usrd"
|
66
106
|
end
|
67
107
|
# parse data for <input name="usrd" value="2726212" type="hidden"> and add it to the postdata
|
68
|
-
|
108
|
+
|
69
109
|
postdata["SNS_SC"] = cookie_hash_from_string(cookies)["SNS_SC"]
|
70
110
|
postdata["SNS_LDC"] = cookie_hash_from_string(cookies)["SNS_LDC"]
|
71
111
|
postdata["LTState"] = cookie_hash_from_string(cookies)["LTState"]
|
72
112
|
# raise data.inspect
|
73
|
-
|
113
|
+
|
74
114
|
data, resp, cookies, forward, old_url = post(LOGIN_URL, h_to_query_string(postdata), cookies, LOGIN_REFERER_URL) + [LOGIN_REFERER_URL]
|
75
|
-
|
115
|
+
|
76
116
|
until forward.nil?
|
77
117
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
78
118
|
end
|
79
|
-
|
80
|
-
if data.index("Invalid
|
119
|
+
|
120
|
+
if data.index("Invalid Password, Username or Email")
|
81
121
|
raise AuthenticationError, "Username and password do not match"
|
82
122
|
elsif data.index("Required field must not be blank")
|
83
123
|
raise AuthenticationError, "Login and password must not be blank"
|
@@ -88,64 +128,23 @@ class Contacts
|
|
88
128
|
elsif cookies == ""
|
89
129
|
raise ConnectionError, PROTOCOL_ERROR
|
90
130
|
end
|
91
|
-
|
131
|
+
|
92
132
|
@cookies = cookies
|
93
133
|
end
|
94
|
-
|
95
|
-
|
96
|
-
postdata = {
|
97
|
-
"file" => 'contacts',
|
98
|
-
"fileType" => 'csv'
|
99
|
-
}
|
100
|
-
|
101
|
-
return @contacts if @contacts
|
102
|
-
if connected?
|
103
|
-
data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
|
104
|
-
|
105
|
-
until forward.nil?
|
106
|
-
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
107
|
-
end
|
108
|
-
|
109
|
-
if resp.code_type != Net::HTTPOK
|
110
|
-
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
111
|
-
end
|
112
|
-
|
113
|
-
# parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
|
114
|
-
doc = Hpricot(data)
|
115
|
-
(doc/:input).each do |input|
|
116
|
-
postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
|
117
|
-
end
|
118
|
-
|
119
|
-
data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
|
120
|
-
|
121
|
-
until forward.nil?
|
122
|
-
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
123
|
-
end
|
124
|
-
|
125
|
-
if data.include?("error.gif")
|
126
|
-
raise AuthenticationError, "Account invalid"
|
127
|
-
end
|
128
|
-
|
129
|
-
parse data
|
130
|
-
end
|
131
|
-
end
|
132
|
-
private
|
133
|
-
|
134
|
+
|
135
|
+
|
134
136
|
def parse(data, options={})
|
135
|
-
data = CSV
|
137
|
+
data = CSV.parse(data)
|
136
138
|
col_names = data.shift
|
137
139
|
@contacts = data.map do |person|
|
138
140
|
["#{person[0]} #{person[1]}", person[4]] if person[4] && !person[4].empty?
|
139
141
|
end.compact
|
140
|
-
end
|
141
|
-
|
142
|
+
end
|
143
|
+
|
142
144
|
def h_to_query_string(hash)
|
143
145
|
u = ERB::Util.method(:u)
|
144
|
-
hash.map
|
145
|
-
u.call(k) + "=" + u.call(v)
|
146
|
-
}.join("&")
|
146
|
+
hash.map{ |k, v| u.call(k) + "=" + u.call(v) }.join("&")
|
147
147
|
end
|
148
148
|
end
|
149
|
-
|
150
|
-
TYPES[:aol] = Aol
|
149
|
+
TYPES[:aolImporter] = AolImporter
|
151
150
|
end
|
data/lib/contacts/base.rb
CHANGED
@@ -9,8 +9,9 @@ require "erb"
|
|
9
9
|
|
10
10
|
class Contacts
|
11
11
|
TYPES = {}
|
12
|
-
|
13
|
-
|
12
|
+
FILETYPES = {}
|
13
|
+
VERSION = "1.4.1"
|
14
|
+
|
14
15
|
class Base
|
15
16
|
def initialize(login, password, options={})
|
16
17
|
@login = login
|
@@ -20,12 +21,12 @@ class Contacts
|
|
20
21
|
@connections = {}
|
21
22
|
connect
|
22
23
|
end
|
23
|
-
|
24
|
+
|
24
25
|
def connect
|
25
26
|
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
27
|
real_connect
|
27
28
|
end
|
28
|
-
|
29
|
+
|
29
30
|
def connected?
|
30
31
|
@cookies && !@cookies.empty?
|
31
32
|
end
|
@@ -38,19 +39,19 @@ class Contacts
|
|
38
39
|
resp, data = http.get("#{url.path}?#{url.query}",
|
39
40
|
"Cookie" => @cookies
|
40
41
|
)
|
41
|
-
|
42
|
+
|
42
43
|
if resp.code_type != Net::HTTPOK
|
43
44
|
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
44
45
|
end
|
45
|
-
|
46
|
+
|
46
47
|
parse(data, options)
|
47
48
|
end
|
48
49
|
end
|
49
|
-
|
50
|
+
|
50
51
|
def login
|
51
52
|
@attempt ||= 0
|
52
53
|
@attempt += 1
|
53
|
-
|
54
|
+
|
54
55
|
if @attempt == 1
|
55
56
|
@login
|
56
57
|
else
|
@@ -61,13 +62,13 @@ class Contacts
|
|
61
62
|
end
|
62
63
|
end
|
63
64
|
end
|
64
|
-
|
65
|
+
|
65
66
|
def password
|
66
67
|
@password
|
67
68
|
end
|
68
|
-
|
69
|
+
|
69
70
|
private
|
70
|
-
|
71
|
+
|
71
72
|
def domain
|
72
73
|
@d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
|
73
74
|
end
|
@@ -94,16 +95,16 @@ class Contacts
|
|
94
95
|
http.start unless http.started?
|
95
96
|
http
|
96
97
|
end
|
97
|
-
|
98
|
+
|
98
99
|
def cookie_hash_from_string(cookie_string)
|
99
100
|
cookie_string.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
|
100
101
|
end
|
101
|
-
|
102
|
+
|
102
103
|
def parse_cookies(data, existing="")
|
103
104
|
return existing if data.nil?
|
104
105
|
|
105
106
|
cookies = cookie_hash_from_string(existing)
|
106
|
-
|
107
|
+
|
107
108
|
data.gsub!(/ ?[\w]+=EXPIRED;/,'')
|
108
109
|
data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
|
109
110
|
data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
|
@@ -112,7 +113,7 @@ class Contacts
|
|
112
113
|
data.gsub!(/(,\s*){2,}/,', ')
|
113
114
|
data.sub!(/^,\s*/,'')
|
114
115
|
data.sub!(/\s*,$/,'')
|
115
|
-
|
116
|
+
|
116
117
|
data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
|
117
118
|
k, v = data.split("=", 2).map{|j|j.strip}
|
118
119
|
if cookies[k] && v.empty?
|
@@ -121,14 +122,14 @@ class Contacts
|
|
121
122
|
cookies[k] = v
|
122
123
|
end
|
123
124
|
end
|
124
|
-
|
125
|
+
|
125
126
|
cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
|
126
127
|
end
|
127
|
-
|
128
|
+
|
128
129
|
def remove_cookie(cookie, cookies)
|
129
130
|
parse_cookies("#{cookie}=", cookies)
|
130
131
|
end
|
131
|
-
|
132
|
+
|
132
133
|
def post(url, postdata, cookies="", referer="")
|
133
134
|
url = URI.parse(url)
|
134
135
|
http = open_http(url)
|
@@ -143,12 +144,12 @@ class Contacts
|
|
143
144
|
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
144
145
|
forward = resp.response['Location']
|
145
146
|
forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
|
146
|
-
|
147
|
-
|
148
|
-
|
147
|
+
if (not forward.nil?) && URI.parse(forward).host.nil?
|
148
|
+
forward = url.scheme.to_s + "://" + url.host.to_s + forward
|
149
|
+
end
|
149
150
|
return data, resp, cookies, forward
|
150
151
|
end
|
151
|
-
|
152
|
+
|
152
153
|
def get(url, cookies="", referer="")
|
153
154
|
url = URI.parse(url)
|
154
155
|
http = open_http(url)
|
@@ -161,12 +162,12 @@ class Contacts
|
|
161
162
|
data = uncompress(resp, data)
|
162
163
|
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
163
164
|
forward = resp.response['Location']
|
164
|
-
|
165
|
-
|
166
|
-
|
165
|
+
if (not forward.nil?) && URI.parse(forward).host.nil?
|
166
|
+
forward = url.scheme.to_s + "://" + url.host.to_s + forward
|
167
|
+
end
|
167
168
|
return data, resp, cookies, forward
|
168
169
|
end
|
169
|
-
|
170
|
+
|
170
171
|
def uncompress(resp, data)
|
171
172
|
case resp.response['content-encoding']
|
172
173
|
when 'gzip'
|
@@ -184,27 +185,32 @@ class Contacts
|
|
184
185
|
data
|
185
186
|
end
|
186
187
|
end
|
187
|
-
|
188
|
+
|
188
189
|
class ContactsError < StandardError
|
189
190
|
end
|
190
|
-
|
191
|
+
|
191
192
|
class AuthenticationError < ContactsError
|
192
193
|
end
|
193
194
|
|
194
195
|
class ConnectionError < ContactsError
|
195
196
|
end
|
196
|
-
|
197
|
+
|
197
198
|
class TypeNotFound < ContactsError
|
198
199
|
end
|
199
|
-
|
200
|
-
def self.new(type, login, password, options={})
|
200
|
+
|
201
|
+
def self.new(type, login, password="", secret_key="", options={})
|
202
|
+
if !password.nil? && password != '' && !secret_key.nil? && secret_key != ''
|
203
|
+
password = Encryptor.decrypt(URI.unescape(password), :key => secret_key)
|
204
|
+
end
|
201
205
|
if TYPES.include?(type.to_s.intern)
|
202
206
|
TYPES[type.to_s.intern].new(login, password, options)
|
207
|
+
elsif FILETYPES.include?(type.to_s.intern)
|
208
|
+
FILETYPES[type.to_s.intern].new(login)
|
203
209
|
else
|
204
|
-
raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
|
210
|
+
raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect} or #{FILETYPES.keys.inspect}"
|
205
211
|
end
|
206
212
|
end
|
207
|
-
|
213
|
+
|
208
214
|
def self.guess(login, password, options={})
|
209
215
|
TYPES.inject([]) do |a, t|
|
210
216
|
begin
|
data/lib/contacts/gmail.rb
CHANGED
data/lib/contacts/hotmail.rb
CHANGED
@@ -3,19 +3,19 @@ class Contacts
|
|
3
3
|
URL = "https://login.live.com/login.srf?id=2"
|
4
4
|
OLD_CONTACT_LIST_URL = "http://%s/cgi-bin/addresses"
|
5
5
|
NEW_CONTACT_LIST_URL = "http://%s/mail/GetContacts.aspx"
|
6
|
-
CONTACT_LIST_URL = "http://mpeople.live.com/default.aspx?pg=0"
|
6
|
+
CONTACT_LIST_URL = "http://mpeople.live.com/default.aspx?pg=0"
|
7
7
|
COMPOSE_URL = "http://%s/cgi-bin/compose?"
|
8
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
9
|
PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
|
10
10
|
MAX_HTTP_THREADS = 8
|
11
|
-
|
11
|
+
|
12
12
|
def real_connect
|
13
13
|
data, resp, cookies, forward = get(URL)
|
14
14
|
old_url = URL
|
15
15
|
until forward.nil?
|
16
16
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
postdata = "PPSX=%s&PwdPad=%s&login=%s&passwd=%s&LoginOptions=2&PPFT=%s" % [
|
20
20
|
CGI.escape(data.split("><").grep(/PPSX/).first[/=\S+$/][2..-3]),
|
21
21
|
PWDPAD[0...(PWDPAD.length-@password.length)],
|
@@ -23,15 +23,15 @@ class Contacts
|
|
23
23
|
CGI.escape(password),
|
24
24
|
CGI.escape(data.split("><").grep(/PPFT/).first[/=\S+$/][2..-3])
|
25
25
|
]
|
26
|
-
|
26
|
+
|
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
30
|
old_url = form_url
|
31
31
|
until cookies =~ /; PPAuth=/ || forward.nil?
|
32
32
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
if data.index("The e-mail address or password is incorrect")
|
36
36
|
raise AuthenticationError, "Username and password do not match"
|
37
37
|
elsif data != ""
|
@@ -39,12 +39,12 @@ class Contacts
|
|
39
39
|
elsif cookies == ""
|
40
40
|
raise ConnectionError, PROTOCOL_ERROR
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
|
44
44
|
until forward.nil?
|
45
45
|
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
@domain = URI.parse(old_url).host
|
49
49
|
@cookies = cookies
|
50
50
|
rescue AuthenticationError => m
|
@@ -54,38 +54,38 @@ class Contacts
|
|
54
54
|
raise m
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
def contacts(options = {})
|
59
59
|
if connected?
|
60
60
|
url = URI.parse(contact_list_url)
|
61
61
|
data, resp, cookies, forward = get( contact_list_url, @cookies )
|
62
|
-
|
62
|
+
|
63
63
|
if resp.code_type != Net::HTTPOK
|
64
64
|
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
@contacts = []
|
68
68
|
build_contacts = []
|
69
69
|
go = true
|
70
70
|
index = 0
|
71
|
-
|
71
|
+
|
72
72
|
while(go) do
|
73
73
|
go = false
|
74
74
|
url = URI.parse(get_contact_list_url(index))
|
75
75
|
http = open_http(url)
|
76
76
|
resp, data = http.get(get_contact_list_url(index), "Cookie" => @cookies)
|
77
|
-
|
77
|
+
|
78
78
|
email_match_text_beginning = Regexp.escape("http://m.mail.live.com/?rru=compose&to=")
|
79
79
|
email_match_text_end = Regexp.escape("&")
|
80
|
-
|
81
|
-
raw_html = resp.body.grep(/(?:e|dn)lk[0-9]+/)
|
80
|
+
|
81
|
+
raw_html = resp.body.split("\n").grep(/(?:e|dn)lk[0-9]+/)
|
82
82
|
raw_html.delete_at 0
|
83
|
-
raw_html.inject do |memo, row|
|
83
|
+
raw_html.inject('') do |memo, row|
|
84
84
|
c_info = row.match(/(e|dn)lk([0-9])+/)
|
85
|
-
|
85
|
+
|
86
86
|
# Same contact, or different?
|
87
87
|
build_contacts << [] if memo != c_info[2]
|
88
|
-
|
88
|
+
|
89
89
|
# Grab info
|
90
90
|
case c_info[1]
|
91
91
|
when "e" # Email
|
@@ -93,32 +93,30 @@ class Contacts
|
|
93
93
|
when "dn" # Name
|
94
94
|
build_contacts.last[0] = row.match(/<a[^>]*>(.+)<\/a>/)[1]
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
# Set memo to contact id
|
98
98
|
c_info[2]
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
go = resp.body.include?("ContactList_next")
|
102
102
|
index += 1
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
build_contacts.each do |contact|
|
106
106
|
unless contact[1].nil?
|
107
107
|
# Only return contacts with email addresses
|
108
108
|
contact[1] = CGI::unescape(contact[1])
|
109
|
+
contact[1] = contact[1].gsub(/&ru.*/, '').gsub(/%40/, '@')
|
109
110
|
@contacts << contact
|
110
111
|
end
|
111
112
|
end
|
112
113
|
return @contacts
|
113
114
|
end
|
114
115
|
end
|
115
|
-
|
116
|
-
def get_contact_list_url(index)
|
116
|
+
|
117
|
+
def get_contact_list_url(index)
|
117
118
|
"http://mpeople.live.com/default.aspx?pg=#{index}"
|
118
119
|
end
|
119
|
-
|
120
|
-
private
|
121
|
-
|
122
|
-
TYPES[:hotmail] = Hotmail
|
123
120
|
end
|
124
|
-
|
121
|
+
TYPES[:hotmail] = Hotmail
|
122
|
+
end
|
data/lib/contacts/yahoo.rb
CHANGED
@@ -61,16 +61,12 @@ class Contacts
|
|
61
61
|
if resp.code_type != Net::HTTPOK
|
62
62
|
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
63
63
|
end
|
64
|
-
|
65
|
-
parse data
|
66
|
-
|
67
|
-
#parse more_data
|
68
|
-
|
64
|
+
|
69
65
|
if more_data =~ /"TotalABContacts":(\d+)/
|
70
66
|
total = $1.to_i
|
71
|
-
((total / 50)).times do |i|
|
67
|
+
((total / 50.0).ceil).times do |i|
|
72
68
|
# now proceed with the new ".crumb" parameter to get the csv data
|
73
|
-
url = URI.parse(contact_list_url.sub("bucket=1","bucket=#{i
|
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]))
|
74
70
|
http = open_http(url)
|
75
71
|
resp, more_data = http.get("#{url.path}?#{url.query}",
|
76
72
|
"Cookie" => @cookies,
|
@@ -81,28 +77,25 @@ class Contacts
|
|
81
77
|
if resp.code_type != Net::HTTPOK
|
82
78
|
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
83
79
|
end
|
84
|
-
|
80
|
+
|
85
81
|
parse more_data
|
86
82
|
end
|
87
83
|
end
|
88
|
-
|
84
|
+
|
89
85
|
@contacts
|
90
86
|
end
|
91
87
|
end
|
92
88
|
|
93
89
|
private
|
94
|
-
|
90
|
+
|
95
91
|
def parse(data, options={})
|
96
92
|
@contacts ||= []
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
@contacts
|
103
|
-
end
|
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(" "), contact["email"]]
|
96
|
+
end if data =~ /^\{"response":/
|
97
|
+
@contacts
|
104
98
|
end
|
105
|
-
|
106
99
|
end
|
107
100
|
|
108
101
|
TYPES[:yahoo] = Yahoo
|
data/lib/contacts.rb
CHANGED
@@ -1,11 +1,29 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__)+"/contacts/")
|
2
2
|
|
3
|
+
## Use ActiveSupport's version of JSON if available
|
4
|
+
#if Object.const_defined?('ActiveSupport') && ActiveSupport.const_defined?('JSON')
|
5
|
+
# module ActiveSupportJsonParseFunction
|
6
|
+
# def parse(i)
|
7
|
+
# ActiveSupport::JSON.decode(i)
|
8
|
+
# end
|
9
|
+
# end
|
10
|
+
# # newer versions of ActiveSupport define a root JSON module to extend...
|
11
|
+
# if Object.const_defined?('JSON')
|
12
|
+
# JSON.send(:extend, ActiveSupportJsonParseFunction)
|
13
|
+
# else
|
14
|
+
# # ... older need it defined from scratch
|
15
|
+
# class JSON
|
16
|
+
# extend ActiveSupportJsonParseFunction
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#else
|
20
|
+
# require 'json/add/rails'
|
21
|
+
#end
|
22
|
+
|
3
23
|
require 'rubygems'
|
4
24
|
|
25
|
+
require 'json_picker'
|
5
26
|
require 'base'
|
6
27
|
require 'gmail'
|
7
28
|
require 'hotmail'
|
8
29
|
require 'yahoo'
|
9
|
-
require 'plaxo'
|
10
|
-
require 'aol'
|
11
|
-
require 'json_picker'
|
metadata
CHANGED
@@ -1,21 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toferboy-contacts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 2
|
9
|
-
- 3
|
10
|
-
version: 1.2.3
|
4
|
+
prerelease:
|
5
|
+
version: 1.4.1
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Lucas Carlson
|
9
|
+
- Paperless
|
14
10
|
autorequire:
|
15
11
|
bindir: bin
|
16
12
|
cert_chain: []
|
17
13
|
|
18
|
-
date: 2010-
|
14
|
+
date: 2010-09-20 00:00:00 -07:00
|
19
15
|
default_executable:
|
20
16
|
dependencies:
|
21
17
|
- !ruby/object:Gem::Dependency
|
@@ -26,32 +22,44 @@ dependencies:
|
|
26
22
|
requirements:
|
27
23
|
- - ">="
|
28
24
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 17
|
30
|
-
segments:
|
31
|
-
- 1
|
32
|
-
- 1
|
33
|
-
- 1
|
34
25
|
version: 1.1.1
|
35
26
|
type: :runtime
|
36
27
|
version_requirements: *id001
|
37
28
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
29
|
+
name: gdata19
|
39
30
|
prerelease: false
|
40
31
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
32
|
none: false
|
42
33
|
requirements:
|
43
34
|
- - ">="
|
44
35
|
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
segments:
|
47
|
-
- 1
|
48
|
-
- 1
|
49
|
-
- 1
|
50
|
-
version: 1.1.1
|
36
|
+
version: "0"
|
51
37
|
type: :runtime
|
52
38
|
version_requirements: *id002
|
53
|
-
|
54
|
-
|
39
|
+
- !ruby/object:Gem::Dependency
|
40
|
+
name: hpricot
|
41
|
+
prerelease: false
|
42
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id003
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: encryptor
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
type: :runtime
|
60
|
+
version_requirements: *id004
|
61
|
+
description: A universal interface to grab contact list information from various providers including Outlook, Address Book, Yahoo, AOL, Gmail, Hotmail, and Plaxo.
|
62
|
+
email:
|
55
63
|
executables: []
|
56
64
|
|
57
65
|
extensions: []
|
@@ -67,12 +75,12 @@ files:
|
|
67
75
|
- lib/contacts/base.rb
|
68
76
|
- lib/contacts/json_picker.rb
|
69
77
|
- lib/contacts/gmail.rb
|
70
|
-
- lib/contacts/
|
78
|
+
- lib/contacts/aol_importer.rb
|
71
79
|
- lib/contacts/hotmail.rb
|
72
80
|
- lib/contacts/plaxo.rb
|
73
81
|
- lib/contacts/yahoo.rb
|
74
82
|
has_rdoc: true
|
75
|
-
homepage: http://github.com/
|
83
|
+
homepage: http://github.com/paperlesspost/contacts
|
76
84
|
licenses: []
|
77
85
|
|
78
86
|
post_install_message:
|
@@ -85,25 +93,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
93
|
requirements:
|
86
94
|
- - ">="
|
87
95
|
- !ruby/object:Gem::Version
|
88
|
-
hash: 3
|
89
|
-
segments:
|
90
|
-
- 0
|
91
96
|
version: "0"
|
92
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
98
|
none: false
|
94
99
|
requirements:
|
95
100
|
- - ">="
|
96
101
|
- !ruby/object:Gem::Version
|
97
|
-
hash: 3
|
98
|
-
segments:
|
99
|
-
- 0
|
100
102
|
version: "0"
|
101
103
|
requirements: []
|
102
104
|
|
103
105
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.
|
106
|
+
rubygems_version: 1.6.2
|
105
107
|
signing_key:
|
106
108
|
specification_version: 3
|
107
|
-
summary: A universal interface to grab contact list information from various providers including Yahoo, AOL, Gmail, Hotmail, and Plaxo.
|
109
|
+
summary: A universal interface to grab contact list information from various providers including Outlook, Address Book, Yahoo, AOL, Gmail, Hotmail, and Plaxo.
|
108
110
|
test_files: []
|
109
111
|
|