contacts 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +10 -0
- data/README +37 -0
- data/Rakefile +90 -0
- data/examples/grab_contacts.rb +12 -0
- data/lib/contacts.rb +7 -0
- data/lib/contacts/base.rb +171 -0
- data/lib/contacts/gmail.rb +81 -0
- data/lib/contacts/hotmail.rb +77 -0
- data/lib/contacts/yahoo.rb +57 -0
- metadata +62 -0
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,37 @@
|
|
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
|
+
* Lucas Carlson from MOG (mailto:lucas@rufy.com) - http://mog.com
|
35
|
+
|
36
|
+
This library is released under the terms of the BSD.
|
37
|
+
|
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,171 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "net/http"
|
3
|
+
require "net/https"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
class Contacts
|
7
|
+
TYPES = {}
|
8
|
+
VERSION = "1.0.0"
|
9
|
+
|
10
|
+
class Base
|
11
|
+
def initialize(login, password)
|
12
|
+
@login = login
|
13
|
+
@password = password
|
14
|
+
connect
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect
|
18
|
+
raise AuthenticationError, "Login and password must not be nil, login: #{@login.inspect}, password: #{@password.inspect}" if @login.nil? || @password.nil?
|
19
|
+
real_connect
|
20
|
+
end
|
21
|
+
|
22
|
+
def connected?
|
23
|
+
@cookies && !@cookies.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def contacts
|
27
|
+
return @contacts if @contacts
|
28
|
+
if connected?
|
29
|
+
url = URI.parse(contact_list_url)
|
30
|
+
http = Net::HTTP.new(url.host, url.port)
|
31
|
+
if url.port == 443
|
32
|
+
http.use_ssl = true
|
33
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
34
|
+
end
|
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
|
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 parse_cookies(data, existing="")
|
77
|
+
return existing if data.nil?
|
78
|
+
|
79
|
+
cookies = existing.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
|
80
|
+
|
81
|
+
data.gsub!(/ ?[\w]+=EXPIRED;/,'')
|
82
|
+
data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
|
83
|
+
data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
|
84
|
+
data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
|
85
|
+
data.gsub!(/(;\s*){2,}/,', ')
|
86
|
+
data.gsub!(/(,\s*){2,}/,', ')
|
87
|
+
data.sub!(/^,\s*/,'')
|
88
|
+
data.sub!(/\s*,$/,'')
|
89
|
+
|
90
|
+
data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
|
91
|
+
k, v = data.split("=", 2).map{|j|j.strip}
|
92
|
+
if cookies[k] && v.empty?
|
93
|
+
cookies.delete(k)
|
94
|
+
elsif v && !v.empty?
|
95
|
+
cookies[k] = v
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
|
100
|
+
end
|
101
|
+
|
102
|
+
def remove_cookie(cookie, cookies)
|
103
|
+
parse_cookies("#{cookie}=", cookies)
|
104
|
+
end
|
105
|
+
|
106
|
+
def post(url, postdata, cookies="", referer="")
|
107
|
+
url = URI.parse(url)
|
108
|
+
http = Net::HTTP.new(url.host, url.port)
|
109
|
+
if url.port == 443
|
110
|
+
http.use_ssl = true
|
111
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
112
|
+
end
|
113
|
+
resp, data = http.post(url.path, postdata,
|
114
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
|
115
|
+
"Cookie" => cookies,
|
116
|
+
"Referer" => referer,
|
117
|
+
"Content-Type" => 'application/x-www-form-urlencoded'
|
118
|
+
)
|
119
|
+
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
120
|
+
forward = resp.response['Location']
|
121
|
+
return data, resp, cookies, forward
|
122
|
+
end
|
123
|
+
|
124
|
+
def get(url, cookies="", referer="")
|
125
|
+
url = URI.parse(url)
|
126
|
+
http = Net::HTTP.new(url.host, url.port)
|
127
|
+
if url.port == 443
|
128
|
+
http.use_ssl = true
|
129
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
130
|
+
end
|
131
|
+
resp, data = http.get("#{url.path}?#{url.query}",
|
132
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
|
133
|
+
"Cookie" => cookies,
|
134
|
+
"Referer" => referer
|
135
|
+
)
|
136
|
+
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
137
|
+
forward = resp.response['Location']
|
138
|
+
return data, resp, cookies, forward
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
class ContactsError < StandardError
|
143
|
+
end
|
144
|
+
|
145
|
+
class AuthenticationError < ContactsError
|
146
|
+
end
|
147
|
+
|
148
|
+
class ConnectionError < ContactsError
|
149
|
+
end
|
150
|
+
|
151
|
+
class TypeNotFound < ContactsError
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.new(type, login, password)
|
155
|
+
if TYPES.include?(type.to_s.intern)
|
156
|
+
TYPES[type.to_s.intern].new(login, password)
|
157
|
+
else
|
158
|
+
raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.guess(login, password)
|
163
|
+
TYPES.inject([]) do |a, t|
|
164
|
+
begin
|
165
|
+
a + t[1].new(login, password).contacts
|
166
|
+
rescue AuthenticationError
|
167
|
+
a
|
168
|
+
end
|
169
|
+
end.uniq
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,81 @@
|
|
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<mpl=yj_blanco<mplcache=2&hl=en"
|
8
|
+
CONTACT_LIST_URL = "https://mail.google.com/mail/?view=cl&search=contacts&pnl=a"
|
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=#{CGI.escape(URL)}"
|
14
|
+
postdata += "<mplcache=2"
|
15
|
+
postdata += "&service=mail"
|
16
|
+
postdata += "&rm=false"
|
17
|
+
postdata += "<mpl=yj_blanco"
|
18
|
+
postdata += "&hl=en"
|
19
|
+
postdata += "&Email=#{CGI.escape(login)}"
|
20
|
+
postdata += "&Passwd=#{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
|
+
until forward.nil?
|
45
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
46
|
+
end
|
47
|
+
cookies = remove_cookie("LSID", cookies)
|
48
|
+
cookies = remove_cookie("GV", cookies)
|
49
|
+
|
50
|
+
if resp.code_type != Net::HTTPOK
|
51
|
+
raise ConnectionError, PROTOCOL_ERROR
|
52
|
+
end
|
53
|
+
|
54
|
+
@cookies = cookies
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def parse(data)
|
60
|
+
data.gsub!("\n","")
|
61
|
+
data.gsub!("D([", "\nD([")
|
62
|
+
data.gsub!("]);", "]);\n")
|
63
|
+
data.gsub!("u003d", "=")
|
64
|
+
data.gsub!("u002f", "/")
|
65
|
+
|
66
|
+
parsed_data = []
|
67
|
+
data.scan(%r{D\(\[(.*)\]\)}) do |match|
|
68
|
+
parsed_data << JSON.parse("[#{match[0]}]") rescue next
|
69
|
+
end
|
70
|
+
|
71
|
+
@contacts = parsed_data.select{|d| d[0] == "cl"}.inject([]) do |a,d|
|
72
|
+
a.concat(d.inject([]) do |ar,dr|
|
73
|
+
ar.concat(dr.is_a?(Array) ? [[dr[3],dr[4]]] : [])
|
74
|
+
end)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
TYPES[:gmail] = Gmail
|
81
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Hotmail < Base
|
3
|
+
URL = "http://www.hotmail.com/"
|
4
|
+
CONTACT_LIST_URL = "http://%s/cgi-bin/addresses"
|
5
|
+
PROTOCOL_ERROR = "Hotmail has changed its protocols, please upgrade this library first. If that does not work, contact lucas@rufy.com with this error"
|
6
|
+
PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
|
7
|
+
|
8
|
+
def real_connect
|
9
|
+
data, resp, cookies, forward = get(URL)
|
10
|
+
data, resp, cookies, forward = get(forward, cookies, URL)
|
11
|
+
|
12
|
+
postdata = "PPSX=#{CGI.escape(data.split("><").grep(/PPSX/).first[/=\S+$/][2..-3])}&"
|
13
|
+
postdata += "PwdPad=#{PWDPAD[0...(PWDPAD.length-@password.length)]}&"
|
14
|
+
postdata += "login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}&LoginOptions=2&"
|
15
|
+
postdata += "PPFT=#{CGI.escape(data.split("><").grep(/PPFT/).first[/=\S+$/][2..-3])}"
|
16
|
+
|
17
|
+
form_url = data.split("><").grep(/form/).first.split[5][8..-2]
|
18
|
+
data, resp, cookies, forward = post(form_url, postdata, cookies)
|
19
|
+
|
20
|
+
if data.index("The e-mail address or password is incorrect")
|
21
|
+
raise AuthenticationError, "Username and password do not match"
|
22
|
+
elsif data != ""
|
23
|
+
raise AuthenticationError, "Required field must not be blank"
|
24
|
+
elsif cookies == ""
|
25
|
+
raise ConnectionError, PROTOCOL_ERROR
|
26
|
+
end
|
27
|
+
|
28
|
+
old_url = form_url
|
29
|
+
until forward.nil?
|
30
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
31
|
+
end
|
32
|
+
|
33
|
+
if resp.code_type != Net::HTTPOK
|
34
|
+
raise ConnectionError, PROTOCOL_ERROR
|
35
|
+
end
|
36
|
+
|
37
|
+
forward = "http://www.hotmail.msn.com/cgi-bin/sbox"
|
38
|
+
|
39
|
+
until forward.nil?
|
40
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
41
|
+
end
|
42
|
+
|
43
|
+
if resp.code_type != Net::HTTPOK
|
44
|
+
raise ConnectionError, PROTOCOL_ERROR
|
45
|
+
end
|
46
|
+
|
47
|
+
@domain = URI.parse(old_url).host
|
48
|
+
@cookies = cookies
|
49
|
+
rescue AuthenticationError => m
|
50
|
+
if @attempt == 1
|
51
|
+
retry
|
52
|
+
else
|
53
|
+
raise m
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def contact_list_url
|
60
|
+
CONTACT_LIST_URL % @domain
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse(data)
|
64
|
+
chunks = data.split('id="hotmail"')
|
65
|
+
chunks.delete_at(0)
|
66
|
+
|
67
|
+
@contacts = chunks.map do |chunk|
|
68
|
+
name = chunk.split('return false;">')[1].split('</a>')[0] rescue nil
|
69
|
+
email = chunk.split('return false;">')[2].split('</a>')[0] rescue nil
|
70
|
+
[name, email] if email
|
71
|
+
end.compact
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
TYPES[:hotmail] = Hotmail
|
77
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Contacts
|
2
|
+
class Yahoo < Base
|
3
|
+
URL = "http://mail.yahoo.com/"
|
4
|
+
LOGIN_URL = "https://login.yahoo.com/config/login"
|
5
|
+
CONTACT_LIST_URL = "http://address.yahoo.com/"
|
6
|
+
PROTOCOL_ERROR = "Yahoo has changed its protocols, please upgrade this library first. If that does not work, contact lucas@rufy.com with this error"
|
7
|
+
|
8
|
+
def real_connect
|
9
|
+
postdata = ".tries=2&.src=ym&.md5=&.hash=&.js=&.last=&promo=&.intl=us&.bypass="
|
10
|
+
postdata += "&.partner=&.u=4eo6isd23l8r3&.v=0&.challenge=gsMsEcoZP7km3N3NeI4mX"
|
11
|
+
postdata += "kGB7zMV&.yplus=&.emailCode=&pkg=&stepid=&.ev=&hasMsgr=1&.chkP=Y&."
|
12
|
+
postdata += "done=#{CGI.escape(URL)}&login=#{CGI.escape(login)}&passwd=#{CGI.escape(password)}"
|
13
|
+
|
14
|
+
data, resp, cookies, forward = post(LOGIN_URL, postdata)
|
15
|
+
|
16
|
+
if data.index("Invalid ID or password")
|
17
|
+
raise AuthenticationError, "Username and password do not match"
|
18
|
+
elsif data.index("Sign in") && data.index("to Yahoo!")
|
19
|
+
raise AuthenticationError, "Required field must not be blank"
|
20
|
+
elsif data != ""
|
21
|
+
raise ConnectionError, PROTOCOL_ERROR
|
22
|
+
elsif cookies == ""
|
23
|
+
raise ConnectionError, PROTOCOL_ERROR
|
24
|
+
end
|
25
|
+
|
26
|
+
data, resp, cookies, forward = get(forward, cookies, LOGIN_URL)
|
27
|
+
|
28
|
+
if resp.code_type != Net::HTTPOK
|
29
|
+
raise ConnectionError, PROTOCOL_ERROR
|
30
|
+
end
|
31
|
+
|
32
|
+
@cookies = cookies
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def parse(data)
|
38
|
+
chunks = data.split("c_row")
|
39
|
+
chunks.delete_at(0)
|
40
|
+
|
41
|
+
processed_data = {}
|
42
|
+
chunks.each_with_index do |chunk, i|
|
43
|
+
processed_data[i/2] ||= []
|
44
|
+
if i%2 == 0 # then it is a name
|
45
|
+
processed_data[i/2] << chunk.split(/[^,]\n/).grep(/contactname/).first.gsub(/(<|^).*?(>|$)/,'').strip
|
46
|
+
else # it is an email
|
47
|
+
processed_data[i/2] << chunk.split.grep(/compose.mail.yahoo.com/).first.gsub(/(<|^).*?(>|$)/,'').strip rescue next
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@contacts = processed_data.values.delete_if{|data|data[1].nil?}
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
TYPES[:yahoo] = Yahoo
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.11
|
3
|
+
specification_version: 1
|
4
|
+
name: contacts
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.0.0
|
7
|
+
date: 2006-12-04 00:00:00 -08:00
|
8
|
+
summary: Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: lucas@rufy.com
|
12
|
+
homepage: http://rubyforge.org/projects/contacts
|
13
|
+
rubyforge_project:
|
14
|
+
description: Ridiculously easy contact list information from various providers including Yahoo, Gmail, and Hotmail
|
15
|
+
autorequire: contacts
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
authors:
|
29
|
+
- Lucas Carlson
|
30
|
+
files:
|
31
|
+
- lib/contacts
|
32
|
+
- lib/contacts.rb
|
33
|
+
- lib/contacts/base.rb
|
34
|
+
- lib/contacts/gmail.rb
|
35
|
+
- lib/contacts/hotmail.rb
|
36
|
+
- lib/contacts/yahoo.rb
|
37
|
+
- LICENSE
|
38
|
+
- Rakefile
|
39
|
+
- README
|
40
|
+
- examples/grab_contacts.rb
|
41
|
+
test_files: []
|
42
|
+
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
extra_rdoc_files: []
|
46
|
+
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
requirements:
|
52
|
+
- A json parser
|
53
|
+
dependencies:
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: json
|
56
|
+
version_requirement:
|
57
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.4.1
|
62
|
+
version:
|