graybook 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/CHANGES.md +49 -0
- data/Manifest.txt +59 -0
- data/README.md +66 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/VERSION.yml +4 -0
- data/VERSION_NAME +1 -0
- data/debug_graybook.rb +10 -0
- data/init.rb +1 -0
- data/lib/graybook/exporter/base.rb +16 -0
- data/lib/graybook/exporter/vcf.rb +45 -0
- data/lib/graybook/exporter/xml.rb +28 -0
- data/lib/graybook/importer/aol.rb +87 -0
- data/lib/graybook/importer/base.rb +39 -0
- data/lib/graybook/importer/csv.rb +74 -0
- data/lib/graybook/importer/freenet.rb +62 -0
- data/lib/graybook/importer/gmail.rb +84 -0
- data/lib/graybook/importer/gmx.rb +68 -0
- data/lib/graybook/importer/hotmail.rb +135 -0
- data/lib/graybook/importer/page_scraper.rb +86 -0
- data/lib/graybook/importer/web.de.rb +67 -0
- data/lib/graybook/importer/yahoo.rb +63 -0
- data/lib/graybook.rb +83 -0
- data/test/config/credentials.yml.example +9 -0
- data/test/fixtures/aol_application_page.html +566 -0
- data/test/fixtures/aol_bad_login_response_stage_3.html +565 -0
- data/test/fixtures/aol_contacts.html +102 -0
- data/test/fixtures/aol_login_response_stage_1.html +158 -0
- data/test/fixtures/aol_login_response_stage_2.html +559 -0
- data/test/fixtures/aol_login_response_stage_3.html +48 -0
- data/test/fixtures/aol_login_response_stage_4.html +404 -0
- data/test/fixtures/aol_login_response_stage_5.html +404 -0
- data/test/fixtures/aol_new_contacts.html +431 -0
- data/test/fixtures/gmail.csv +3 -0
- data/test/fixtures/gmail_bad_login_response_stage_2.html +560 -0
- data/test/fixtures/gmail_contacts.html +228 -0
- data/test/fixtures/gmail_login_response_stage_1.html +556 -0
- data/test/fixtures/gmail_login_response_stage_2.html +1 -0
- data/test/fixtures/gmail_login_response_stage_2a.html +1 -0
- data/test/fixtures/gmail_login_response_stage_3.html +249 -0
- data/test/fixtures/gmail_redirect_body.html +10 -0
- data/test/fixtures/hotmail_bad_login_response_stage_2.html +31 -0
- data/test/fixtures/hotmail_contacts.html +262 -0
- data/test/fixtures/hotmail_login_response_stage_1.html +31 -0
- data/test/fixtures/hotmail_login_response_stage_2.html +1 -0
- data/test/fixtures/hotmail_login_response_stage_3.html +519 -0
- data/test/fixtures/hotmail_scrape_first_page.html +77 -0
- data/test/fixtures/hotmail_scrape_response_stage_1.html +90 -0
- data/test/fixtures/hotmail_scrape_response_stage_2.html +77 -0
- data/test/fixtures/hotmail_scrape_response_stage_3.html +0 -0
- data/test/fixtures/yahoo_bad_login_response_stage_2.html +443 -0
- data/test/fixtures/yahoo_contacts.csv +3 -0
- data/test/fixtures/yahoo_contacts_not_logged_in.html +432 -0
- data/test/fixtures/yahoo_contacts_stage_1.html +399 -0
- data/test/fixtures/yahoo_login_response_stage_1.html +433 -0
- data/test/fixtures/yahoo_login_response_stage_2.html +16 -0
- data/test/fixtures/yahoo_no_user_response_stage_2.html +574 -0
- data/test/freenet_importer_test.rb +53 -0
- data/test/gmx_importer_test.rb +53 -0
- data/test/scripts/live_test.rb +25 -0
- data/test/test_graybook.rb +60 -0
- data/test/test_graybook_exporter_base.rb +16 -0
- data/test/test_graybook_exporter_vcf.rb +52 -0
- data/test/test_graybook_exporter_xml.rb +16 -0
- data/test/test_graybook_importer_aol.rb +108 -0
- data/test/test_graybook_importer_base.rb +24 -0
- data/test/test_graybook_importer_csv.rb +60 -0
- data/test/test_graybook_importer_gmail.rb +116 -0
- data/test/test_graybook_importer_hotmail.rb +165 -0
- data/test/test_graybook_importer_page_scraper.rb +51 -0
- data/test/test_graybook_importer_yahoo.rb +137 -0
- data/test/test_helper.rb +71 -0
- data/test/web.de_importer_test.rb +53 -0
- data/updater.rb +15 -0
- data/vendor/plugins/graybook/lib/autotest/discover.rb +3 -0
- data/vendor/plugins/graybook/lib/autotest/graybook.rb +27 -0
- metadata +185 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.DS_Store
|
data/CHANGES.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
== 1.0.20 / 2009-09-30
|
2
|
+
|
3
|
+
* lots of changes undocumented here.
|
4
|
+
* putting on gemcutter.
|
5
|
+
|
6
|
+
== 1.0.5 / 2008-11-26
|
7
|
+
|
8
|
+
* moved to github
|
9
|
+
* added patch for gmail. emails were coming in with notes.
|
10
|
+
* added german stuff to graybook gem
|
11
|
+
|
12
|
+
== 1.0.4 / 2008-02-27
|
13
|
+
|
14
|
+
* 1 minor enhancement
|
15
|
+
* fix 1.0.3 dud with AOL issues - Thank you Howard Wong!
|
16
|
+
|
17
|
+
== 1.0.3 / 2008-02-27
|
18
|
+
|
19
|
+
* 1 minor enhancement
|
20
|
+
* address the change in where AOL contacts are listed
|
21
|
+
|
22
|
+
== 1.0.2 / 2008-02-20
|
23
|
+
|
24
|
+
* 1 minor enhancement
|
25
|
+
* fixed sign on issue with GMail
|
26
|
+
|
27
|
+
== 1.0.1 / 2008-02-08
|
28
|
+
|
29
|
+
* 2 minor enhancement
|
30
|
+
* fix edge case sign on issue with GMail
|
31
|
+
|
32
|
+
== 1.0.0 / 2008-01-30
|
33
|
+
|
34
|
+
* 2 major enhancements
|
35
|
+
* Birthday!
|
36
|
+
* Mechanize patch for AOL sign-on! Thanks Mortee!
|
37
|
+
|
38
|
+
* Importers
|
39
|
+
* AOL
|
40
|
+
* GMail
|
41
|
+
* Hotmail
|
42
|
+
* Yahoo!
|
43
|
+
* CSV
|
44
|
+
|
45
|
+
* Exporters
|
46
|
+
* Hash
|
47
|
+
* XML
|
48
|
+
* VCard
|
49
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
CHANGES.markdown
|
2
|
+
Manifest.txt
|
3
|
+
README.markdown
|
4
|
+
Rakefile
|
5
|
+
debug_graybook.rb
|
6
|
+
init.rb
|
7
|
+
lib/graybook.rb
|
8
|
+
lib/graybook/exporter/base.rb
|
9
|
+
lib/graybook/exporter/vcf.rb
|
10
|
+
lib/graybook/exporter/xml.rb
|
11
|
+
lib/graybook/importer/aol.rb
|
12
|
+
lib/graybook/importer/base.rb
|
13
|
+
lib/graybook/importer/csv.rb
|
14
|
+
lib/graybook/importer/gmail.rb
|
15
|
+
lib/graybook/importer/hotmail.rb
|
16
|
+
lib/graybook/importer/page_scraper.rb
|
17
|
+
lib/graybook/importer/yahoo.rb
|
18
|
+
test/fixtures/aol_application_page.html
|
19
|
+
test/fixtures/aol_bad_login_response_stage_3.html
|
20
|
+
test/fixtures/aol_contacts.html
|
21
|
+
test/fixtures/aol_login_response_stage_1.html
|
22
|
+
test/fixtures/aol_login_response_stage_2.html
|
23
|
+
test/fixtures/aol_login_response_stage_3.html
|
24
|
+
test/fixtures/aol_login_response_stage_4.html
|
25
|
+
test/fixtures/aol_login_response_stage_5.html
|
26
|
+
test/fixtures/aol_new_contacts.html
|
27
|
+
test/fixtures/gmail.csv
|
28
|
+
test/fixtures/gmail_bad_login_response_stage_2.html
|
29
|
+
test/fixtures/gmail_contacts.html
|
30
|
+
test/fixtures/gmail_login_response_stage_1.html
|
31
|
+
test/fixtures/gmail_login_response_stage_2.html
|
32
|
+
test/fixtures/gmail_login_response_stage_2a.html
|
33
|
+
test/fixtures/gmail_login_response_stage_3.html
|
34
|
+
test/fixtures/hotmail_bad_login_response_stage_2.html
|
35
|
+
test/fixtures/hotmail_contacts.html
|
36
|
+
test/fixtures/hotmail_login_response_stage_1.html
|
37
|
+
test/fixtures/hotmail_login_response_stage_2.html
|
38
|
+
test/fixtures/hotmail_login_response_stage_3.html
|
39
|
+
test/fixtures/yahoo_bad_login_response_stage_2.html
|
40
|
+
test/fixtures/yahoo_contacts.csv
|
41
|
+
test/fixtures/yahoo_contacts_not_logged_in.html
|
42
|
+
test/fixtures/yahoo_contacts_stage_1.html
|
43
|
+
test/fixtures/yahoo_login_response_stage_1.html
|
44
|
+
test/fixtures/yahoo_login_response_stage_2.html
|
45
|
+
test/scripts/live_test.rb
|
46
|
+
test/test_graybook.rb
|
47
|
+
test/test_graybook_exporter_base.rb
|
48
|
+
test/test_graybook_exporter_vcf.rb
|
49
|
+
test/test_graybook_exporter_xml.rb
|
50
|
+
test/test_graybook_importer_aol.rb
|
51
|
+
test/test_graybook_importer_base.rb
|
52
|
+
test/test_graybook_importer_csv.rb
|
53
|
+
test/test_graybook_importer_gmail.rb
|
54
|
+
test/test_graybook_importer_hotmail.rb
|
55
|
+
test/test_graybook_importer_page_scraper.rb
|
56
|
+
test/test_graybook_importer_yahoo.rb
|
57
|
+
test/test_helper.rb
|
58
|
+
vendor/plugins/graybook/lib/autotest/graybook.rb
|
59
|
+
vendor/plugins/graybook/lib/autotest/discover.rb
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
Graybook
|
2
|
+
|
3
|
+
== Description:
|
4
|
+
|
5
|
+
Pulls contact lists from EVERYWHERE! (Some sites not included.)
|
6
|
+
|
7
|
+
== Features:
|
8
|
+
|
9
|
+
The current list of supported services and file types:
|
10
|
+
|
11
|
+
import:
|
12
|
+
- AOL
|
13
|
+
- CSV files
|
14
|
+
- Gmail
|
15
|
+
- Hotmail
|
16
|
+
- Yahoo! Mail
|
17
|
+
|
18
|
+
export:
|
19
|
+
- Simple hash (default)
|
20
|
+
- JSON (soon)
|
21
|
+
|
22
|
+
If you create an additional importer or exporter - or simply find a bug - please consider submitting it as a patch to the project so the community can all benefit from your hard work and ingenuity.
|
23
|
+
|
24
|
+
== Synopsis:
|
25
|
+
|
26
|
+
# An example of fetching Gmail contacts - by default, returns an array of hashes with :name and :email
|
27
|
+
contacts = Graybook.get :username => 'me@gmail.com', :password => 'whatever'
|
28
|
+
|
29
|
+
== Requirements:
|
30
|
+
|
31
|
+
* Mechanize and its dependencies, for interacting with online providers
|
32
|
+
* Fastercsv for reading CSV, Mechanize >= 0.7.0 for page scraping
|
33
|
+
|
34
|
+
== Install:
|
35
|
+
|
36
|
+
1. gem install gemcutter
|
37
|
+
1. gem tumble
|
38
|
+
2. gem install graybook
|
39
|
+
|
40
|
+
== Thanks:
|
41
|
+
|
42
|
+
Big thanks to Contentfree for making Blackbook.
|
43
|
+
|
44
|
+
== License:
|
45
|
+
|
46
|
+
Copyright (c) 2009, kojul.com
|
47
|
+
Copyright (c) 2007, Contentfree
|
48
|
+
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
50
|
+
a copy of this software and associated documentation files (the
|
51
|
+
'Software'), to deal in the Software without restriction, including
|
52
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
53
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
54
|
+
permit persons to whom the Software is furnished to do so, subject to
|
55
|
+
the following conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be
|
58
|
+
included in all copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
61
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
62
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
63
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
64
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
65
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
66
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
$LOAD_PATH.unshift 'lib'
|
6
|
+
require 'graybook'
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'rcov/rcovtask'
|
10
|
+
rescue LoadError
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
Rcov::RcovTask.new do |t|
|
15
|
+
t.test_files = FileList['test/test*.rb']
|
16
|
+
t.verbose = true
|
17
|
+
#t.rcov_opts << "--exclude rcov.rb,hpricot.rb,hpricot/.*\.rb"
|
18
|
+
end
|
19
|
+
rescue NameError
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rubygems'
|
23
|
+
|
24
|
+
begin
|
25
|
+
require 'jeweler'
|
26
|
+
Jeweler::Tasks.new do |s|
|
27
|
+
s.name = "graybook"
|
28
|
+
s.summary = "It does things. Like import contacts from EVERYWHERE."
|
29
|
+
s.email = "kojul@kojul.com"
|
30
|
+
s.homepage = "http://github.com/kojul/gray"
|
31
|
+
s.description = "It does things. Like import contacts from EVERYWHERE."
|
32
|
+
s.authors = ["kojul"]
|
33
|
+
s.add_dependency('hpricot', '>= 0.7.0')
|
34
|
+
s.add_dependency('mechanize', '>= 0.7.0')
|
35
|
+
s.add_dependency('hoe', '>= 1.5.0')
|
36
|
+
s.add_dependency('fastercsv', '>= 1.2.0')
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
40
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.22
|
data/VERSION.yml
ADDED
data/VERSION_NAME
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gray Grenade
|
data/debug_graybook.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'graybook'
|
2
|
+
|
3
|
+
aol = Graybook.get :username => 'mondragonmichael@aol.com', :password => '123aol0'
|
4
|
+
puts "===== aol #{aol.inspect}"
|
5
|
+
|
6
|
+
unless aol.detect{|c| c[:name].downcase == 'Dave Myron'.downcase && c[:email] == 'dave.myron@contentfree.com'}
|
7
|
+
puts "AOL not working"
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/lib/graybook'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
##
|
2
|
+
# base class for exporters of contact information
|
3
|
+
|
4
|
+
module Graybook::Exporter
|
5
|
+
|
6
|
+
class Base
|
7
|
+
##
|
8
|
+
# Override this to convert +contacts+ (an array of hashes) to something more useful. Here, it
|
9
|
+
# just returns Graybook's internal format
|
10
|
+
def export( contacts )
|
11
|
+
contacts
|
12
|
+
end
|
13
|
+
|
14
|
+
Graybook.register :basic, self
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
##
|
2
|
+
# exports contacts in Vcard format
|
3
|
+
class Graybook::Exporter::Vcf < Graybook::Exporter::Base
|
4
|
+
|
5
|
+
##
|
6
|
+
# representation of a vcard
|
7
|
+
|
8
|
+
class Vcard
|
9
|
+
|
10
|
+
attr_accessor :first, :last, :email
|
11
|
+
|
12
|
+
##
|
13
|
+
# initialize dynamically sets the attributes passed in as accessible
|
14
|
+
# attribute on its object
|
15
|
+
|
16
|
+
def initialize( attributes = {} )
|
17
|
+
attributes.each{ |name,value| self.send("#{name}=", value) rescue next }
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# text representation of this vcard
|
22
|
+
def to_s
|
23
|
+
<<-EOVC
|
24
|
+
BEGIN:VCARD
|
25
|
+
N:#{last};#{first}
|
26
|
+
EMAIL:#{email}
|
27
|
+
END:VCARD
|
28
|
+
EOVC
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# exports contacts as Vcards
|
34
|
+
|
35
|
+
def export( contacts = [] )
|
36
|
+
return if contacts.empty?
|
37
|
+
|
38
|
+
contacts.uniq.compact.collect do |contact|
|
39
|
+
first_name, last_name = contact[:name].split(' ', 2)
|
40
|
+
Vcard.new( :first => first_name.to_s, :last => last_name.to_s, :email => contact[:email])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Graybook.register(:vcf, self)
|
45
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
##
|
4
|
+
# exports contacts in xml format
|
5
|
+
|
6
|
+
class Graybook::Exporter::Xml < Graybook::Exporter::Base
|
7
|
+
|
8
|
+
##
|
9
|
+
# contacts are an array of hashes that are contacts and returns xml
|
10
|
+
|
11
|
+
def export( contacts )
|
12
|
+
doc = REXML::Document.new
|
13
|
+
doc << REXML::XMLDecl.new
|
14
|
+
|
15
|
+
root = doc.add_element 'contacts'
|
16
|
+
contacts.each do |contact|
|
17
|
+
el = root.add_element 'contact'
|
18
|
+
name = el.add_element 'name'
|
19
|
+
name.text = contact[:name]
|
20
|
+
|
21
|
+
el.add_element('email').text = contact[:email]
|
22
|
+
end
|
23
|
+
|
24
|
+
doc.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
Graybook.register(:xml, self)
|
28
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'graybook/importer/page_scraper'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Imports contacts from AOL
|
5
|
+
|
6
|
+
class Graybook::Importer::Aol < Graybook::Importer::PageScraper
|
7
|
+
|
8
|
+
##
|
9
|
+
# Matches this importer to an user's name/address
|
10
|
+
|
11
|
+
def =~( options )
|
12
|
+
options && options[:username] =~ /@(aol|aim)\.com$/i ? true : false
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Login process:
|
17
|
+
# - Get mail.aol.com which redirects to a page containing a javascript redirect
|
18
|
+
# - Get the URL that the javascript is supposed to redirect you to
|
19
|
+
# - Fill out and submit the login form
|
20
|
+
# - Get the URL from *another* javascript redirect
|
21
|
+
|
22
|
+
def login
|
23
|
+
page = agent.get( 'http://webmail.aol.com/' )
|
24
|
+
|
25
|
+
form = page.forms.find{|form| form.name == 'AOLLoginForm'}
|
26
|
+
form.loginId = options[:username].split('@').first # Drop the domain
|
27
|
+
form.password = options[:password]
|
28
|
+
page = agent.submit(form, form.buttons.first)
|
29
|
+
|
30
|
+
case page.body
|
31
|
+
when /Invalid Screen Name or Password. Please try again./
|
32
|
+
raise( Graybook::BadCredentialsError, "That username and password was not accepted. Please check them and try again." )
|
33
|
+
when /Terms of Service/
|
34
|
+
raise( Graybook::LegacyAccount, "Your AOL account is not setup for WebMail. Please signup: http://webmail.aol.com")
|
35
|
+
end
|
36
|
+
|
37
|
+
# aol bumps to a wait page while logging in. if we can't scrape out the js then its a bad login
|
38
|
+
extractor = proc { |var_name| page.body.scan(/var\s*#{var_name}\s*=\s*\"(.*?)\"\s*;/).first.first }
|
39
|
+
|
40
|
+
base_uri = extractor.call( 'gSuccessPath' )
|
41
|
+
raise( Graybook::BadCredentialsError, "You do not appear to be signed in." ) unless base_uri
|
42
|
+
page = agent.get base_uri
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# must login to prepare
|
47
|
+
|
48
|
+
def prepare
|
49
|
+
login
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# The url to scrape contacts from has to be put together from the Auth cookie
|
54
|
+
# and a known uri that hosts their contact service. An array of hashes with
|
55
|
+
# :name and :email keys is returned.
|
56
|
+
|
57
|
+
def scrape_contacts
|
58
|
+
unless auth_cookie = agent.cookies.find{|c| c.name =~ /^Auth/}
|
59
|
+
raise( Graybook::BadCredentialsError, "Must be authenticated to access contacts." )
|
60
|
+
end
|
61
|
+
|
62
|
+
# jump through the hoops of formulating a request to get printable contacts
|
63
|
+
uri = agent.current_page.uri.dup
|
64
|
+
inputs = agent.current_page.search("//input")
|
65
|
+
user = inputs.detect{|i| i['type'] == 'hidden' && i['name'] == 'user'}
|
66
|
+
utoken = user['value']
|
67
|
+
|
68
|
+
path = uri.path.split('/')
|
69
|
+
path.pop
|
70
|
+
path << 'addresslist-print.aspx'
|
71
|
+
uri.path = path.join('/')
|
72
|
+
uri.query = "command=all&sort=FirstLastNick&sortDir=Ascending&nameFormat=FirstLastNick&user=#{utoken}"
|
73
|
+
page = agent.get uri.to_s
|
74
|
+
|
75
|
+
# Grab all the contacts
|
76
|
+
names = page.body.scan( /<span class="fullName">([^<]+)<\/span>/ ).flatten
|
77
|
+
emails = page.body.scan( /<span>Email 1:<\/span> <span>([^<]+)<\/span>/ ).flatten
|
78
|
+
(0...[names.size,emails.size].max).collect do |i|
|
79
|
+
{
|
80
|
+
:name => names[i],
|
81
|
+
:email => emails[i]
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Graybook.register :aol, self
|
87
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#
|
2
|
+
# Provides a base template for interface and behavior of contact importers
|
3
|
+
|
4
|
+
module Graybook::Importer
|
5
|
+
class Base
|
6
|
+
attr_accessor :options
|
7
|
+
|
8
|
+
##
|
9
|
+
# Should return true or false/nil depending on whether the +options+ given
|
10
|
+
# can be handled by this importer
|
11
|
+
|
12
|
+
def =~( options ); end # stub
|
13
|
+
|
14
|
+
##
|
15
|
+
# Does the work of extracting contacts. Returns an Array of Arrays
|
16
|
+
# containing the name and email as the first and second elements. Of
|
17
|
+
# course, you can override this behavior to meet the needs of a
|
18
|
+
# particular service.
|
19
|
+
|
20
|
+
def fetch_contacts!; end # stub
|
21
|
+
|
22
|
+
##
|
23
|
+
# Imports the contacts using the given +options+. Returns an array of
|
24
|
+
# hashes in the internal format (a hash with at least :name and :email
|
25
|
+
# values).
|
26
|
+
|
27
|
+
def import(options = {})
|
28
|
+
self.options = options
|
29
|
+
fetch_contacts!
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Name of the importer service.
|
34
|
+
|
35
|
+
def service_name
|
36
|
+
self.class.name.split("::").last
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'fastercsv'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Imports contacts from a CSV file
|
5
|
+
|
6
|
+
class Graybook::Importer::Csv < Graybook::Importer::Base
|
7
|
+
|
8
|
+
DEFAULT_COLUMNS = [:name,:email,:misc]
|
9
|
+
DEFAULT_PATTERN = /,/
|
10
|
+
|
11
|
+
##
|
12
|
+
# Matches this importer to a file that contains CSV values
|
13
|
+
|
14
|
+
def =~(options)
|
15
|
+
options && options[:file].respond_to?(:open) ? true : false
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# fetch_contacts! implementation for this importer
|
20
|
+
|
21
|
+
def fetch_contacts!
|
22
|
+
lines = IO.readlines(options[:file].path)
|
23
|
+
columns = to_columns(lines.first)
|
24
|
+
lines.shift if columns.first == :name
|
25
|
+
columns = DEFAULT_COLUMNS.dup unless columns.first == :name
|
26
|
+
|
27
|
+
contacts = Array.new
|
28
|
+
lines.each do |l|
|
29
|
+
vals = l.split(pattern)
|
30
|
+
next if vals.empty?
|
31
|
+
vals.map! {|x| x.gsub( /\A"/m, "" ).strip.gsub( /"\Z/m, "" ) }
|
32
|
+
contacts << to_hash(columns, vals)
|
33
|
+
end
|
34
|
+
|
35
|
+
contacts
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_hash(cols, vals) # :nodoc:
|
39
|
+
h = Hash.new
|
40
|
+
cols.each do |c|
|
41
|
+
h[c] = (c == cols.last) ? vals.join(',') : vals.shift
|
42
|
+
end
|
43
|
+
h
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_columns(line) # :nodoc:
|
47
|
+
columns = Array.new
|
48
|
+
if line.match(pattern)
|
49
|
+
tags = line.split(pattern)
|
50
|
+
else
|
51
|
+
tags = line.split(DEFAULT_PATTERN)
|
52
|
+
end
|
53
|
+
# deal with "Name,E-mail..." oddity up front
|
54
|
+
if tags.first =~ /^name$/i
|
55
|
+
tags.shift
|
56
|
+
columns << :name
|
57
|
+
if tags.first =~ /^e.?mail/i # E-mail or Email
|
58
|
+
tags.shift
|
59
|
+
columns << :email
|
60
|
+
end
|
61
|
+
end
|
62
|
+
tags.each do |v|
|
63
|
+
v = v.to_s.gsub(/\0/,"")
|
64
|
+
columns << v.strip.to_sym unless v.blank?
|
65
|
+
end
|
66
|
+
columns
|
67
|
+
end
|
68
|
+
|
69
|
+
def pattern
|
70
|
+
@pattern ||= (options[:pattern] || DEFAULT_PATTERN) rescue DEFAULT_PATTERN
|
71
|
+
end
|
72
|
+
|
73
|
+
Graybook.register(:csv, self)
|
74
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'graybook/importer/page_scraper'
|
2
|
+
|
3
|
+
class Graybook::Importer::Freenet < Graybook::Importer::PageScraper
|
4
|
+
LOGIN_URL = "https://office.freenet.de"
|
5
|
+
MESSAGES_URL = "/main_overview.html"
|
6
|
+
|
7
|
+
def =~( options )
|
8
|
+
options && options[:username] =~ /@freenet\.de$/i
|
9
|
+
end
|
10
|
+
|
11
|
+
def login
|
12
|
+
username, password = options[:username], options[:password]
|
13
|
+
|
14
|
+
begin
|
15
|
+
page = agent.get LOGIN_URL
|
16
|
+
|
17
|
+
form = page.forms.with.name("loginform").first
|
18
|
+
form.username = username.split("@").first
|
19
|
+
form.password = password
|
20
|
+
page = form.submit
|
21
|
+
|
22
|
+
if page.body.match(/Fehlgeschlagene Login-Versuche/)
|
23
|
+
page = page.forms.with.name("sicherform").submit
|
24
|
+
page = agent.get MESSAGES_URL
|
25
|
+
end
|
26
|
+
|
27
|
+
if page.body.match(/Login (erneut )?fehlgeschlagen/) || page.body.match(/Ich bin bereits Mitglied/)
|
28
|
+
raise Graybook::BadCredentialsError.new
|
29
|
+
end
|
30
|
+
rescue => e
|
31
|
+
raise e || Graybook::GraybookError.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def prepare
|
36
|
+
login
|
37
|
+
end
|
38
|
+
|
39
|
+
def scrape_contacts
|
40
|
+
contacts = ["INBOX", "INBOX.sent"].map do |folder|
|
41
|
+
page = agent.get "/messages/mail_mailbox.html?mail_folder=#{folder}"
|
42
|
+
find_contacts(page)
|
43
|
+
end
|
44
|
+
|
45
|
+
contacts.inject([]) do |memo, contact|
|
46
|
+
memo << contact unless memo.include? contact
|
47
|
+
memo
|
48
|
+
end.flatten
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
protected
|
53
|
+
def find_contacts(page)
|
54
|
+
page.search("tr[@id]/td[9]").map do |cell|
|
55
|
+
recp, fullname, email = *cell.attributes["title"].match(/"(.*)"<(.*)>/)
|
56
|
+
|
57
|
+
{ :name => fullname, :email => email }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
Graybook.register :freenet, self
|
62
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'graybook/importer/page_scraper'
|
2
|
+
|
3
|
+
##
|
4
|
+
# Imports contacts from GMail
|
5
|
+
|
6
|
+
class Graybook::Importer::Gmail < Graybook::Importer::PageScraper
|
7
|
+
|
8
|
+
RETRY_THRESHOLD = 5
|
9
|
+
##
|
10
|
+
# Matches this importer to an user's name/address
|
11
|
+
|
12
|
+
def =~(options = {})
|
13
|
+
options && options[:username] =~ /@(gmail|googlemail).com$/i ? true : false
|
14
|
+
end
|
15
|
+
|
16
|
+
def import(*args)
|
17
|
+
# GMail depends on Hpricot for some reason...
|
18
|
+
parser = WWW::Mechanize.html_parser
|
19
|
+
WWW::Mechanize.html_parser = Hpricot
|
20
|
+
return super do
|
21
|
+
WWW::Mechanize.html_parser = parser
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# login to gmail
|
27
|
+
|
28
|
+
def login
|
29
|
+
page = agent.get('http://mail.google.com/mail/')
|
30
|
+
form = page.forms.first
|
31
|
+
form.Email = options[:username]
|
32
|
+
form.Passwd = options[:password]
|
33
|
+
page = agent.submit(form,form.buttons.first)
|
34
|
+
|
35
|
+
raise( Graybook::BadCredentialsError, "That username and password was not accepted. Please check them and try again." ) if page.body =~ /Username and password do not match/
|
36
|
+
|
37
|
+
if page.search('//meta').first.attributes['content'] =~ /url='?(http.+?)'?$/i
|
38
|
+
page = agent.get $1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# prepare this importer
|
44
|
+
|
45
|
+
def prepare
|
46
|
+
login
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# scrape gmail contacts for this importer
|
51
|
+
|
52
|
+
def scrape_contacts
|
53
|
+
unless agent.cookies.find { |c|
|
54
|
+
c.name == 'GAUSR' && c.value.match(/mail(.*?):#{options[:username]}/)
|
55
|
+
}
|
56
|
+
raise( Graybook::BadCredentialsError, "Must be authenticated to access contacts." )
|
57
|
+
end
|
58
|
+
|
59
|
+
page = agent.get('http://mail.google.com/mail/h/?v=cl&pnl=a')
|
60
|
+
title = page.search("//title").inner_text
|
61
|
+
if title == 'Redirecting'
|
62
|
+
redirect_text = page.search('//meta').first.attributes['content'].inner_text
|
63
|
+
url = redirect_text.match(/url='(http.*)'/i)[1]
|
64
|
+
page = agent.get(url)
|
65
|
+
end
|
66
|
+
|
67
|
+
contact_rows = page.search("//input[@name='c']/../..")
|
68
|
+
contact_rows.collect do |row|
|
69
|
+
columns = row/"td"
|
70
|
+
email = columns[2].inner_html.gsub( /(\n| )/, '' ) # email
|
71
|
+
clean_email = email[/[a-zA-Z0-9._%+-]+@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4}/]
|
72
|
+
|
73
|
+
unless clean_email.empty?
|
74
|
+
columns = row/"td"
|
75
|
+
{
|
76
|
+
:name => ( columns[1] / "b" / "a" ).inner_html.strip, # name
|
77
|
+
:email => clean_email
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end.compact
|
81
|
+
end
|
82
|
+
|
83
|
+
Graybook.register(:gmail, self)
|
84
|
+
end
|