blackbook 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/History.txt +18 -0
  2. data/Manifest.txt +55 -0
  3. data/README.txt +71 -0
  4. data/Rakefile +38 -0
  5. data/init.rb +1 -0
  6. data/lib/blackbook.rb +80 -0
  7. data/lib/blackbook/exporter/base.rb +16 -0
  8. data/lib/blackbook/exporter/vcf.rb +45 -0
  9. data/lib/blackbook/exporter/xml.rb +28 -0
  10. data/lib/blackbook/importer/aol.rb +74 -0
  11. data/lib/blackbook/importer/base.rb +39 -0
  12. data/lib/blackbook/importer/csv.rb +61 -0
  13. data/lib/blackbook/importer/gmail.rb +59 -0
  14. data/lib/blackbook/importer/hotmail.rb +125 -0
  15. data/lib/blackbook/importer/page_scraper.rb +86 -0
  16. data/lib/blackbook/importer/yahoo.rb +61 -0
  17. data/test/fixtures/aol_bad_login_response_stage_3.html +565 -0
  18. data/test/fixtures/aol_contacts.html +90 -0
  19. data/test/fixtures/aol_login_response_stage_1.html +158 -0
  20. data/test/fixtures/aol_login_response_stage_2.html +559 -0
  21. data/test/fixtures/aol_login_response_stage_3.html +61 -0
  22. data/test/fixtures/aol_login_response_stage_4.html +48 -0
  23. data/test/fixtures/aol_login_response_stage_5.html +404 -0
  24. data/test/fixtures/gmail.csv +3 -0
  25. data/test/fixtures/gmail_bad_login_response_stage_2.html +560 -0
  26. data/test/fixtures/gmail_contacts.html +228 -0
  27. data/test/fixtures/gmail_login_response_stage_1.html +556 -0
  28. data/test/fixtures/gmail_login_response_stage_2.html +1 -0
  29. data/test/fixtures/gmail_login_response_stage_3.html +249 -0
  30. data/test/fixtures/hotmail_bad_login_response_stage_2.html +31 -0
  31. data/test/fixtures/hotmail_contacts.html +132 -0
  32. data/test/fixtures/hotmail_login_response_stage_1.html +31 -0
  33. data/test/fixtures/hotmail_login_response_stage_2.html +1 -0
  34. data/test/fixtures/hotmail_login_response_stage_3.html +380 -0
  35. data/test/fixtures/yahoo_bad_login_response_stage_2.html +443 -0
  36. data/test/fixtures/yahoo_contacts.csv +3 -0
  37. data/test/fixtures/yahoo_contacts_not_logged_in.html +432 -0
  38. data/test/fixtures/yahoo_contacts_stage_1.html +399 -0
  39. data/test/fixtures/yahoo_login_response_stage_1.html +433 -0
  40. data/test/fixtures/yahoo_login_response_stage_2.html +16 -0
  41. data/test/scripts/live_test.rb +25 -0
  42. data/test/test_blackbook.rb +60 -0
  43. data/test/test_blackbook_exporter_base.rb +16 -0
  44. data/test/test_blackbook_exporter_vcf.rb +52 -0
  45. data/test/test_blackbook_exporter_xml.rb +16 -0
  46. data/test/test_blackbook_importer_aol.rb +107 -0
  47. data/test/test_blackbook_importer_base.rb +24 -0
  48. data/test/test_blackbook_importer_csv.rb +60 -0
  49. data/test/test_blackbook_importer_gmail.rb +96 -0
  50. data/test/test_blackbook_importer_hotmail.rb +143 -0
  51. data/test/test_blackbook_importer_page_scraper.rb +51 -0
  52. data/test/test_blackbook_importer_yahoo.rb +97 -0
  53. data/test/test_helper.rb +47 -0
  54. data/vendor/plugins/blackbook/lib/autotest/blackbook.rb +27 -0
  55. data/vendor/plugins/blackbook/lib/autotest/discover.rb +3 -0
  56. metadata +147 -0
