im_contacts 1.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +61 -0
- data/Rakefile +91 -0
- data/examples/grab_contacts.rb +23 -0
- data/lib/contacts/aol.rb +154 -0
- data/lib/contacts/base.rb +229 -0
- data/lib/contacts/gmail.rb +35 -0
- data/lib/contacts/hash_ext.rb +9 -0
- data/lib/contacts/hotmail.rb +125 -0
- data/lib/contacts/json_picker.rb +16 -0
- data/lib/contacts/net_ease.rb +117 -0
- data/lib/contacts/plaxo.rb +130 -0
- data/lib/contacts/sina.rb +91 -0
- data/lib/contacts/sohu.rb +59 -0
- data/lib/contacts/yahoo.rb +103 -0
- data/lib/im_contacts.rb +17 -0
- data/test/example_accounts.yml +101 -0
- data/test/test_helper.rb +31 -0
- data/test/test_suite.rb +4 -0
- data/test/unit/aol_contact_importer_test.rb +39 -0
- data/test/unit/gmail_contact_importer_test.rb +39 -0
- data/test/unit/hotmail_contact_importer_test.rb +41 -0
- data/test/unit/net_ease_contact_importer_test.rb +60 -0
- data/test/unit/sina_contact_importer_test.rb +59 -0
- data/test/unit/sohu_contact_importer_test.rb +39 -0
- data/test/unit/test_accounts_test.rb +23 -0
- data/test/unit/yahoo_csv_contact_importer_test.rb +35 -0
- metadata +124 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
== Welcome to im_contacts
|
2
|
+
|
3
|
+
Contacts_cn is a universal interface to grab contact list information from various providers including Hotmail, AOL, Gmail, Plaxo, 126, 163, Yeah, Sina, Sohu and Yahoo.It is extended from contacts gem.
|
4
|
+
|
5
|
+
== Download
|
6
|
+
|
7
|
+
* gem install im_contacts
|
8
|
+
* http://github.com/liangwenke/im_contacts
|
9
|
+
* git clone git://github.com/liangwenke/im_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
|
+
Contacts::NetEase.new(login,password).contacts
|
21
|
+
Contacts::Sina.new(login,password).contacts
|
22
|
+
|
23
|
+
Contacts.new(:gmail, login, password).contacts
|
24
|
+
Contacts.new(:hotmail, login, password).contacts
|
25
|
+
Contacts.new(:yahoo, login, password).contacts
|
26
|
+
Contacts.new(:net_ease,login,password).contacts
|
27
|
+
Contacts.new(:sina,login,password).contacts
|
28
|
+
|
29
|
+
Contacts.guess(login, password).contacts
|
30
|
+
|
31
|
+
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.
|
32
|
+
|
33
|
+
== Captcha error
|
34
|
+
|
35
|
+
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:
|
36
|
+
|
37
|
+
Contacts::Gmail.new(login, password, :captcha_token => params[:captcha_token], :captcha_response => params[:captcha_response]).contacts
|
38
|
+
|
39
|
+
== Examples
|
40
|
+
|
41
|
+
See the examples/ directory.
|
42
|
+
|
43
|
+
== Authors
|
44
|
+
|
45
|
+
* Lucas Carlson from MOG (mailto:lucas@rufy.com) - http://mog.com
|
46
|
+
|
47
|
+
== Contributors
|
48
|
+
|
49
|
+
* Britt Selvitelle from Twitter (mailto:anotherbritt@gmail.com) - http://twitter.com
|
50
|
+
* Tony Targonski from GigPark (mailto:tony@gigpark.com) - http://gigpark.com
|
51
|
+
* Waheed Barghouthi from Watwet (mailto:waheed.barghouthi@gmail.com) - http://watwet.com
|
52
|
+
* Glenn Sidney from Glenn Fu (mailto:glenn@glennfu.com) - http://glennfu.com
|
53
|
+
* Brian McQuay from Onomojo (mailto:brian@onomojo.com) - http://onomojo.com
|
54
|
+
* Adam Hunter (mailto:adamhunter@me.com) - http://adamhunter.me/
|
55
|
+
* Glenn Ford (mailto:glenn@glennfu.com) - http://www.glennfu.com/
|
56
|
+
* Leonardo Wong (mailto:mac@boy.name)
|
57
|
+
* Rusty Burchfield
|
58
|
+
* justintv
|
59
|
+
* kame
|
60
|
+
|
61
|
+
This library is released under the terms of the BSD.
|
data/Rakefile
ADDED
@@ -0,0 +1,91 @@
|
|
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/im_contacts'
|
8
|
+
|
9
|
+
PKG_VERSION = Contacts::VERSION
|
10
|
+
|
11
|
+
PKG_FILES = FileList[
|
12
|
+
"lib/**/*", "bin/*", "test/**/*", "[A-Z]*", "Rakefile", "doc/**/*", "examples/**/*"
|
13
|
+
] - ["test/accounts.yml"]
|
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 = 'im_contacts'
|
47
|
+
s.version = PKG_VERSION
|
48
|
+
s.summary = <<-EOF
|
49
|
+
A universal interface to grab contact list information from various providers including Yahoo, AOL, Gmail, Hotmail, 126, 163, Yeah, Sohu, Sina and Plaxo.It is extended from contacts gem.
|
50
|
+
EOF
|
51
|
+
s.description = <<-EOF
|
52
|
+
A universal interface to grab contact list information from various providers including Yahoo, AOL, Gmail, Hotmail, 126, 163, Yeah, Sohu, Sina and Plaxo.It is extended from contacts gem.
|
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.add_dependency("gdata19", "~> 0.1.9.2")
|
66
|
+
s.requirements << "A json parser, the gdata ruby gem"
|
67
|
+
|
68
|
+
#### Documentation and testing.
|
69
|
+
|
70
|
+
s.has_rdoc = true
|
71
|
+
|
72
|
+
#### Author and project details.
|
73
|
+
|
74
|
+
s.authors = ["Mike Liang"]
|
75
|
+
s.email = ["liangwenke.com@gmail.com"]
|
76
|
+
s.homepage = "http://github.com/liangwenke/im_contacts"
|
77
|
+
end
|
78
|
+
|
79
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
80
|
+
pkg.need_zip = true
|
81
|
+
pkg.need_tar = true
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "Report code statistics (KLOCs, etc) from the application"
|
85
|
+
task :stats do
|
86
|
+
require 'code_statistics'
|
87
|
+
CodeStatistics.new(
|
88
|
+
["Library", "lib"],
|
89
|
+
["Units", "test"]
|
90
|
+
).to_s
|
91
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.dirname(__FILE__)+"/../lib/im_contacts"
|
2
|
+
|
3
|
+
login = ARGV[0]
|
4
|
+
password = ARGV[1]
|
5
|
+
#login is your email
|
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
|
13
|
+
|
14
|
+
Contacts.new(:yahoo,login,password).contacts
|
15
|
+
|
16
|
+
Contacts.new(:hotmail,login,password).contacts
|
17
|
+
|
18
|
+
#net_ease support 163.com, 126.com, yeah.net
|
19
|
+
Contacts.new(:net_ease,login,password).contacts
|
20
|
+
|
21
|
+
Contacts.new(:sina,login,password).contacts
|
22
|
+
|
23
|
+
Contacts.new(:sohu,login,password).contacts
|
data/lib/contacts/aol.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
class Contacts
|
2
|
+
require 'hpricot'
|
3
|
+
require 'csv'
|
4
|
+
class Aol < Base
|
5
|
+
URL = "http://www.aol.com/"
|
6
|
+
LOGIN_URL = "https://my.screenname.aol.com/_cqr/login/login.psp"
|
7
|
+
LOGIN_REFERER_URL = "http://webmail.aol.com/"
|
8
|
+
LOGIN_REFERER_PATH = "sitedomain=sns.webmail.aol.com&lang=en&locale=us&authLev=0&uitype=mini&loginId=&redirType=js&xchk=false"
|
9
|
+
AOL_NUM = "29970-343" # this seems to change each time they change the protocol
|
10
|
+
|
11
|
+
CONTACT_LIST_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ContactList.aspx?folder=Inbox&showUserFolders=False"
|
12
|
+
CONTACT_LIST_CSV_URL = "http://webmail.aol.com/#{AOL_NUM}/aim-2/en-us/Lite/ABExport.aspx?command=all"
|
13
|
+
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
|
+
def real_connect
|
16
|
+
if login.strip =~ /^(.+)@aol\.com$/ # strip off the @aol.com for AOL logins
|
17
|
+
login = $1
|
18
|
+
end
|
19
|
+
|
20
|
+
postdata = {
|
21
|
+
"loginId" => login,
|
22
|
+
"password" => password,
|
23
|
+
"rememberMe" => "on",
|
24
|
+
"_sns_fg_color_" => "",
|
25
|
+
"_sns_err_color_" => "",
|
26
|
+
"_sns_link_color_" => "",
|
27
|
+
"_sns_width_" => "",
|
28
|
+
"_sns_height_" => "",
|
29
|
+
"offerId" => "mail-second-en-us",
|
30
|
+
"_sns_bg_color_" => "",
|
31
|
+
"sitedomain" => "sns.webmail.aol.com",
|
32
|
+
"regPromoCode" => "",
|
33
|
+
"mcState" => "initialized",
|
34
|
+
"uitype" => "std",
|
35
|
+
"siteId" => "",
|
36
|
+
"lang" => "en",
|
37
|
+
"locale" => "us",
|
38
|
+
"authLev" => "0",
|
39
|
+
"siteState" => "",
|
40
|
+
"isSiteStateEncoded" => "false",
|
41
|
+
"use_aam" => "0",
|
42
|
+
"seamless" => "novl",
|
43
|
+
"aolsubmit" => CGI.escape("Sign In"),
|
44
|
+
"idType" => "SN",
|
45
|
+
"usrd" => "",
|
46
|
+
"doSSL" => "",
|
47
|
+
"redirType" => "",
|
48
|
+
"xchk" => "false"
|
49
|
+
}
|
50
|
+
|
51
|
+
# Get this cookie and stick it in the form to confirm to Aol that your cookies work
|
52
|
+
data, resp, cookies, forward = get(URL)
|
53
|
+
postdata["stips"] = cookie_hash_from_string(cookies)["stips"]
|
54
|
+
postdata["tst"] = cookie_hash_from_string(cookies)["tst"]
|
55
|
+
|
56
|
+
data, resp, cookies, forward, old_url = get(LOGIN_REFERER_URL, cookies) + [URL]
|
57
|
+
until forward.nil?
|
58
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
59
|
+
end
|
60
|
+
|
61
|
+
data, resp, cookies, forward, old_url = get("#{LOGIN_URL}?#{LOGIN_REFERER_PATH}", cookies) + [LOGIN_REFERER_URL]
|
62
|
+
until forward.nil?
|
63
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
64
|
+
end
|
65
|
+
|
66
|
+
doc = Hpricot(data)
|
67
|
+
(doc/:input).each do |input|
|
68
|
+
postdata["usrd"] = input.attributes["value"] if input.attributes["name"] == "usrd"
|
69
|
+
end
|
70
|
+
# parse data for <input name="usrd" value="2726212" type="hidden"> and add it to the postdata
|
71
|
+
|
72
|
+
postdata["SNS_SC"] = cookie_hash_from_string(cookies)["SNS_SC"]
|
73
|
+
postdata["SNS_LDC"] = cookie_hash_from_string(cookies)["SNS_LDC"]
|
74
|
+
postdata["LTState"] = cookie_hash_from_string(cookies)["LTState"]
|
75
|
+
# raise data.inspect
|
76
|
+
|
77
|
+
data, resp, cookies, forward, old_url = post(LOGIN_URL, h_to_query_string(postdata), cookies, LOGIN_REFERER_URL) + [LOGIN_REFERER_URL]
|
78
|
+
|
79
|
+
until forward.nil?
|
80
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
81
|
+
end
|
82
|
+
|
83
|
+
if data.index("Invalid Username or Password. Please try again.")
|
84
|
+
raise AuthenticationError, "Username and password do not match"
|
85
|
+
elsif data.index("Required field must not be blank")
|
86
|
+
raise AuthenticationError, "Login and password must not be blank"
|
87
|
+
elsif data.index("errormsg_0_logincaptcha")
|
88
|
+
raise AuthenticationError, "Captcha error"
|
89
|
+
elsif data.index("Invalid request")
|
90
|
+
raise ConnectionError, PROTOCOL_ERROR
|
91
|
+
elsif cookies == ""
|
92
|
+
raise ConnectionError, PROTOCOL_ERROR
|
93
|
+
end
|
94
|
+
|
95
|
+
@cookies = cookies
|
96
|
+
end
|
97
|
+
|
98
|
+
def contacts
|
99
|
+
postdata = {
|
100
|
+
"file" => 'contacts',
|
101
|
+
"fileType" => 'csv'
|
102
|
+
}
|
103
|
+
|
104
|
+
return @contacts if @contacts
|
105
|
+
if connected?
|
106
|
+
data, resp, cookies, forward, old_url = get(CONTACT_LIST_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
|
107
|
+
|
108
|
+
until forward.nil?
|
109
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
110
|
+
end
|
111
|
+
|
112
|
+
if resp.code_type != Net::HTTPOK
|
113
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
114
|
+
end
|
115
|
+
|
116
|
+
# parse data and grab <input name="user" value="8QzMPIAKs2" type="hidden">
|
117
|
+
doc = Hpricot(data)
|
118
|
+
(doc/:input).each do |input|
|
119
|
+
postdata["user"] = input.attributes["value"] if input.attributes["name"] == "user"
|
120
|
+
end
|
121
|
+
|
122
|
+
data, resp, cookies, forward, old_url = get(CONTACT_LIST_CSV_URL, @cookies, CONTACT_LIST_URL) + [CONTACT_LIST_URL]
|
123
|
+
|
124
|
+
until forward.nil?
|
125
|
+
data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
|
126
|
+
end
|
127
|
+
|
128
|
+
if data.include?("error.gif")
|
129
|
+
raise AuthenticationError, "Account invalid"
|
130
|
+
end
|
131
|
+
|
132
|
+
parse data
|
133
|
+
end
|
134
|
+
end
|
135
|
+
private
|
136
|
+
|
137
|
+
def parse(data, options={})
|
138
|
+
data = CSV::Reader.parse(data)
|
139
|
+
col_names = data.shift
|
140
|
+
@contacts = data.map do |person|
|
141
|
+
["#{person[0]} #{person[1]}", person[4]] if person[4] && !person[4].empty?
|
142
|
+
end.compact
|
143
|
+
end
|
144
|
+
|
145
|
+
def h_to_query_string(hash)
|
146
|
+
u = ERB::Util.method(:u)
|
147
|
+
hash.map { |k, v|
|
148
|
+
u.call(k) + "=" + u.call(v)
|
149
|
+
}.join("&")
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
TYPES[:aol] = Aol
|
154
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "net/http"
|
3
|
+
require "net/https"
|
4
|
+
require "uri"
|
5
|
+
require "zlib"
|
6
|
+
require "stringio"
|
7
|
+
require "thread"
|
8
|
+
require "erb"
|
9
|
+
|
10
|
+
class Contacts
|
11
|
+
TYPES = {}
|
12
|
+
VERSION = "1.2.7"
|
13
|
+
|
14
|
+
class Base
|
15
|
+
def initialize(login, password, options={})
|
16
|
+
@login = login
|
17
|
+
@password = password
|
18
|
+
@captcha_token = options[:captcha_token]
|
19
|
+
@captcha_response = options[:captcha_response]
|
20
|
+
@connections = {}
|
21
|
+
connect
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
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
|
+
real_connect
|
27
|
+
end
|
28
|
+
|
29
|
+
def connected?
|
30
|
+
@cookies && !@cookies.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def contacts(options = {})
|
34
|
+
return @contacts if @contacts
|
35
|
+
if connected?
|
36
|
+
url = URI.parse(contact_list_url)
|
37
|
+
http = open_http(url)
|
38
|
+
resp, data = http.get("#{url.path}?#{url.query}",
|
39
|
+
"Cookie" => @cookies
|
40
|
+
)
|
41
|
+
|
42
|
+
if resp.code_type != Net::HTTPOK
|
43
|
+
raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
|
44
|
+
end
|
45
|
+
|
46
|
+
parse(data, options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def login
|
51
|
+
@attempt ||= 0
|
52
|
+
@attempt += 1
|
53
|
+
|
54
|
+
if @attempt == 1
|
55
|
+
@login
|
56
|
+
else
|
57
|
+
if @login.include?("@#{domain}")
|
58
|
+
@login.sub("@#{domain}","")
|
59
|
+
else
|
60
|
+
"#{@login}@#{domain}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def login_with_domain
|
66
|
+
@login.include?("@#{domain}") ? "#{@login}" : "#{@login}@#{domain}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def login_without_domain
|
70
|
+
@login.include?("@#{domain}") ? @login.sub("@#{domain}",'') : "#{@login}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def password
|
74
|
+
@password
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def domain
|
80
|
+
@d ||= self.class.const_get(:DOMAIN) rescue nil
|
81
|
+
@d ||= URI.parse(self.class.const_get(:URL)).host.sub(/^www\./,'')
|
82
|
+
end
|
83
|
+
|
84
|
+
def contact_list_url
|
85
|
+
self.class.const_get(:CONTACT_LIST_URL)
|
86
|
+
end
|
87
|
+
|
88
|
+
def address_book_url
|
89
|
+
self.class.const_get(:ADDRESS_BOOK_URL)
|
90
|
+
end
|
91
|
+
|
92
|
+
def open_http(url)
|
93
|
+
c = @connections[Thread.current.object_id] ||= {}
|
94
|
+
http = c["#{url.host}:#{url.port}"]
|
95
|
+
unless http
|
96
|
+
http = Net::HTTP.new(url.host, url.port)
|
97
|
+
if url.port == 443
|
98
|
+
http.use_ssl = true
|
99
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
100
|
+
end
|
101
|
+
c["#{url.host}:#{url.port}"] = http
|
102
|
+
end
|
103
|
+
http.start unless http.started?
|
104
|
+
http
|
105
|
+
end
|
106
|
+
|
107
|
+
def cookie_hash_from_string(cookie_string)
|
108
|
+
cookie_string.split(";").map{|i|i.split("=", 2).map{|j|j.strip}}.inject({}){|h,i|h[i[0]]=i[1];h}
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse_cookies(data, existing="")
|
112
|
+
return existing if data.nil?
|
113
|
+
|
114
|
+
cookies = cookie_hash_from_string(existing)
|
115
|
+
|
116
|
+
data.gsub!(/ ?[\w]+=EXPIRED;/,'')
|
117
|
+
data.gsub!(/ ?expires=(.*?, .*?)[;,$]/i, ';')
|
118
|
+
data.gsub!(/ ?(domain|path)=[\S]*?[;,$]/i,';')
|
119
|
+
data.gsub!(/[,;]?\s*(secure|httponly)/i,'')
|
120
|
+
data.gsub!(/(;\s*){2,}/,', ')
|
121
|
+
data.gsub!(/(,\s*){2,}/,', ')
|
122
|
+
data.sub!(/^,\s*/,'')
|
123
|
+
data.sub!(/\s*,$/,'')
|
124
|
+
|
125
|
+
data.split(", ").map{|t|t.to_s.split(";").first}.each do |data|
|
126
|
+
k, v = data.split("=", 2).map{|j|j.strip}
|
127
|
+
if cookies[k] && v.empty?
|
128
|
+
cookies.delete(k)
|
129
|
+
elsif v && !v.empty?
|
130
|
+
cookies[k] = v
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
cookies.map{|k,v| "#{k}=#{v}"}.join("; ")
|
135
|
+
end
|
136
|
+
|
137
|
+
def remove_cookie(cookie, cookies)
|
138
|
+
parse_cookies("#{cookie}=", cookies)
|
139
|
+
end
|
140
|
+
|
141
|
+
def post(url, postdata, cookies="", referer="")
|
142
|
+
url = URI.parse(url)
|
143
|
+
http = open_http(url)
|
144
|
+
resp, data = http.post(url.path, postdata,
|
145
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
|
146
|
+
"Accept-Encoding" => "gzip",
|
147
|
+
"Cookie" => cookies,
|
148
|
+
"Referer" => referer,
|
149
|
+
"Content-Type" => 'application/x-www-form-urlencoded'
|
150
|
+
)
|
151
|
+
data = uncompress(resp, data)
|
152
|
+
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
153
|
+
forward = resp.response['Location']
|
154
|
+
forward ||= (data =~ /<meta.*?url='([^']+)'/ ? CGI.unescapeHTML($1) : nil)
|
155
|
+
if (not forward.nil?) && URI.parse(forward).host.nil?
|
156
|
+
forward = url.scheme.to_s + "://" + url.host.to_s + forward
|
157
|
+
end
|
158
|
+
return data, resp, cookies, forward
|
159
|
+
end
|
160
|
+
|
161
|
+
def get(url, cookies="", referer="")
|
162
|
+
url = URI.parse(url)
|
163
|
+
http = open_http(url)
|
164
|
+
resp, data = http.get("#{url.path}?#{url.query}",
|
165
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
|
166
|
+
"Accept-Encoding" => "gzip",
|
167
|
+
"Cookie" => cookies,
|
168
|
+
"Referer" => referer
|
169
|
+
)
|
170
|
+
data = uncompress(resp, data)
|
171
|
+
cookies = parse_cookies(resp.response['set-cookie'], cookies)
|
172
|
+
forward = resp.response['Location']
|
173
|
+
if (not forward.nil?) && URI.parse(forward).host.nil?
|
174
|
+
forward = url.scheme.to_s + "://" + url.host.to_s + forward
|
175
|
+
end
|
176
|
+
return data, resp, cookies, forward
|
177
|
+
end
|
178
|
+
|
179
|
+
def uncompress(resp, data)
|
180
|
+
case resp.response['content-encoding']
|
181
|
+
when 'gzip'
|
182
|
+
gz = Zlib::GzipReader.new(StringIO.new(data))
|
183
|
+
data = gz.read
|
184
|
+
gz.close
|
185
|
+
resp.response['content-encoding'] = nil
|
186
|
+
# FIXME: Not sure what Hotmail was feeding me with their 'deflate',
|
187
|
+
# but the headers definitely were not right
|
188
|
+
when 'deflate'
|
189
|
+
data = Zlib::Inflate.inflate(data)
|
190
|
+
resp.response['content-encoding'] = nil
|
191
|
+
end
|
192
|
+
|
193
|
+
data
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class ContactsError < StandardError
|
198
|
+
end
|
199
|
+
|
200
|
+
class AuthenticationError < ContactsError
|
201
|
+
end
|
202
|
+
|
203
|
+
class ConnectionError < ContactsError
|
204
|
+
end
|
205
|
+
|
206
|
+
class TypeNotFound < ContactsError
|
207
|
+
end
|
208
|
+
|
209
|
+
class MailServerError < ContactsError
|
210
|
+
end
|
211
|
+
|
212
|
+
def self.new(type, login, password, options={})
|
213
|
+
if TYPES.include?(type.to_s.intern)
|
214
|
+
TYPES[type.to_s.intern].new(login, password, options)
|
215
|
+
else
|
216
|
+
raise TypeNotFound, "#{type.inspect} is not a valid type, please choose one of the following: #{TYPES.keys.inspect}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.guess(login, password, options={})
|
221
|
+
TYPES.inject([]) do |a, t|
|
222
|
+
begin
|
223
|
+
a + t[1].new(login, password, options).contacts
|
224
|
+
rescue AuthenticationError
|
225
|
+
a
|
226
|
+
end
|
227
|
+
end.uniq
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'gdata'
|
2
|
+
|
3
|
+
class Contacts
|
4
|
+
class Gmail < Base
|
5
|
+
|
6
|
+
CONTACTS_SCOPE = 'http://www.google.com/m8/feeds/'
|
7
|
+
CONTACTS_FEED = CONTACTS_SCOPE + 'contacts/default/full/?max-results=1000'
|
8
|
+
|
9
|
+
def contacts
|
10
|
+
return @contacts if @contacts
|
11
|
+
end
|
12
|
+
|
13
|
+
def real_connect
|
14
|
+
@client = GData::Client::Contacts.new
|
15
|
+
@client.clientlogin(@login, @password, @captcha_token, @captcha_response)
|
16
|
+
|
17
|
+
feed = @client.get(CONTACTS_FEED).to_xml
|
18
|
+
|
19
|
+
@contacts = feed.elements.to_a('entry').collect do |entry|
|
20
|
+
title, email = entry.elements['title'].text, nil
|
21
|
+
entry.elements.each('gd:email') do |e|
|
22
|
+
email = e.attribute('address').value if e.attribute('primary')
|
23
|
+
end
|
24
|
+
[title, email] unless email.nil?
|
25
|
+
end
|
26
|
+
@contacts.compact!
|
27
|
+
rescue GData::Client::AuthorizationError => e
|
28
|
+
raise AuthenticationError, "Username or password are incorrect"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
TYPES[:gmail] = Gmail
|
34
|
+
end
|
35
|
+
end
|