deadprogrammer-contacts 1.0.15

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,10 @@
1
+ Copyright (c) 2006, Lucas Carlson, MOG
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+ Neither the name of the Lucas Carlson nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10
+
data/README ADDED
@@ -0,0 +1,43 @@
1
+ == Welcome to Contacts
2
+
3
+ Contacts is a universal interface to grab contact list information from various providers including Hotmail, Gmail and Yahoo.
4
+
5
+ == Download
6
+
7
+ * gem install contacts
8
+ * http://rubyforge.org/projects/contacts
9
+ * svn co svn://rubyforge.org/var/svn/contacts
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
+ == Examples
30
+
31
+ See the examples/ directory.
32
+
33
+ == Authors
34
+
35
+ * Lucas Carlson from MOG (mailto:lucas@rufy.com) - http://mog.com
36
+
37
+ == Contributors
38
+
39
+ * Britt Selvitelle from Twitter (mailto:anotherbritt@gmail.com) - http://twitter.com
40
+ * Tony Targonski from GigPark (mailto:tony@gigpark.com) - http://gigpark.com
41
+
42
+ This library is released under the terms of the BSD.
43
+
data/Rakefile ADDED
@@ -0,0 +1,90 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'rake/contrib/rubyforgepublisher'
7
+ require 'lib/contacts'
8
+
9
+ PKG_VERSION = MOG::Contacts::VERSION
10
+
11
+ PKG_FILES = FileList[
12
+ "lib/**/*", "bin/*", "test/**/*", "[A-Z]*", "Rakefile", "doc/**/*", "examples/**/*"
13
+ ]
14
+
15
+ desc "Default Task"
16
+ task :default => [ :test ]
17
+
18
+ # Run the unit tests
19
+ desc "Run all unit tests"
20
+ Rake::TestTask.new("test") { |t|
21
+ t.libs << "lib"
22
+ t.pattern = 'test/*/*_test.rb'
23
+ t.verbose = true
24
+ }
25
+
26
+ # Make a console, useful when working on tests
27
+ desc "Generate a test console"
28
+ task :console do
29
+ verbose( false ) { sh "irb -I lib/ -r 'contacts'" }
30
+ end
31
+
32
+ # Genereate the RDoc documentation
33
+ desc "Create documentation"
34
+ Rake::RDocTask.new("doc") { |rdoc|
35
+ rdoc.title = "Contact List - ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail"
36
+ rdoc.rdoc_dir = 'doc'
37
+ rdoc.rdoc_files.include('README')
38
+ rdoc.rdoc_files.include('lib/**/*.rb')
39
+ }
40
+
41
+ # Genereate the package
42
+ spec = Gem::Specification.new do |s|
43
+
44
+ #### Basic information.
45
+
46
+ s.name = 'contacts'
47
+ s.version = PKG_VERSION
48
+ s.summary = <<-EOF
49
+ Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
50
+ EOF
51
+ s.description = <<-EOF
52
+ Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
53
+ EOF
54
+
55
+ #### Which files are to be included in this gem? Everything! (Except CVS directories.)
56
+
57
+ s.files = PKG_FILES
58
+
59
+ #### Load-time details: library and application (you will need one or both).
60
+
61
+ s.require_path = 'lib'
62
+ s.autorequire = 'contacts'
63
+
64
+ s.add_dependency('json', '>= 0.4.1')
65
+ s.requirements << "A json parser"
66
+
67
+ #### Documentation and testing.
68
+
69
+ s.has_rdoc = true
70
+
71
+ #### Author and project details.
72
+
73
+ s.author = "Lucas Carlson"
74
+ s.email = "lucas@rufy.com"
75
+ s.homepage = "http://rubyforge.org/projects/contacts"
76
+ end
77
+
78
+ Rake::GemPackageTask.new(spec) do |pkg|
79
+ pkg.need_zip = true
80
+ pkg.need_tar = true
81
+ end
82
+
83
+ desc "Report code statistics (KLOCs, etc) from the application"
84
+ task :stats do
85
+ require 'code_statistics'
86
+ CodeStatistics.new(
87
+ ["Library", "lib"],
88
+ ["Units", "test"]
89
+ ).to_s
90
+ end
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__)+"/../lib/contacts"
2
+
3
+ login = ARGV[0]
4
+ password = ARGV[1]
5
+
6
+ Contacts::Gmail.new(login, password).contacts
7
+
8
+ Contacts.new(:gmail, login, password).contacts
9
+
10
+ Contacts.new("gmail", login, password).contacts
11
+
12
+ Contacts.guess(login, password).contacts
data/lib/contacts.rb ADDED
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.dirname(__FILE__)+"/contacts/")
2
+
3
+ require 'rubygems'
4
+ require 'base'
5
+ require 'gmail'
6
+ require 'hotmail'
7
+ require 'yahoo'
8
+ require 'plaxo'
@@ -0,0 +1,213 @@
1
+ require "cgi"
2
+ require "net/http"
3
+ require "net/https"
4
+ require "uri"
5
+ require "zlib"
6
+ require "stringio"
7
+ require "thread"
8
+
9
+ module MOG
10
+
11
+ class Contacts
12
+ TYPES = {}
13
+ VERSION = "1.0.13"
14
+
15
+ class Base
16
+ def initialize(login, password)
17
+ @login = login
18
+ @password = password
19
+ @connections = {}
20
+ connect
21
+ end
22
+
23
+ def connect
24
+ raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @password.nil?
25
+ real_connect
26
+ end
27
+
28
+ def connected?
29
+ @cookies && !@cookies.empty?
30
+ end
31
+
32
+ def contacts(options = {})
33
+ return @contacts if @contacts
34
+ if connected?
35
+ url = URI.parse(contact_list_url)
36
+ http = open_http(url)
37
+ resp, data = http.get("#{url.path}?#{url.query}",
38
+ "Cookie" => @cookies
39
+ )
40
+
41
+ if resp.code_type != Net::HTTPOK
42
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
43
+ end
44
+
45
+ parse(data, options)
46
+ end
47
+ end
48
+
49
+ def login
50
+ @attempt ||= 0
51
+ @attempt += 1
52
+
53
+ if @attempt == 1
54
+ @login
55
+ else
56
+ if @login.include?("@#{domain}")
57
+ @login.sub("@#{domain}","")
58
+ else
59
+ "#{@login}@#{domain}"
60
+ end
61
+ end
62
+ end
63
+
64
+ def password
65
+ @password
66
+ end
67
+
68
+ private
69
+
70
+ def domain
71
+ @d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
72
+ end
73
+
74
+ def contact_list_url
75
+ self.class.const_get(:CONTACT_LIST_URL)
76
+ end
77
+
78
+ def address_book_url
79
+ self.class.const_get(:ADDRESS_BOOK_URL)
80
+ end
81
+
82
+ def open_http(url)
83
+ c = @connections[Thread.current.object_id] ||= {}
84
+ http = c["#{url.host}:#{url.port}"]
85
+ unless http
86
+ http = Net::HTTP.new(url.host, url.port)
87
+ if url.port == 443
88
+ http.use_ssl = true
89
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
90
+ end
91
+ c["#{url.host}:#{url.port}"] = http
92
+ end
93
+ http.start unless http.started?
94
+ http
95
+ end
96
+
97
+ def parse_cookies(data, existing="")
98
+ return existing if data.nil?
99
+
100
+ cookies = existing.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
101
+
102
+ data.gsub!(/ ?[\w]+=EXPIRED;/,'')
103
+ data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
104
+ data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
105
+ data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
106
+ data.gsub!(/(;\s*){2,}/,', ')
107
+ data.gsub!(/(,\s*){2,}/,', ')
108
+ data.sub!(/^,\s*/,'')
109
+ data.sub!(/\s*,$/,'')
110
+
111
+ data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
112
+ k, v = data.split("=", 2).map{|j|j.strip}
113
+ if cookies[k] && v.empty?
114
+ cookies.delete(k)
115
+ elsif v && !v.empty?
116
+ cookies[k] = v
117
+ end
118
+ end
119
+
120
+ cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
121
+ end
122
+
123
+ def remove_cookie(cookie, cookies)
124
+ parse_cookies("#{cookie}=", cookies)
125
+ end
126
+
127
+ def post(url, postdata, cookies="", referer="")
128
+ url = URI.parse(url)
129
+ http = open_http(url)
130
+ resp, data = http.post(url.path, postdata,
131
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
132
+ "Accept-Encoding" => "gzip",
133
+ "Cookie" => cookies,
134
+ "Referer" => referer,
135
+ "Content-Type" => 'application/x-www-form-urlencoded'
136
+ )
137
+ data = uncompress(resp, data)
138
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
139
+ forward = resp.response['Location']
140
+ forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
141
+ if (not forward.nil?) && URI.parse(forward).host.nil?
142
+ forward = url.scheme.to_s + "://" + url.host.to_s + forward
143
+ end
144
+ return data, resp, cookies, forward
145
+ end
146
+
147
+ def get(url, cookies="", referer="")
148
+ url = URI.parse(url)
149
+ http = open_http(url)
150
+ resp, data = http.get("#{url.path}?#{url.query}",
151
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
152
+ "Accept-Encoding" => "gzip",
153
+ "Cookie" => cookies,
154
+ "Referer" => referer
155
+ )
156
+ data = uncompress(resp, data)
157
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
158
+ forward = resp.response['Location']
159
+ if (not forward.nil?) && URI.parse(forward).host.nil?
160
+ forward = url.scheme.to_s + "://" + url.host.to_s + forward
161
+ end
162
+ return data, resp, cookies, forward
163
+ end
164
+
165
+ def uncompress(resp, data)
166
+ case resp.response['content-encoding']
167
+ when 'gzip':
168
+ gz = Zlib::GzipReader.new(StringIO.new(data))
169
+ data = gz.read
170
+ gz.close
171
+ resp.response['content-encoding'] = nil
172
+ # FIXME: Not sure what Hotmail was feeding me with their 'deflate',
173
+ # but the headers definitely were not right
174
+ when 'deflate':
175
+ data = Zlib::Inflate.inflate(data)
176
+ resp.response['content-encoding'] = nil
177
+ end
178
+
179
+ data
180
+ end
181
+ end
182
+
183
+ class ContactsError < StandardError
184
+ end
185
+
186
+ class AuthenticationError < ContactsError
187
+ end
188
+
189
+ class ConnectionError < ContactsError
190
+ end
191
+
192
+ class TypeNotFound < ContactsError
193
+ end
194
+
195
+ def self.new(type, login, password)
196
+ if TYPES.include?(type.to_s.intern)
197
+ TYPES[type.to_s.intern].new(login, password)
198
+ else
199
+ raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
200
+ end
201
+ end
202
+
203
+ def self.guess(login, password)
204
+ TYPES.inject([]) do |a, t|
205
+ begin
206
+ a + t[1].new(login, password).contacts
207
+ rescue AuthenticationError
208
+ a
209
+ end
210
+ end.uniq
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,84 @@
1
+ require "json/add/rails"
2
+
3
+ module MOG
4
+ class Contacts
5
+ class Gmail < Base
6
+ URL = "https://mail.google.com/mail/"
7
+ LOGIN_URL = "https://www.google.com/accounts/ServiceLoginAuth"
8
+ LOGIN_REFERER_URL = "https://www.google.com/accounts/ServiceLogin?service=mail&passive=true&rm=false&continue=http%3A%2F%2Fmail.google.com%2Fmail%3Fui%3Dhtml%26zy%3Dl&ltmpl=yj_blanco&ltmplcache=2&hl=en"
9
+ CONTACT_LIST_URL = "https://mail.google.com/mail/contacts/data/contacts?thumb=true&show=ALL&enums=true&psort=Name&max=10000&out=js&rf=&jsx=true"
10
+ PROTOCOL_ERROR = "Gmail 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"
11
+
12
+ def real_connect
13
+ postdata = "ltmpl=yj_blanco"
14
+ postdata += "&continue=%s" % CGI.escape(URL)
15
+ postdata += "&ltmplcache=2"
16
+ postdata += "&service=mail"
17
+ postdata += "&rm=false"
18
+ postdata += "&ltmpl=yj_blanco"
19
+ postdata += "&hl=en"
20
+ postdata += "&Email=%s" % CGI.escape(login)
21
+ postdata += "&Passwd=%s" % CGI.escape(password)
22
+ postdata += "&rmShown=1"
23
+ postdata += "&null=Sign+in"
24
+
25
+ time = Time.now.to_i
26
+ time_past = Time.now.to_i - 8 - rand(12)
27
+ cookie = "GMAIL_LOGIN=T#{time_past}/#{time_past}/#{time}"
28
+
29
+ data, resp, cookies, forward, old_url = post(LOGIN_URL, postdata, cookie, LOGIN_REFERER_URL) + [LOGIN_URL]
30
+
31
+ cookies = remove_cookie("GMAIL_LOGIN", cookies)
32
+
33
+ if data.index("Username and password do not match")
34
+ raise AuthenticationError, "Username and password do not match"
35
+ elsif data.index("Required field must not be blank")
36
+ raise AuthenticationError, "Login and password must not be blank"
37
+ elsif data.index("errormsg_0_logincaptcha")
38
+ raise AuthenticationError, "Captcha error"
39
+ elsif data.index("Invalid request")
40
+ raise ConnectionError, PROTOCOL_ERROR
41
+ elsif cookies == ""
42
+ raise ConnectionError, PROTOCOL_ERROR
43
+ end
44
+
45
+ cookies = remove_cookie("LSID", cookies)
46
+ cookies = remove_cookie("GV", cookies)
47
+
48
+ @cookies = cookies
49
+ end
50
+
51
+ private
52
+
53
+ def parse(data, options)
54
+ data.gsub!(/^while \(true\); &&&START&&&/, '')
55
+ data.gsub!(/ &&&END&&&$/, '')
56
+ data.gsub!(/\t/, ' ') # tabs in the note field cause errors with JSON.parse
57
+ data.gsub!(/[\t\x00-\x1F]/, " ") # strip control characters
58
+
59
+ @contacts = JSON.parse(data)['Body']['Contacts'] || {}
60
+
61
+ # Determine in which format to return the data.
62
+
63
+ # Return the full JSON Hash.
64
+ return @contacts if(options[:details])
65
+
66
+ # Default format.
67
+ # ['Name', 'Email1', 'Email2', ...]
68
+ if @contacts != nil
69
+ @contacts = @contacts.delete_if {|c| c["Emails"].nil?}.map do |c|
70
+ name, emails = c.values_at "Name", "Emails"
71
+ # emails are returned in a form of
72
+ # [{"Address"=>"home.email@gmail.com"}, {"Type"=>{"Id"=>"WORK"}, "Address"=>"work.email@gmail.com"}]
73
+ emails = emails.collect{|a| a.values_at("Address")}
74
+ [name, emails].flatten
75
+ end
76
+ else
77
+ []
78
+ end
79
+ end
80
+ end
81
+
82
+ TYPES[:gmail] = Gmail
83
+ end
84
+ end
@@ -0,0 +1,181 @@
1
+ module MOG
2
+ class Contacts
3
+ class Hotmail < Base
4
+ URL = "http://login.live.com/login.srf?id=2"
5
+ OLD_CONTACT_LIST_URL = "http://%s/cgi-bin/addresses"
6
+ NEW_CONTACT_LIST_URL = "http://%s/mail/GetContacts.aspx"
7
+ NEWEST_CONTACT_LIST_URL = "http://%s/mail/options.aspx?subsection=26"
8
+ COMPOSE_URL = "http://%s/cgi-bin/compose?"
9
+ 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"
10
+ PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
11
+ MAX_HTTP_THREADS = 8
12
+
13
+ def real_connect
14
+ data, resp, cookies, forward = get(URL)
15
+
16
+ old_url = URL
17
+ until forward.nil?
18
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
19
+ end
20
+
21
+ postdata = "PPSX=%s&PwdPad=%s&login=%s&passwd=%s&LoginOptions=2&PPFT=%s" % [
22
+ CGI.escape(data.split("><").grep(/PPSX/).first[/=\S+$/][2..-3]),
23
+ PWDPAD[0...(PWDPAD.length-@password.length)],
24
+ CGI.escape(login),
25
+ CGI.escape(password),
26
+ CGI.escape(data.split("><").grep(/PPFT/).first[/=\S+$/][2..-3])
27
+ ]
28
+
29
+ form_url = data.split("><").grep(/form/).first.split[5][8..-2]
30
+ data, resp, cookies, forward = post(form_url, postdata, cookies)
31
+
32
+ if data.index("The e-mail address or password is incorrect")
33
+ raise AuthenticationError, "Username and password do not match"
34
+ elsif data != ""
35
+ raise AuthenticationError, "Required field must not be blank"
36
+ elsif cookies == ""
37
+ raise ConnectionError, PROTOCOL_ERROR
38
+ end
39
+
40
+ old_url = form_url
41
+ until forward.nil?
42
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
43
+ end
44
+
45
+ =begin
46
+ if data =~ %r{action="(.*?)"}
47
+ forward = $1
48
+ puts forward
49
+ napexp = CGI.escape(data.to_s[/id="NAPExp" value="(.*?)"/][19...-1])
50
+ nap = CGI.escape(data.to_s[/id="NAP" value="(.*?)"/][16...-1])
51
+ anon = CGI.escape(data.to_s[/id="ANON" value="(.*?)"/][17...-1])
52
+ anonexp = CGI.escape(data.to_s[/id="ANONExp" value="(.*?)"/][20...-1])
53
+ t = CGI.escape(data.to_s[/id="t" value="(.*?)"/][14...-1])
54
+
55
+ postdata = "NAPExp=%s&NAP=%s&ANON=%s&ANONExp=%s&t=%s" % [ napexp, nap, anon, anonexp, t ]
56
+ puts postdata
57
+ data, resp, cookies, forward, old_url = post(forward, postdata, cookies, old_url) + [forward]
58
+ end
59
+
60
+ until forward.nil?
61
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
62
+ end
63
+ =end
64
+
65
+ data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
66
+ until forward.nil?
67
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
68
+ end
69
+
70
+ # click on 'Contiune' if presented with the Hotmail Listened page
71
+ # look for the Submit button with a "TakeMeToInbox" name (this should work for other languages)
72
+ if (not old_url.grep(/MessageAtLogin.aspx/).first.nil?)
73
+
74
+ viewState = data.split(/>\s*?</).grep(/__VIEWSTATE/).first[/value=\".+?\"/][7..-2]
75
+ eventValidation = data.split(/>\s*?</).grep(/__EVENTVALIDATION/).first[/value=\".+?\"/][7..-2]
76
+ continueValue = data.split(/>\s*?</).grep(/TakeMeToInbox/).first[/value=\".+?\"/][7..-2]
77
+
78
+ # post back to the same url
79
+ postdata = "%s=%s&%s=%s&%s=%s" % [
80
+ "__VIEWSTATE", CGI.escape(viewState),
81
+ "__EVENTVALIDATION", CGI.escape(eventValidation),
82
+ CGI.escape("TakeMeToInbox"), CGI.escape(continueValue)
83
+ ]
84
+ data, resp, cookies, forward = post( old_url, postdata, cookies, old_url )
85
+ until forward.nil?
86
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
87
+ end
88
+ end
89
+
90
+ @domain = URI.parse(old_url).host
91
+ @cookies = cookies
92
+ rescue AuthenticationError => m
93
+ if @attempt == 1
94
+ retry
95
+ else
96
+ raise m
97
+ end
98
+ end
99
+
100
+ def contacts(options = {})
101
+ return @contacts if @contacts
102
+ if connected?
103
+ url = URI.parse(contact_list_url)
104
+ data, resp, cookies, forward = get( contact_list_url, @cookies )
105
+
106
+ if resp.code_type != Net::HTTPOK
107
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
108
+ end
109
+
110
+ # we have to click on the Export Contacts button to get the csv:
111
+ # Search the content for __VIEWSTATE or __EVENTVALIDATION
112
+ viewState = data.split(/>\s*?</).grep(/__VIEWSTATE/).first[/value=\".+?\"/][7..-2]
113
+ eventValidation = data.split(/>\s*?</).grep(/__EVENTVALIDATION/).first[/value=\".+?\"/][7..-2]
114
+ exportValue = data.split(/>\s*?</).grep(/ctl02\$ExportButton/).first[/value=\".+?\"/][7..-2]
115
+ mt = cookies.split("; ").grep(/mt=/).first[3..-1]
116
+
117
+ # post back to the same url
118
+ postdata = "%s=%s&%s=%s&%s=%s&%s=%s" % [
119
+ "__VIEWSTATE", CGI.escape(viewState),
120
+ "__EVENTVALIDATION", CGI.escape(eventValidation),
121
+ CGI.escape("ctl02$ExportButton"), CGI.escape(exportValue),
122
+ "mt", CGI.escape( mt )
123
+ ]
124
+
125
+ url = URI.parse(contact_list_url)
126
+ http = open_http(url)
127
+ resp, data = http.post("#{url.path}?#{url.query}", postdata,
128
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
129
+ "Accept-Encoding" => "gzip",
130
+ "Cookie" => cookies,
131
+ "Referer" => contact_list_url,
132
+ "Content-Type" => 'application/x-www-form-urlencoded'
133
+ )
134
+
135
+ data = uncompress(resp, data)
136
+ parse(data, options)
137
+ end
138
+ end
139
+
140
+
141
+ private
142
+
143
+ def contact_list_url
144
+ NEWEST_CONTACT_LIST_URL % @domain
145
+ end
146
+
147
+ def follow_email(data, id, contacts_slot)
148
+ compose_url = COMPOSE_URL % @domain
149
+ postdata = "HrsTest=&to=#{id}&mailto=1&ref=addresses"
150
+ postdata += "&curmbox=00000000-0000-0000-0000-000000000001"
151
+
152
+ a = data.split(/>\s*<input\s+/i).grep(/\s+name="a"/i)
153
+ return nil if a.empty?
154
+
155
+ a = a[0].match(/\s+value="([a-f0-9]+)"/i) or return nil
156
+ postdata += "&a=#{a[1]}"
157
+
158
+ data, resp, @cookies, forward = post(compose_url, postdata, @cookies)
159
+ e = data.split(/>\s*<input\s+/i).grep(/\s+name="to"/i)
160
+ return nil if e.empty?
161
+
162
+ e = e[0].match(/\s+value="([^"]+)"/i) or return nil
163
+ @contacts[contacts_slot][1] = e[1] if e[1].match(/@/)
164
+ end
165
+
166
+ def parse(data, options={})
167
+ data = data.split("\r\n")
168
+ data = CSV.parse(data.join("\r\n").gsub('"', '').gsub(';', ','), ';')
169
+ col_names = data.shift
170
+
171
+ @contacts = data.delete_if{|person|person[0].nil?}.map do |person|
172
+ person = person[0].split(",")
173
+ next unless (idx = person.index('SMTP'))
174
+ [[person[1], person[2], person[3]].delete_if{|i|i.empty?}.join(" "), person[idx - 1]] unless person[idx - 1].nil?
175
+ end.compact
176
+ end
177
+ end
178
+
179
+ TYPES[:hotmail] = Hotmail
180
+ end
181
+ end
@@ -0,0 +1,124 @@
1
+ require 'rexml/document'
2
+
3
+ module MOG
4
+ class Contacts
5
+ class Plaxo < Base
6
+ URL = "http://www.plaxo.com/"
7
+ LOGIN_URL = "https://www.plaxo.com/signin"
8
+ ADDRESS_BOOK_URL = "http://www.plaxo.com/po3/?module=ab&operation=viewFull&mode=normal"
9
+ CONTACT_LIST_URL = "http://www.plaxo.com/axis/soap/contact?_action=getContacts&_format=xml"
10
+ 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"
11
+
12
+ def real_connect
13
+
14
+ end # real_connect
15
+
16
+ def contacts
17
+ getdata = "&authInfo.authByEmail.email=%s" % CGI.escape(login)
18
+ getdata += "&authInfo.authByEmail.password=%s" % CGI.escape(password)
19
+ data, resp, cookies, forward = get(CONTACT_LIST_URL + getdata)
20
+
21
+ if resp.code_type != Net::HTTPOK
22
+ raise ConnectionError, PROTOCOL_ERROR
23
+ end
24
+
25
+ parse data
26
+ end # contacts
27
+
28
+ private
29
+ def parse(data, options={})
30
+ doc = REXML::Document.new(data)
31
+ code = doc.elements['//response/code'].text
32
+
33
+ if code == '401'
34
+ raise AuthenticationError, "Username and password do not match"
35
+ elsif code == '200'
36
+ @contacts = []
37
+ doc.elements.each('//contact') do |cont|
38
+ name = cont.elements['fullName'].text #rescue nil
39
+ email = cont.elements['email1'].text #rescue nil
40
+ @contacts << [name, email]
41
+ end.compact
42
+ @contacts
43
+ else
44
+ raise ConnectionError, PROTOCOL_ERROR
45
+ end
46
+
47
+ end # parse
48
+
49
+ end # Plaxo
50
+
51
+ TYPES[:plaxo] = Plaxo
52
+
53
+ end # Contacts
54
+ end
55
+
56
+ # sample contacts responses
57
+ '
58
+ Bad email
59
+ =========
60
+ <?xml version="1.0" encoding="utf-8" ?>
61
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
62
+ <response>
63
+ <code>401</code>
64
+ <subCode>1</subCode>
65
+ <message>User not found.</message>
66
+ </response>
67
+ </ns1:GetContactsResponse>
68
+
69
+
70
+ Bad password
71
+ ============
72
+ <?xml version="1.0" encoding="utf-8" ?>
73
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
74
+ <response>
75
+ <code>401</code>
76
+ <subCode>4</subCode>
77
+ <message>Bad password or security token.</message>
78
+ </response>
79
+ </ns1:GetContactsResponse>
80
+
81
+
82
+ Success
83
+ =======
84
+ <?xml version="1.0" encoding="utf-8" ?>
85
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
86
+
87
+ <response>
88
+ <code>200</code>
89
+ <message>OK</message>
90
+ <userId>77311236242</userId>
91
+ </response>
92
+
93
+ <contacts>
94
+
95
+ <contact>
96
+ <itemId>61312569</itemId>
97
+ <displayName>Joe Blow1</displayName>
98
+ <fullName>Joe Blow1</fullName>
99
+ <firstName>Joe</firstName>
100
+ <lastName>Blow1</lastName>
101
+ <homeEmail1>joeblow1@mailinator.com</homeEmail1>
102
+ <email1>joeblow1@mailinator.com</email1>
103
+ <folderId>5291351</folderId>
104
+ </contact>
105
+
106
+ <contact>
107
+ <itemId>61313159</itemId>
108
+ <displayName>Joe Blow2</displayName>
109
+ <fullName>Joe Blow2</fullName>
110
+ <firstName>Joe</firstName>
111
+ <lastName>Blow2</lastName>
112
+ <homeEmail1>joeblow2@mailinator.com</homeEmail1>
113
+ <email1>joeblow2@mailinator.com</email1>
114
+ <folderId>5291351</folderId>
115
+ </contact>
116
+
117
+ </contacts>
118
+
119
+ <totalCount>2</totalCount>
120
+ <editCounter>3</editCounter>
121
+
122
+ </ns1:GetContactsResponse>
123
+
124
+ '
@@ -0,0 +1,84 @@
1
+ require 'csv'
2
+
3
+ module MOG
4
+ class Contacts
5
+ class Yahoo < Base
6
+ URL = "http://mail.yahoo.com/"
7
+ LOGIN_URL = "https://login.yahoo.com/config/login"
8
+ ADDRESS_BOOK_URL = "http://address.mail.yahoo.com/?1&VPC=import_export"
9
+ CONTACT_LIST_URL = "http://address.yahoo.com/index.php?VPC=import_export&A=B&submit[action_export_yahoo]=Export%20Now"
10
+ 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"
11
+
12
+ def real_connect
13
+ postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
14
+ postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
15
+ postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
16
+ postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
17
+
18
+ data, resp, cookies, forward = post(LOGIN_URL, postdata)
19
+
20
+ if data.index("Invalid ID or password") || data.index("This ID is not yet taken")
21
+ raise AuthenticationError, "Username and password do not match"
22
+ elsif data.index("Sign in") && data.index("to Yahoo!")
23
+ raise AuthenticationError, "Required field must not be blank"
24
+ elsif !data.match(/uncompressed\/chunked/)
25
+ raise ConnectionError, PROTOCOL_ERROR
26
+ elsif cookies == ""
27
+ raise ConnectionError, PROTOCOL_ERROR
28
+ end
29
+
30
+ data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
31
+
32
+ if resp.code_type != Net::HTTPOK
33
+ raise ConnectionError, PROTOCOL_ERROR
34
+ end
35
+
36
+ @cookies = cookies
37
+ end
38
+
39
+ def contacts
40
+ return @contacts if @contacts
41
+ if connected?
42
+ # first, get the addressbook site with the new crumb parameter
43
+ url = URI.parse(address_book_url)
44
+ http = open_http(url)
45
+ resp, data = http.get("#{url.path}?#{url.query}",
46
+ "Cookie" => @cookies
47
+ )
48
+
49
+ if resp.code_type != Net::HTTPOK
50
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
51
+ end
52
+
53
+ crumb = data.to_s[/id="crumb2" value="(.*?)"/][19...-1]
54
+
55
+ # now proceed with the new ".crumb" parameter to get the csv data
56
+ url = URI.parse("#{contact_list_url}&.crumb=#{crumb}")
57
+ http = open_http(url)
58
+ resp, data = http.get("#{url.path}?#{url.query}",
59
+ "Cookie" => @cookies
60
+ )
61
+
62
+ if resp.code_type != Net::HTTPOK
63
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
64
+ end
65
+
66
+ parse data
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def parse(data, options={})
73
+ data = CSV.parse(data)
74
+ col_names = data.shift
75
+ @contacts = data.map do |person|
76
+ [[person[0], person[1], person[2]].delete_if{|i|i.empty?}.join(" "), person[4]] unless person[4].empty?
77
+ end.compact
78
+ end
79
+
80
+ end
81
+
82
+ TYPES[:yahoo] = Yahoo
83
+ end
84
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deadprogrammer-contacts
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.15
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Carlson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-27 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ description: A universal interface to grab contact list information from various providers including Yahoo, Gmail, Hotmail, and Plaxo.
26
+ email: lucas@rufy.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - LICENSE
35
+ - Rakefile
36
+ - README
37
+ - examples/grab_contacts.rb
38
+ - lib/contacts.rb
39
+ - lib/contacts/base.rb
40
+ - lib/contacts/gmail.rb
41
+ - lib/contacts/hotmail.rb
42
+ - lib/contacts/plaxo.rb
43
+ - lib/contacts/yahoo.rb
44
+ has_rdoc: false
45
+ homepage: http://github.com/cardmagic/contacts
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.2.0
67
+ signing_key:
68
+ specification_version: 2
69
+ summary: A universal interface to grab contact list information from various providers including Yahoo, Gmail, Hotmail, and Plaxo.
70
+ test_files: []
71
+