@@ -0,0 +1,18 @@
1
+ == 1.0.0 / 2008-01-30
2
+
3
+ * 2 major enhancements
4
+ * Birthday!
5
+ * Mechanize patch for AOL sign-on! Thanks Mortee!
6
+
7
+ * Importers
8
+ * AOL
9
+ * GMail
10
+ * Hotmail
11
+ * Yahoo!
12
+ * CSV
13
+
14
+ * Exporters
15
+ * Hash
16
+ * XML
17
+ * VCard
18
+
@@ -0,0 +1,55 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ init.rb
6
+ lib/blackbook.rb
7
+ lib/blackbook/exporter/base.rb
8
+ lib/blackbook/exporter/vcf.rb
9
+ lib/blackbook/exporter/xml.rb
10
+ lib/blackbook/importer/aol.rb
11
+ lib/blackbook/importer/base.rb
12
+ lib/blackbook/importer/csv.rb
13
+ lib/blackbook/importer/gmail.rb
14
+ lib/blackbook/importer/hotmail.rb
15
+ lib/blackbook/importer/page_scraper.rb
16
+ lib/blackbook/importer/yahoo.rb
17
+ test/fixtures/aol_bad_login_response_stage_3.html
18
+ test/fixtures/aol_contacts.html
19
+ test/fixtures/aol_login_response_stage_1.html
20
+ test/fixtures/aol_login_response_stage_2.html
21
+ test/fixtures/aol_login_response_stage_3.html
22
+ test/fixtures/aol_login_response_stage_4.html
23
+ test/fixtures/aol_login_response_stage_5.html
24
+ test/fixtures/gmail.csv
25
+ test/fixtures/gmail_bad_login_response_stage_2.html
26
+ test/fixtures/gmail_contacts.html
27
+ test/fixtures/gmail_login_response_stage_1.html
28
+ test/fixtures/gmail_login_response_stage_2.html
29
+ test/fixtures/gmail_login_response_stage_3.html
30
+ test/fixtures/hotmail_bad_login_response_stage_2.html
31
+ test/fixtures/hotmail_contacts.html
32
+ test/fixtures/hotmail_login_response_stage_1.html
33
+ test/fixtures/hotmail_login_response_stage_2.html
34
+ test/fixtures/hotmail_login_response_stage_3.html
35
+ test/fixtures/yahoo_bad_login_response_stage_2.html
36
+ test/fixtures/yahoo_contacts.csv
37
+ test/fixtures/yahoo_contacts_not_logged_in.html
38
+ test/fixtures/yahoo_contacts_stage_1.html
39
+ test/fixtures/yahoo_login_response_stage_1.html
40
+ test/fixtures/yahoo_login_response_stage_2.html
41
+ test/scripts/live_test.rb
42
+ test/test_blackbook.rb
43
+ test/test_blackbook_exporter_base.rb
44
+ test/test_blackbook_exporter_vcf.rb
45
+ test/test_blackbook_exporter_xml.rb
46
+ test/test_blackbook_importer_aol.rb
47
+ test/test_blackbook_importer_base.rb
48
+ test/test_blackbook_importer_csv.rb
49
+ test/test_blackbook_importer_gmail.rb
50
+ test/test_blackbook_importer_hotmail.rb
51
+ test/test_blackbook_importer_page_scraper.rb
52
+ test/test_blackbook_importer_yahoo.rb
53
+ test/test_helper.rb
54
+ vendor/plugins/blackbook/lib/autotest/blackbook.rb
55
+ vendor/plugins/blackbook/lib/autotest/discover.rb
@@ -0,0 +1,71 @@
1
+ Blackbook
2
+ http://rubyforge.org/projects/contentfree/
3
+
4
+ == DESCRIPTION:
5
+
6
+ Blackbook automates the nitty-gritty of importing contacts from various services and files and exporting them as VCard, XML, or simple Hash. Utilize those contacts from services like AOL, GMail, Yahoo Mail, Hotmail or CSV to help your social networking site become GIGANTIC overnight! You'll be able to get big and sell for millions before anyone figures out it's just like every other social network.
7
+
8
+ == FEATURES/PROBLEMS:
9
+
10
+ The current list of supported services and file types:
11
+
12
+ Import:
13
+ * AOL
14
+ * CSV files
15
+ * Gmail
16
+ * Hotmail
17
+ * Yahoo! Mail
18
+
19
+ Export:
20
+ * Simple hash (default)
21
+ * Vcard
22
+ * XML
23
+
24
+ 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.
25
+
26
+ == SYNOPSIS:
27
+
28
+ # An example of fetching Gmail contacts - by default, returns an array of hashes with :name and :email
29
+ contacts = Blackbook.get :username => 'me@gmail.com', :password => 'whatever'
30
+
31
+ # or returning XML
32
+ contacts = Blackbook.get :username => 'me@gmail.com', :password => 'whatever', :as => :xml
33
+
34
+ # or importing from a CSV file
35
+ contacts = Blackbook.get :csv, :file => #<File:/path/to/file.csv>
36
+
37
+ == REQUIREMENTS:
38
+
39
+ * Mechanize and its dependencies, for interacting with online providers
40
+ * Fastercsv for reading CSV, Mechanize >= 0.7.0 for page scraping
41
+
42
+ == INSTALL:
43
+
44
+ * sudo gem install blackbook
45
+
46
+ == THANKS:
47
+
48
+ Big thanks to Marton Fabo for figuring out why Mechanize couldn't log in to AOL.
49
+
50
+ == LICENSE:
51
+
52
+ Copyright (c) 2007, Contentfree
53
+
54
+ Permission is hereby granted, free of charge, to any person obtaining
55
+ a copy of this software and associated documentation files (the
56
+ 'Software'), to deal in the Software without restriction, including
57
+ without limitation the rights to use, copy, modify, merge, publish,
58
+ distribute, sublicense, and/or sell copies of the Software, and to
59
+ permit persons to whom the Software is furnished to do so, subject to
60
+ the following conditions:
61
+
62
+ The above copyright notice and this permission notice shall be
63
+ included in all copies or substantial portions of the Software.
64
+
65
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
66
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
67
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
68
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
69
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
70
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
71
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ $LOAD_PATH.unshift 'lib'
6
+ require 'blackbook'
7
+
8
+ begin
9
+ require 'rcov/rcovtask'
10
+ rescue LoadError
11
+ end
12
+
13
+ Hoe.new('blackbook', Blackbook::VERSION) do |p|
14
+ p.rubyforge_name = 'contentfree'
15
+ p.author = 'Contentfree'
16
+ p.email = 'dave.myron@contentfree.com'
17
+ p.summary = 'Blackbook handles the nitty-gritty of importing contacts from various service providers and contact lists and exporting them in a useful format.'
18
+ p.description = p.paragraphs_of('README.txt', 1).join("\n\n")
19
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
20
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
21
+ p.extra_deps << [ "mechanize", ">= 0.7.0" ]
22
+ p.extra_deps << [ "fastercsv", ">= 1.2.0" ]
23
+ p.clean_globs << 'coverage'
24
+ p.clean_globs << 'pkg'
25
+ p.clean_globs << 'doc'
26
+ p.clean_globs << 'ri'
27
+ end
28
+
29
+ task :release_and_publish => [:release, :publish_docs]
30
+
31
+ begin
32
+ Rcov::RcovTask.new do |t|
33
+ t.test_files = FileList['test/test*.rb']
34
+ t.verbose = true
35
+ #t.rcov_opts << "--exclude rcov.rb,hpricot.rb,hpricot/.*\.rb"
36
+ end
37
+ rescue NameError
38
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'blackbook'
@@ -0,0 +1,80 @@
1
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__)))
2
+ require 'singleton'
3
+ require 'rubygems'
4
+
5
+ class Blackbook
6
+ include ::Singleton
7
+ VERSION = '1.0.0'
8
+
9
+ class BlackbookError < ::StandardError; end
10
+ class BadCredentialsError < BlackbookError; end
11
+
12
+ attr_accessor :importers
13
+ attr_accessor :exporters
14
+
15
+ def self.get( *args )
16
+ instance.get( *args )
17
+ end
18
+
19
+ def self.register(name, adapter_class)
20
+ case adapter = adapter_class.new
21
+ when Importer::Base
22
+ instance.importers[name.to_sym] = adapter
23
+ when Exporter::Base
24
+ instance.exporters[name.to_sym] = adapter
25
+ else
26
+ raise ArgumentError, "Unknown adapter"
27
+ end
28
+ end
29
+
30
+ # Sends the vcards from the import to whatever is handling the export
31
+ def export( importer, exporter, options )
32
+ exporter.export importer.import( options )
33
+ end
34
+
35
+ # Searches registered importers for one that will handle the given options
36
+ def find_importer( options )
37
+ importers.each{ |key, importer| return importer if importer =~ options }
38
+ nil
39
+ end
40
+
41
+ # Fetches contacts from various services or filetypes. The default is to return an array
42
+ # of hashes - Blackbook's internal format
43
+ #
44
+ # Handles several different calls:
45
+ # get( :username => 'something@gmail.com', :password => 'whatever' )
46
+ # get( :as => :xml, :username => 'something@gmail.com', :password => 'whatever' )
47
+ # get( :csv, :file => #<File:/path/to/file.csv> )
48
+ def get( *args )
49
+ options = args.last.is_a?(Hash) ? args.pop : {}
50
+ to_format = exporters[ options[:as] || :basic ]
51
+ source = (importers[args.first.to_sym] rescue nil) || find_importer(options)
52
+
53
+ raise ArgumentError, "Unknown exporter" unless to_format
54
+ raise ArgumentError, "Unknown source" unless source
55
+
56
+ export source, to_format, options
57
+ end
58
+
59
+ def initialize
60
+ self.importers = {}
61
+ self.exporters = {}
62
+ end
63
+ end
64
+
65
+ # Require all the importers/exporters
66
+ require 'blackbook/importer/base'
67
+ require 'blackbook/exporter/base'
68
+ Dir.glob(File.join(File.dirname(__FILE__), 'blackbook/importer/*.rb')).each {|f| require f }
69
+ Dir.glob(File.join(File.dirname(__FILE__), 'blackbook/exporter/*.rb')).each {|f| require f }
70
+
71
+ class String
72
+ alias :blank? :empty?
73
+ end
74
+ class NilClass
75
+ alias :blank? :nil?
76
+ end
77
+ class Array
78
+ alias :blank? :empty?
79
+ end
80
+
@@ -0,0 +1,16 @@
1
+ ##
2
+ # base class for exporters of contact information
3
+
4
+ module Blackbook::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 Blackbook's internal format
10
+ def export( contacts )
11
+ contacts
12
+ end
13
+
14
+ Blackbook.register :basic, self
15
+ end
16
+ end
@@ -0,0 +1,45 @@
1
+ ##
2
+ # exports contacts in Vcard format
3
+ class Blackbook::Exporter::Vcf < Blackbook::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.blank?
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
+ Blackbook.register(:vcf, self)
45
+ end
@@ -0,0 +1,28 @@
1
+ require 'rexml/document'
2
+
3
+ ##
4
+ # exports contacts in xml format
5
+
6
+ class Blackbook::Exporter::Xml < Blackbook::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
+ Blackbook.register(:xml, self)
28
+ end
@@ -0,0 +1,74 @@
1
+ require 'blackbook/importer/page_scraper'
2
+
3
+ ##
4
+ # Imports contacts from AOL
5
+
6
+ class Blackbook::Importer::Aol < Blackbook::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.name('AOLLoginForm').first
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
+ raise( Blackbook::BadCredentialsError, "That username and password was not accepted. Please check them and try again." ) if page.body =~ /Invalid Screen Name or Password. Please try again./
31
+
32
+ # aol bumps to a wait page while logging in. if we can't scrape out the js then its a bad login
33
+ wait_url = page.body.scan(/onLoad="checkError[^\)]+/).first.scan(/'([^']+)'/).last.first
34
+ page = agent.get wait_url
35
+ # aol generates the URL below from some JS magic in the wait_url page
36
+ agent.get 'http://webmail.aol.com/31361/aol/en-us/Suite.aspx'
37
+ end
38
+
39
+ ##
40
+ # must login to prepare
41
+
42
+ def prepare
43
+ login
44
+ end
45
+
46
+ ##
47
+ # The url to scrape contacts from has to be put together from the Auth cookie
48
+ # and a known uri that hosts their contact service. An array of hashes with
49
+ # :name and :email keys is returned.
50
+
51
+ def scrape_contacts
52
+ unless auth_cookie = agent.cookies.find{|c| c.name =~ /^Auth/}
53
+ raise( Blackbook::BadCredentialsError, "Must be authenticated to access contacts." )
54
+ end
55
+
56
+ # Get user id from cookies
57
+ user_id = auth_cookie.value.scan(/&uid:([^&]+)&/).first.first
58
+
59
+ # Get contacts print page
60
+ page = agent.get "http://webmail.aol.com/aim/en-us/Lite/addresslist-print.aspx?command=all&sort=FirstLastNick&sortDir=Ascending&nameFormat=FirstLastNick&user=#{user_id}"
61
+
62
+ # Grab all the contacts
63
+ names = page.body.scan( /<span class="fullName">([^<]+)<\/span>/ ).flatten
64
+ emails = page.body.scan( /<span>Email 1:<\/span> <span>([^<]+)<\/span>/ ).flatten
65
+ (0...[names.size,emails.size].max).collect do |i|
66
+ {
67
+ :name => names[i],
68
+ :email => emails[i]
69
+ }
70
+ end
71
+ end
72
+
73
+ Blackbook.register :aol, self
74
+ end
@@ -0,0 +1,39 @@
1
+ #
2
+ # Provides a base template for interface and behavior of contact importers
3
+
4
+ module Blackbook::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