loe-contacts 1.0.14

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 = 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,204 @@
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
+ class Contacts
10
+ TYPES = {}
11
+ VERSION = "1.0.11"
12
+
13
+ class Base
14
+ def initialize(login, password)
15
+ @login = login
16
+ @password = password
17
+ @connections = {}
18
+ connect
19
+ end
20
+
21
+ def connect
22
+ raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @password.nil?
23
+ real_connect
24
+ end
25
+
26
+ def connected?
27
+ @cookies && !@cookies.empty?
28
+ end
29
+
30
+ def contacts(options = {})
31
+ return @contacts if @contacts
32
+ if connected?
33
+ url = URI.parse(contact_list_url)
34
+ http = open_http(url)
35
+ resp, data = http.get("#{url.path}?#{url.query}",
36
+ "Cookie" => @cookies
37
+ )
38
+
39
+ if resp.code_type != Net::HTTPOK
40
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
41
+ end
42
+
43
+ parse(data, options)
44
+ end
45
+ end
46
+
47
+ def login
48
+ @attempt ||= 0
49
+ @attempt += 1
50
+
51
+ if @attempt == 1
52
+ @login
53
+ else
54
+ if @login.include?("@#{domain}")
55
+ @login.sub("@#{domain}","")
56
+ else
57
+ "#{@login}@#{domain}"
58
+ end
59
+ end
60
+ end
61
+
62
+ def password
63
+ @password
64
+ end
65
+
66
+ private
67
+
68
+ def domain
69
+ @d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
70
+ end
71
+
72
+ def contact_list_url
73
+ self.class.const_get(:CONTACT_LIST_URL)
74
+ end
75
+
76
+ def address_book_url
77
+ self.class.const_get(:ADDRESS_BOOK_URL)
78
+ end
79
+
80
+ def open_http(url)
81
+ c = @connections[Thread.current.object_id] ||= {}
82
+ http = c["#{url.host}:#{url.port}"]
83
+ unless http
84
+ http = Net::HTTP.new(url.host, url.port)
85
+ if url.port == 443
86
+ http.use_ssl = true
87
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
88
+ end
89
+ c["#{url.host}:#{url.port}"] = http
90
+ end
91
+ http.start unless http.started?
92
+ http
93
+ end
94
+
95
+ def parse_cookies(data, existing="")
96
+ return existing if data.nil?
97
+
98
+ cookies = existing.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
99
+
100
+ data.gsub!(/ ?[\w]+=EXPIRED;/,'')
101
+ data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
102
+ data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
103
+ data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
104
+ data.gsub!(/(;\s*){2,}/,', ')
105
+ data.gsub!(/(,\s*){2,}/,', ')
106
+ data.sub!(/^,\s*/,'')
107
+ data.sub!(/\s*,$/,'')
108
+
109
+ data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
110
+ k, v = data.split("=", 2).map{|j|j.strip}
111
+ if cookies[k] && v.empty?
112
+ cookies.delete(k)
113
+ elsif v && !v.empty?
114
+ cookies[k] = v
115
+ end
116
+ end
117
+
118
+ cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
119
+ end
120
+
121
+ def remove_cookie(cookie, cookies)
122
+ parse_cookies("#{cookie}=", cookies)
123
+ end
124
+
125
+ def post(url, postdata, cookies="", referer="")
126
+ url = URI.parse(url)
127
+ http = open_http(url)
128
+ resp, data = http.post(url.path, postdata,
129
+ "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
130
+ "Accept-Encoding" => "gzip",
131
+ "Cookie" => cookies,
132
+ "Referer" => referer,
133
+ "Content-Type" => 'application/x-www-form-urlencoded'
134
+ )
135
+ data = uncompress(resp, data)
136
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
137
+ forward = resp.response['Location']
138
+ forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
139
+ return data, resp, cookies, forward
140
+ end
141
+
142
+ def get(url, cookies="", referer="")
143
+ url = URI.parse(url)
144
+ http = open_http(url)
145
+ resp, data = http.get("#{url.path}?#{url.query}",
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
+ )
151
+ data = uncompress(resp, data)
152
+ cookies = parse_cookies(resp.response['set-cookie'], cookies)
153
+ forward = resp.response['Location']
154
+ return data, resp, cookies, forward
155
+ end
156
+
157
+ def uncompress(resp, data)
158
+ case resp.response['content-encoding']
159
+ when 'gzip':
160
+ gz = Zlib::GzipReader.new(StringIO.new(data))
161
+ data = gz.read
162
+ gz.close
163
+ resp.response['content-encoding'] = nil
164
+ # FIXME: Not sure what Hotmail was feeding me with their 'deflate',
165
+ # but the headers definitely were not right
166
+ when 'deflate':
167
+ data = Zlib::Inflate.inflate(data)
168
+ resp.response['content-encoding'] = nil
169
+ end
170
+
171
+ data
172
+ end
173
+ end
174
+
175
+ class ContactsError < StandardError
176
+ end
177
+
178
+ class AuthenticationError < ContactsError
179
+ end
180
+
181
+ class ConnectionError < ContactsError
182
+ end
183
+
184
+ class TypeNotFound < ContactsError
185
+ end
186
+
187
+ def self.new(type, login, password)
188
+ if TYPES.include?(type.to_s.intern)
189
+ TYPES[type.to_s.intern].new(login, password)
190
+ else
191
+ raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
192
+ end
193
+ end
194
+
195
+ def self.guess(login, password)
196
+ TYPES.inject([]) do |a, t|
197
+ begin
198
+ a + t[1].new(login, password).contacts
199
+ rescue AuthenticationError
200
+ a
201
+ end
202
+ end.uniq
203
+ end
204
+ end
@@ -0,0 +1,76 @@
1
+ require "json"
2
+
3
+ class Contacts
4
+ class Gmail < Base
5
+ URL = "https://mail.google.com/mail/"
6
+ LOGIN_URL = "https://www.google.com/accounts/ServiceLoginAuth"
7
+ 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"
8
+ 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"
9
+ PROTOCOL_ERROR = "Gmail has changed its protocols, please upgrade this library first. If that does not work, contact lucas@rufy.com with this error"
10
+
11
+ def real_connect
12
+ postdata = "ltmpl=yj_blanco"
13
+ postdata += "&continue=%s" % CGI.escape(URL)
14
+ postdata += "&ltmplcache=2"
15
+ postdata += "&service=mail"
16
+ postdata += "&rm=false"
17
+ postdata += "&ltmpl=yj_blanco"
18
+ postdata += "&hl=en"
19
+ postdata += "&Email=%s" % CGI.escape(login)
20
+ postdata += "&Passwd=%s" % CGI.escape(password)
21
+ postdata += "&rmShown=1"
22
+ postdata += "&null=Sign+in"
23
+
24
+ time = Time.now.to_i
25
+ time_past = Time.now.to_i - 8 - rand(12)
26
+ cookie = "GMAIL_LOGIN=T#{time_past}/#{time_past}/#{time}"
27
+
28
+ data, resp, cookies, forward, old_url = post(LOGIN_URL, postdata, cookie, LOGIN_REFERER_URL) + [LOGIN_URL]
29
+
30
+ cookies = remove_cookie("GMAIL_LOGIN", cookies)
31
+
32
+ if data.index("Username and password do not match")
33
+ raise AuthenticationError, "Username and password do not match"
34
+ elsif data.index("Required field must not be blank")
35
+ raise AuthenticationError, "Login and password must not be blank"
36
+ elsif data.index("errormsg_0_logincaptcha")
37
+ raise AuthenticationError, "Captcha error"
38
+ elsif data.index("Invalid request")
39
+ raise ConnectionError, PROTOCOL_ERROR
40
+ elsif cookies == ""
41
+ raise ConnectionError, PROTOCOL_ERROR
42
+ end
43
+
44
+ cookies = remove_cookie("LSID", cookies)
45
+ cookies = remove_cookie("GV", cookies)
46
+
47
+ @cookies = cookies
48
+ end
49
+
50
+ private
51
+
52
+ def parse(data, options)
53
+ data.gsub!(/^while \(true\); &&&START&&&/, '')
54
+ data.gsub!(/ &&&END&&&$/, '')
55
+ data.gsub!(/\t/, ' ') # tabs in the note field cause errors with JSON.parse
56
+
57
+ @contacts = JSON.parse(data)['Body']['Contacts'] || {}
58
+
59
+ # Determine in which format to return the data.
60
+
61
+ # Return the full JSON Hash.
62
+ return @contacts if(options[:details])
63
+
64
+ # Default format.
65
+ # ['Name', 'Email1', 'Email2', ...]
66
+ @contacts = @contacts.delete_if {|c| c["Emails"].nil?}.map do |c|
67
+ name, emails, company = c.values_at "Name", "Emails", "Company"
68
+ emails = emails.first.values
69
+ [name, emails, company].flatten
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ TYPES[:gmail] = Gmail
76
+ end
@@ -0,0 +1,116 @@
1
+ class Contacts
2
+ class Hotmail < Base
3
+ URL = "http://login.live.com/login.srf?id=2"
4
+ OLD_CONTACT_LIST_URL = "http://%s/cgi-bin/addresses"
5
+ NEW_CONTACT_LIST_URL = "http://%s/mail/GetContacts.aspx"
6
+ COMPOSE_URL = "http://%s/cgi-bin/compose?"
7
+ 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"
8
+ PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
9
+ MAX_HTTP_THREADS = 8
10
+
11
+ def real_connect
12
+ data, resp, cookies, forward = get(URL)
13
+
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
+ if data.index("The e-mail address or password is incorrect")
31
+ raise AuthenticationError, "Username and password do not match"
32
+ elsif data != ""
33
+ raise AuthenticationError, "Required field must not be blank"
34
+ elsif cookies == ""
35
+ raise ConnectionError, PROTOCOL_ERROR
36
+ end
37
+
38
+ old_url = form_url
39
+ until forward.nil?
40
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
41
+ end
42
+
43
+ =begin
44
+ if data =~ %r{action="(.*?)"}
45
+ forward = $1
46
+ puts forward
47
+ napexp = CGI.escape(data.to_s[/id="NAPExp" value="(.*?)"/][19...-1])
48
+ nap = CGI.escape(data.to_s[/id="NAP" value="(.*?)"/][16...-1])
49
+ anon = CGI.escape(data.to_s[/id="ANON" value="(.*?)"/][17...-1])
50
+ anonexp = CGI.escape(data.to_s[/id="ANONExp" value="(.*?)"/][20...-1])
51
+ t = CGI.escape(data.to_s[/id="t" value="(.*?)"/][14...-1])
52
+
53
+ postdata = "NAPExp=%s&NAP=%s&ANON=%s&ANONExp=%s&t=%s" % [ napexp, nap, anon, anonexp, t ]
54
+ puts postdata
55
+ data, resp, cookies, forward, old_url = post(forward, postdata, cookies, old_url) + [forward]
56
+ end
57
+
58
+ until forward.nil?
59
+ data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
60
+ end
61
+ =end
62
+
63
+ data, resp, cookies, forward, old_url = get("http://mail.live.com/mail", cookies)
64
+ data, resp, cookies, forward, old_url = get(forward, cookies)
65
+
66
+ @domain = URI.parse(forward).host
67
+ @cookies = cookies
68
+ rescue AuthenticationError => m
69
+ if @attempt == 1
70
+ retry
71
+ else
72
+ raise m
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def contact_list_url
79
+ NEW_CONTACT_LIST_URL % @domain
80
+ end
81
+
82
+ def follow_email(data, id, contacts_slot)
83
+ compose_url = COMPOSE_URL % @domain
84
+ postdata = "HrsTest=&to=#{id}&mailto=1&ref=addresses"
85
+ postdata += "&curmbox=00000000-0000-0000-0000-000000000001"
86
+
87
+ a = data.split(/>\s*<input\s+/i).grep(/\s+name="a"/i)
88
+ return nil if a.empty?
89
+
90
+ a = a[0].match(/\s+value="([a-f0-9]+)"/i) or return nil
91
+ postdata += "&a=#{a[1]}"
92
+
93
+ data, resp, @cookies, forward = post(compose_url, postdata, @cookies)
94
+ e = data.split(/>\s*<input\s+/i).grep(/\s+name="to"/i)
95
+ return nil if e.empty?
96
+
97
+ e = e[0].match(/\s+value="([^"]+)"/i) or return nil
98
+ @contacts[contacts_slot][1] = e[1] if e[1].match(/@/)
99
+ end
100
+
101
+ def parse(data, options={})
102
+ data = data.split("\r\n")
103
+ data = CSV.parse(data.join("\r\n").gsub('"', ''), ';')
104
+ col_names = data.shift
105
+
106
+ @contacts = data.map do |person|
107
+ person = person[0].split(",")
108
+ next unless (idx = person.index('SMTP'))
109
+ [[person[1], person[2], person[3]].delete_if{|i|i.empty?}.join(" "), person[idx - 1], person[5]] unless person[idx - 1].nil?
110
+ end.compact
111
+ end
112
+
113
+ end
114
+
115
+ TYPES[:hotmail] = Hotmail
116
+ end
@@ -0,0 +1,123 @@
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, contact lucas@rufy.com with this error"
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 = cont.elements['fullName'].text #rescue nil
38
+ email = cont.elements['email1'].text #rescue nil
39
+ @contacts << [name, email]
40
+ end.compact
41
+ @contacts
42
+ else
43
+ raise ConnectionError, PROTOCOL_ERROR
44
+ end
45
+
46
+ end # parse
47
+
48
+ end # Plaxo
49
+
50
+ TYPES[:plaxo] = Plaxo
51
+
52
+ end # Contacts
53
+
54
+
55
+ # sample contacts responses
56
+ '
57
+ Bad email
58
+ =========
59
+ <?xml version="1.0" encoding="utf-8" ?>
60
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
61
+ <response>
62
+ <code>401</code>
63
+ <subCode>1</subCode>
64
+ <message>User not found.</message>
65
+ </response>
66
+ </ns1:GetContactsResponse>
67
+
68
+
69
+ Bad password
70
+ ============
71
+ <?xml version="1.0" encoding="utf-8" ?>
72
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
73
+ <response>
74
+ <code>401</code>
75
+ <subCode>4</subCode>
76
+ <message>Bad password or security token.</message>
77
+ </response>
78
+ </ns1:GetContactsResponse>
79
+
80
+
81
+ Success
82
+ =======
83
+ <?xml version="1.0" encoding="utf-8" ?>
84
+ <ns1:GetContactsResponse xmlns:ns1="Plaxo">
85
+
86
+ <response>
87
+ <code>200</code>
88
+ <message>OK</message>
89
+ <userId>77311236242</userId>
90
+ </response>
91
+
92
+ <contacts>
93
+
94
+ <contact>
95
+ <itemId>61312569</itemId>
96
+ <displayName>Joe Blow1</displayName>
97
+ <fullName>Joe Blow1</fullName>
98
+ <firstName>Joe</firstName>
99
+ <lastName>Blow1</lastName>
100
+ <homeEmail1>joeblow1@mailinator.com</homeEmail1>
101
+ <email1>joeblow1@mailinator.com</email1>
102
+ <folderId>5291351</folderId>
103
+ </contact>
104
+
105
+ <contact>
106
+ <itemId>61313159</itemId>
107
+ <displayName>Joe Blow2</displayName>
108
+ <fullName>Joe Blow2</fullName>
109
+ <firstName>Joe</firstName>
110
+ <lastName>Blow2</lastName>
111
+ <homeEmail1>joeblow2@mailinator.com</homeEmail1>
112
+ <email1>joeblow2@mailinator.com</email1>
113
+ <folderId>5291351</folderId>
114
+ </contact>
115
+
116
+ </contacts>
117
+
118
+ <totalCount>2</totalCount>
119
+ <editCounter>3</editCounter>
120
+
121
+ </ns1:GetContactsResponse>
122
+
123
+ '
@@ -0,0 +1,82 @@
1
+ require 'csv'
2
+
3
+ class Contacts
4
+ class Yahoo < Base
5
+ URL = "http://mail.yahoo.com/"
6
+ 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"
9
+ PROTOCOL_ERROR = "Yahoo has changed its protocols, please upgrade this library first. If that does not work, contact lucas@rufy.com with this error"
10
+
11
+ def real_connect
12
+ postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
13
+ postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
14
+ postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
15
+ postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
16
+
17
+ data, resp, cookies, forward = post(LOGIN_URL, postdata)
18
+
19
+ if data.index("Invalid ID or password")
20
+ raise AuthenticationError, "Username and password do not match"
21
+ elsif data.index("Sign in") && data.index("to Yahoo!")
22
+ raise AuthenticationError, "Required field must not be blank"
23
+ elsif !data.match(/uncompressed\/chunked/)
24
+ raise ConnectionError, PROTOCOL_ERROR
25
+ elsif cookies == ""
26
+ raise ConnectionError, PROTOCOL_ERROR
27
+ end
28
+
29
+ data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
30
+
31
+ if resp.code_type != Net::HTTPOK
32
+ raise ConnectionError, PROTOCOL_ERROR
33
+ end
34
+
35
+ @cookies = cookies
36
+ end
37
+
38
+ def contacts
39
+ return @contacts if @contacts
40
+ if connected?
41
+ # first, get the addressbook site with the new crumb parameter
42
+ url = URI.parse(address_book_url)
43
+ http = open_http(url)
44
+ resp, data = http.get("#{url.path}?#{url.query}",
45
+ "Cookie" => @cookies
46
+ )
47
+
48
+ if resp.code_type != Net::HTTPOK
49
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
50
+ end
51
+
52
+ crumb = data.to_s[/id="crumb2" value="(.*?)"/][19...-1]
53
+
54
+ # now proceed with the new ".crumb" parameter to get the csv data
55
+ url = URI.parse("#{contact_list_url}&.crumb=#{crumb}")
56
+ http = open_http(url)
57
+ resp, data = http.get("#{url.path}?#{url.query}",
58
+ "Cookie" => @cookies
59
+ )
60
+
61
+ if resp.code_type != Net::HTTPOK
62
+ raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
63
+ end
64
+
65
+ parse data
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ 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], person[21]] unless person[4].empty?
76
+ end.compact
77
+ end
78
+
79
+ end
80
+
81
+ TYPES[:yahoo] = Yahoo
82
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: loe-contacts
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.14
5
+ platform: ruby
6
+ authors:
7
+ - W. Andrew Loe III
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-11 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: json
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">"
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.0
23
+ version:
24
+ description: A universal interface to grab contact list information from various providers including Yahoo, Gmail, Hotmail, and Plaxo.
25
+ email: loe@onehub.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
32
+ files:
33
+ - LICENSE
34
+ - Rakefile
35
+ - README
36
+ - examples/grab_contacts.rb
37
+ - lib/contacts.rb
38
+ - lib/contacts/base.rb
39
+ - lib/contacts/gmail.rb
40
+ - lib/contacts/hotmail.rb
41
+ - lib/contacts/plaxo.rb
42
+ - lib/contacts/yahoo.rb
43
+ has_rdoc: false
44
+ homepage: http://github.com/loe/contacts
45
+ post_install_message:
46
+ rdoc_options: []
47
+
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.2.0
66
+ signing_key:
67
+ specification_version: 2
68
+ summary: A universal interface to grab contact list information from various providers including Yahoo, Gmail, Hotmail, and Plaxo.
69
+ test_files: []
70
+