contacts 1.0.13 → 1.0.15

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ PKG_VERSION = Contacts::VERSION
10
10
 
11
11
  PKG_FILES = FileList[
12
12
  "lib/**/*", "bin/*", "test/**/*", "[A-Z]*", "Rakefile", "doc/**/*", "examples/**/*"
13
- ]
13
+ ] - ["test/accounts.yml"]
14
14
 
15
15
  desc "Default Task"
16
16
  task :default => [ :test ]
data/lib/contacts/base.rb CHANGED
@@ -8,7 +8,7 @@ require "thread"
8
8
 
9
9
  class Contacts
10
10
  TYPES = {}
11
- VERSION = "1.0.13"
11
+ VERSION = "1.0.15"
12
12
 
13
13
  class Base
14
14
  def initialize(login, password)
@@ -1,4 +1,14 @@
1
- require "json/add/rails"
1
+ begin
2
+ # If the json gem is available, use it
3
+ require "json/add/rails"
4
+ rescue MissingSourceFile
5
+ # Otherwise wrap the ActiveSupport JSON implementation for our simple use case
6
+ class JSON
7
+ def self.parse(i)
8
+ ActiveSupport::JSON.decode(i)
9
+ end
10
+ end
11
+ end
2
12
 
3
13
  class Contacts
4
14
  class Gmail < Base
@@ -28,8 +38,10 @@ class Contacts
28
38
  data, resp, cookies, forward, old_url = post(LOGIN_URL, postdata, cookie, LOGIN_REFERER_URL) + [LOGIN_URL]
29
39
 
30
40
  cookies = remove_cookie("GMAIL_LOGIN", cookies)
31
-
32
- if data.index("Username and password do not match")
41
+
42
+ if data.index("Username and password do not match") || data.index("New to Gmail? It's free and easy")
43
+ raise AuthenticationError, "Username and password do not match"
44
+ elsif data.index("The username or password you entered is incorrect")
33
45
  raise AuthenticationError, "Username and password do not match"
34
46
  elsif data.index("Required field must not be blank")
35
47
  raise AuthenticationError, "Login and password must not be blank"
@@ -79,4 +91,4 @@ class Contacts
79
91
  end
80
92
 
81
93
  TYPES[:gmail] = Gmail
82
- end
94
+ end
@@ -1,9 +1,9 @@
1
1
  class Contacts
2
2
  class Hotmail < Base
3
- URL = "http://login.live.com/login.srf?id=2"
3
+ URL = "https://login.live.com/login.srf?id=2"
4
4
  OLD_CONTACT_LIST_URL = "http://%s/cgi-bin/addresses"
5
5
  NEW_CONTACT_LIST_URL = "http://%s/mail/GetContacts.aspx"
6
- NEWEST_CONTACT_LIST_URL = "http://%s/mail/options.aspx?subsection=26"
6
+ CONTACT_LIST_URL = "http://mpeople.live.com/default.aspx?pg=0"
7
7
  COMPOSE_URL = "http://%s/cgi-bin/compose?"
8
8
  PROTOCOL_ERROR = "Hotmail has changed its protocols, please upgrade this library first. If that does not work, report this error at http://rubyforge.org/forum/?group_id=2693"
9
9
  PWDPAD = "IfYouAreReadingThisYouHaveTooMuchFreeTime"
@@ -11,7 +11,6 @@ class Contacts
11
11
 
12
12
  def real_connect
13
13
  data, resp, cookies, forward = get(URL)
14
-
15
14
  old_url = URL
16
15
  until forward.nil?
17
16
  data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
@@ -37,55 +36,16 @@ class Contacts
37
36
  end
38
37
 
39
38
  old_url = form_url
39
+
40
40
  until forward.nil?
41
41
  data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
42
42
  end
