graybook 1.0.22
Sign up to get free protection for your applications and to get access to all the features.
- 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
|