43
-
44
- =begin
45
- if data =~ %r{action="(.*?)"}
46
- forward = $1
47
- puts forward
48
- napexp = CGI.escape(data.to_s[/id="NAPExp" value="(.*?)"/][19...-1])
49
- nap = CGI.escape(data.to_s[/id="NAP" value="(.*?)"/][16...-1])
50
- anon = CGI.escape(data.to_s[/id="ANON" value="(.*?)"/][17...-1])
51
- anonexp = CGI.escape(data.to_s[/id="ANONExp" value="(.*?)"/][20...-1])
52
- t = CGI.escape(data.to_s[/id="t" value="(.*?)"/][14...-1])
53
-
54
- postdata = "NAPExp=%s&NAP=%s&ANON=%s&ANONExp=%s&t=%s" % [ napexp, nap, anon, anonexp, t ]
55
- puts postdata
56
- data, resp, cookies, forward, old_url = post(forward, postdata, cookies, old_url) + [forward]
57
- end
58
-
59
- until forward.nil?
60
- data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
61
- end
62
- =end
63
43
 
64
44
  data, resp, cookies, forward = get("http://mail.live.com/mail", cookies)
65
45
  until forward.nil?
66
46
  data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
67
47
  end
68
-
69
- # click on 'Contiune' if presented with the Hotmail Listened page
70
- # look for the Submit button with a "TakeMeToInbox" name (this should work for other languages)
71
- if (not old_url.grep(/MessageAtLogin.aspx/).first.nil?)
72
-
73
- viewState = data.split(/>\s*?</).grep(/__VIEWSTATE/).first[/value=\".+?\"/][7..-2]
74
- eventValidation = data.split(/>\s*?</).grep(/__EVENTVALIDATION/).first[/value=\".+?\"/][7..-2]
75
- continueValue = data.split(/>\s*?</).grep(/TakeMeToInbox/).first[/value=\".+?\"/][7..-2]
76
-
77
- # post back to the same url
78
- postdata = "%s=%s&%s=%s&%s=%s" % [
79
- "__VIEWSTATE", CGI.escape(viewState),
80
- "__EVENTVALIDATION", CGI.escape(eventValidation),
81
- CGI.escape("TakeMeToInbox"), CGI.escape(continueValue)
82
- ]
83
- data, resp, cookies, forward = post( old_url, postdata, cookies, old_url )
84
- until forward.nil?
85
- data, resp, cookies, forward, old_url = get(forward, cookies, old_url) + [forward]
86
- end
87
- end
88
-
48
+
89
49
  @domain = URI.parse(old_url).host
90
50
  @cookies = cookies
91
51
  rescue AuthenticationError => m
@@ -97,7 +57,6 @@ class Contacts
97
57
  end
98
58
 
99
59
  def contacts(options = {})
100
- return @contacts if @contacts
101
60
  if connected?
102
61
  url = URI.parse(contact_list_url)
103
62
  data, resp, cookies, forward = get( contact_list_url, @cookies )
@@ -105,75 +64,62 @@ class Contacts
105
64
  if resp.code_type != Net::HTTPOK
106
65
  raise ConnectionError, self.class.const_get(:PROTOCOL_ERROR)
107
66
  end
108
-
109
- # we have to click on the Export Contacts button to get the csv:
110
- # Search the content for __VIEWSTATE or __EVENTVALIDATION
111
- viewState = data.split(/>\s*?</).grep(/__VIEWSTATE/).first[/value=\".+?\"/][7..-2]
112
- eventValidation = data.split(/>\s*?</).grep(/__EVENTVALIDATION/).first[/value=\".+?\"/][7..-2]
113
- exportValue = data.split(/>\s*?</).grep(/ctl02\$ExportButton/).first[/value=\".+?\"/][7..-2]
114
- mt = cookies.split("; ").grep(/mt=/).first[3..-1]
115
67
 
116
- # post back to the same url
117
- postdata = "%s=%s&%s=%s&%s=%s&%s=%s" % [
118
- "__VIEWSTATE", CGI.escape(viewState),
119
- "__EVENTVALIDATION", CGI.escape(eventValidation),
120
- CGI.escape("ctl02$ExportButton"), CGI.escape(exportValue),
121
- "mt", CGI.escape( mt )
122
- ]
123
-
124
- url = URI.parse(contact_list_url)
125
- http = open_http(url)
126
- resp, data = http.post("#{url.path}?#{url.query}", postdata,
127
- "User-Agent" => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0",
128
- "Accept-Encoding" => "gzip",
129
- "Cookie" => cookies,
130
- "Referer" => contact_list_url,
131
- "Content-Type" => 'application/x-www-form-urlencoded'
132
- )
133
-
134
- data = uncompress(resp, data)
135
- parse(data, options)
68
+ @contacts = []
69
+ build_contacts = []
70
+ go = true
71
+ index = 0
72
+
73
+ while(go) do
74
+ go = false
75
+ url = URI.parse(get_contact_list_url(index))
76
+ http = open_http(url)
77
+ resp, data = http.get(get_contact_list_url(index), "Cookie" => @cookies)
78
+
79
+ email_match_text_beginning = Regexp.escape("http://m.mail.live.com/?rru=compose&amp;to=")
80
+ email_match_text_end = Regexp.escape("&amp;")
81
+
82
+ raw_html = resp.body.grep(/(?:e|dn)lk[0-9]+/)
83
+ raw_html.delete_at 0
84
+ raw_html.inject do |memo, row|
85
+ c_info = row.match(/(e|dn)lk([0-9])+/)
86
+
87
+ # Same contact, or different?
88
+ build_contacts << [] if memo != c_info[2]
89
+
90
+ # Grab info
91
+ case c_info[1]
92
+ when "e" # Email
93
+ build_contacts.last[1] = row.match(/#{email_match_text_beginning}(.*)#{email_match_text_end}/)[1]
94
+ when "dn" # Name
95
+ build_contacts.last[0] = row.match(/<a[^>]*>(.+)<\/a>/)[1]
96
+ end
97
+
98
+ # Set memo to contact id
99
+ c_info[2]
100
+ end
101
+
102
+ go = resp.body.include?("Next page")
103
+ index += 1
104
+ end
105
+
106
+ build_contacts.each do |contact|
107
+ unless contact[1].nil?
108
+ # Only return contacts with email addresses
109
+ contact[1] = CGI::unescape(contact[1])
110
+ @contacts << contact
111
+ end
112
+ end
113
+ return @contacts
136
114
  end
137
115
  end
138
-
139
-
140
- private
141
116
 
142
- def contact_list_url
143
- NEWEST_CONTACT_LIST_URL % @domain
117
+ def get_contact_list_url(index)
118
+ "http://mpeople.live.com/default.aspx?pg=#{index}"
144
119
  end
145
120
 
146
- def follow_email(data, id, contacts_slot)
147
- compose_url = COMPOSE_URL % @domain
148
- postdata = "HrsTest=&to=#{id}&mailto=1&ref=addresses"
149
- postdata += "&curmbox=00000000-0000-0000-0000-000000000001"
150
-
151
- a = data.split(/>\s*<input\s+/i).grep(/\s+name="a"/i)
152
- return nil if a.empty?
153
-
154
- a = a[0].match(/\s+value="([a-f0-9]+)"/i) or return nil
155
- postdata += "&a=#{a[1]}"
156
-
157
- data, resp, @cookies, forward = post(compose_url, postdata, @cookies)
158
- e = data.split(/>\s*<input\s+/i).grep(/\s+name="to"/i)
159
- return nil if e.empty?
160
-
161
- e = e[0].match(/\s+value="([^"]+)"/i) or return nil
162
- @contacts[contacts_slot][1] = e[1] if e[1].match(/@/)
163
- end
164
-
165
- def parse(data, options={})
166
- data = data.split("\r\n")
167
- data = CSV.parse(data.join("\r\n").gsub('"', '').gsub(';', ','), ';')
168
- col_names = data.shift
169
-
170
- @contacts = data.delete_if{|person|person[0].nil?}.map do |person|
171
- person = person[0].split(",")
172
- next unless (idx = person.index('SMTP'))
173
- [[person[1], person[2], person[3]].delete_if{|i|i.empty?}.join(" "), person[idx - 1]] unless person[idx - 1].nil?
174
- end.compact
175
- end
121
+ private
122
+
123
+ TYPES[:hotmail] = Hotmail
176
124
  end
177
-
178
- TYPES[:hotmail] = Hotmail
179
125
  end
@@ -34,8 +34,8 @@ class Contacts
34
34
  elsif code == '200'
35
35
  @contacts = []
36
36
  doc.elements.each('//contact') do |cont|
37
- name = cont.elements['fullName'].text #rescue nil
38
- email = cont.elements['email1'].text #rescue nil
37
+ name = cont.elements['fullName'].nil? ? cont.elements['displayName'].text : cont.elements['fullName'].text
38
+ email = cont.elements['email1'].text
39
39
  @contacts << [name, email]
40
40
  end.compact
41
41
  @contacts
@@ -53,7 +53,7 @@ end # Contacts
53
53
 
54
54
 
55
55
  # sample contacts responses
56
- '
56
+ =begin
57
57
  Bad email
58
58
  =========
59
59
  <?xml version="1.0" encoding="utf-8" ?>
@@ -119,5 +119,4 @@ Success
119
119
  <editCounter>3</editCounter>
120
120
 
121
121
  </ns1:GetContactsResponse>
122
-
123
- '
122
+ =end
@@ -0,0 +1,40 @@
1
+ gmail:
2
+ username: <changeme>
3
+ password: <changeme>
4
+ contacts:
5
+ -
6
+ name: "FirstName1 LastName1"
7
+ email_address: "firstname1@example.com"
8
+ -
9
+ name: "FirstName2 LastName2"
10
+ email_address: "firstname2@example.com"
11
+ yahoo:
12
+ username: <changeme>
13
+ password: <changeme>
14
+ contacts:
15
+ -
16
+ name: "FirstName1 LastName1"
17
+ email_address: "firstname1@example.com"
18
+ -
19
+ name: "FirstName2 LastName2"
20
+ email_address: "firstname2@example.com"
21
+ hotmail:
22
+ username: <changeme>
23
+ password: <changeme>
24
+ contacts:
25
+ -
26
+ name: "FirstName1 LastName1"
27
+ email_address: "firstname1@example.com"
28
+ -
29
+ name: "FirstName2 LastName2"
30
+ email_address: "firstname2@example.com"
31
+ aol:
32
+ username: <changeme>
33
+ password: <changeme>
34
+ contacts:
35
+ -
36
+ name: "FirstName1 LastName1"
37
+ email_address: "firstname1@example.com"
38
+ -
39
+ name: "FirstName2 LastName2"
40
+ email_address: "firstname2@example.com"
@@ -0,0 +1,30 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(dir + "/../lib/")
3
+ require 'test/unit'
4
+ require 'contacts'
5
+
6
+ class ContactImporterTestCase < Test::Unit::TestCase
7
+ # Add more helper methods to be used by all tests here...
8
+ def default_test
9
+ assert true
10
+ end
11
+ end
12
+
13
+ class TestAccounts
14
+ def self.[](type)
15
+ load[type]
16
+ end
17
+
18
+ def self.load(file = File.dirname(__FILE__) + "/accounts.yml")
19
+ raise "/test/accounts.yml file not found, please create, see /test/example_accounts.yml for information" unless File.exist?(file)
20
+
21
+ accounts = {}
22
+ YAML::load(File.open(file)).each do |type, contents|
23
+ contacts = contents["contacts"].collect {|contact| [contact["name"], contact["email_address"]]}
24
+ accounts[type.to_sym] = Account.new(type.to_sym, contents["username"], contents["password"], contacts)
25
+ end
26
+ accounts
27
+ end
28
+
29
+ Account = Struct.new :type, :username, :password, :contacts
30
+ end
@@ -0,0 +1,4 @@
1
+ dir = File.dirname(__FILE__)
2
+ Dir["#{dir}/**/*_test.rb"].each do |file|
3
+ require file
4
+ end
@@ -0,0 +1,39 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class GmailContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:gmail]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:gmail, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:gmail, @account.username, "wrong_password")
18
+ end
19
+ end
20
+
21
+ def test_importer_fails_with_blank_password
22
+ assert_raise(Contacts::AuthenticationError) do
23
+ Contacts.new(:gmail, @account.username, "")
24
+ end
25
+ end
26
+
27
+ def test_importer_fails_with_blank_username
28
+ assert_raise(Contacts::AuthenticationError) do
29
+ Contacts.new(:gmail, "", @account.password)
30
+ end
31
+ end
32
+
33
+ def test_fetch_contacts
34
+ contacts = Contacts.new(:gmail, @account.username, @account.password).contacts
35
+ @account.contacts.each do |contact|
36
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class HotmailContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:hotmail]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:hotmail, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:hotmail, @account.username,"wrong_password")
18
+ end
19
+ end
20
+
21
+ def test_fetch_contacts
22
+ contacts = Contacts.new(:hotmail, @account.username, @account.password).contacts
23
+ @account.contacts.each do |contact|
24
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+
4
+ class TestAccountsTest < ContactImporterTestCase
5
+ def test_test_accounts_loads_data_from_example_accounts_file
6
+ account = TestAccounts.load(File.dirname(__FILE__) + "/../example_accounts.yml")[:gmail]
7
+
8
+ assert_equal :gmail, account.type
9
+ assert_equal "<changeme>", account.username
10
+ assert_equal "<changeme>", account.password
11
+ assert_equal [["FirstName1 LastName1", "firstname1@example.com"], ["FirstName2 LastName2", "firstname2@example.com"]], account.contacts
12
+ end
13
+
14
+ def test_test_accounts_blows_up_if_file_doesnt_exist
15
+ assert_raise(RuntimeError) do
16
+ TestAccounts.load("file_that_does_not_exist.yml")
17
+ end
18
+ end
19
+
20
+ def test_we_can_load_from_account_file
21
+ assert_not_nil TestAccounts[:gmail].username
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ dir = File.dirname(__FILE__)
2
+ require "#{dir}/../test_helper"
3
+ require 'contacts'
4
+
5
+ class YahooContactImporterTest < ContactImporterTestCase
6
+ def setup
7
+ super
8
+ @account = TestAccounts[:yahoo]
9
+ end
10
+
11
+ def test_successful_login
12
+ Contacts.new(:yahoo, @account.username, @account.password)
13
+ end
14
+
15
+ def test_importer_fails_with_invalid_password
16
+ assert_raise(Contacts::AuthenticationError) do
17
+ Contacts.new(:yahoo, @account.username,"wrong_password")
18
+ end
19
+ # run the "successful" login test to ensure we reset yahoo's failed login lockout counter
20
+ # See http://www.pivotaltracker.com/story/show/138210
21
+ assert_nothing_raised do
22
+ Contacts.new(:yahoo, @account.username, @account.password)
23
+ end
24
+ end
25
+
26
+ def test_fetch_contacts
27
+ contacts = Contacts.new(:yahoo, @account.username, @account.password).contacts
28
+ @account.contacts.each do |contact|
29
+ assert contacts.include?(contact), "Could not find: #{contact.inspect} in #{contacts.inspect}"
30
+ end
31
+ end
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contacts
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.13
4
+ version: 1.0.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucas Carlson
@@ -9,7 +9,7 @@ autorequire: contacts
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-08 00:00:00 -08:00
12
+ date: 2009-07-11 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -38,6 +38,14 @@ files:
38
38
  - lib/contacts/plaxo.rb
39
39
  - lib/contacts/yahoo.rb
40
40
  - lib/contacts.rb
41
+ - test/example_accounts.yml
42
+ - test/test_helper.rb
43
+ - test/test_suite.rb
44
+ - test/unit
45
+ - test/unit/gmail_contact_importer_test.rb
46
+ - test/unit/hotmail_contact_importer_test.rb
47
+ - test/unit/test_accounts_test.rb
48
+ - test/unit/yahoo_csv_contact_importer_test.rb
41
49
  - LICENSE
42
50
  - Rakefile
43
51
  - README