readability_importer 0.1.0

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/README.md ADDED
@@ -0,0 +1,38 @@
1
+ Readability Importer
2
+ ====================
3
+
4
+ A simple command to import many URLs to [Readability](http://www.readability.com/).
5
+
6
+ Install
7
+ -------
8
+
9
+ This command is provided in RubyGems format.
10
+ Run next command to insall all dependencies and command.
11
+
12
+ gem install redability_importer
13
+
14
+ Usage
15
+ -----
16
+
17
+ To import [Instapaper](http://www.instapaper.com/) URL(s),
18
+ export Instapaper CSV, then run next command.
19
+
20
+ redability_importer import -e <Your Readability Email Address> --instapaper-csv <Path to CSV>
21
+
22
+ You can grab your Readability email address in [My Account](https://www.readability.com/account/email) page.
23
+
24
+ If you have a file includes a URL per line,
25
+ also you can use ``--urls`` option.
26
+
27
+ cat path_to_file | readability_importer import -e <Your Readability Email Address> --urls -
28
+
29
+ Or, just simply assign URLs.
30
+
31
+ readability_importer import -e <Your Readability Email Address> --urls http://a.com http://b.com
32
+
33
+ Note
34
+ ----
35
+
36
+ This command is, of course, unofficial tool.
37
+ Please do *NOT* contact with Readability about this tool.
38
+ Also, use at your own risk.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "readability_importer"
5
+
6
+ ReadabilityImporter::Command.start
@@ -0,0 +1,65 @@
1
+ require "thor"
2
+
3
+ module ReadabilityImporter
4
+ class Command < Thor
5
+ method_option(:email_address, {
6
+ :type => :string,
7
+ :aliases => "-e",
8
+ :required => true,
9
+ :desc => "Readability email address, like 'name-abc@inbox.readability.com'."
10
+ })
11
+ method_option(:from, {
12
+ :type => :string,
13
+ :aliases => "-f",
14
+ :desc => "Your email address."
15
+ })
16
+ method_option(:jobs, {
17
+ :type => :numeric,
18
+ :aliases => "-j",
19
+ :desc => "Number of jobs in parallel."
20
+ })
21
+ method_option(:verbose, {
22
+ :type => :boolean,
23
+ :aliases => "-v",
24
+ :desc => "Verbose mode"
25
+ })
26
+ Loader.loaders.each do |(name, klass)|
27
+ method_option(name, {
28
+ :type => klass.type,
29
+ :desc => klass.desc
30
+ })
31
+ end
32
+ desc "import", "Import URL to Redability."
33
+ def import
34
+ p options[:jobs]
35
+ importer = Importer.new(options[:email_address], {
36
+ :verbose => options[:verbose],
37
+ :from => options[:from],
38
+ :max_concurrency => options[:job],
39
+
40
+ :on_importing => proc do |urls|
41
+ puts "Importing #{urls.size} url(s)..."
42
+ end,
43
+ :on_imported => proc do |urls|
44
+ puts urls.map{|u| "Imported #{u}"}
45
+ end,
46
+ :on_failed => proc do |urls|
47
+ puts urls.map{|u| "Failed #{u}"}
48
+ end
49
+ })
50
+ Loader.loaders.each do |(name, klass)|
51
+ if options[name]
52
+ loader = klass.new(options[name])
53
+ urls = loader.load
54
+ puts "Start importing #{urls.size} url(s)."
55
+ importer.import(loader.load)
56
+ end
57
+ end
58
+ end
59
+
60
+ desc "version", "Print the version"
61
+ def version
62
+ puts VERSION
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,16 @@
1
+ require 'eventmachine'
2
+
3
+ # Patch EventMachine::Protocol::SmtpClient to use HELO instead.
4
+ # Readability SMTP server doesn't understand EHLO.
5
+ # See lib/em/protocols/smtpclient.rb
6
+ module EventMachine
7
+ module Protocols
8
+ class SmtpClient
9
+ def receive_signon
10
+ return invoke_error unless @range == 2
11
+ send_data "HELO #{@args[:domain]}\r\n"
12
+ @responder = :receive_ehlo_response
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,67 @@
1
+ require 'eventmachine'
2
+ require 'resolv'
3
+
4
+ module ReadabilityImporter
5
+ class Importer
6
+ MAX_URLS_IN_MAIL = 20.freeze
7
+
8
+ def initialize(email_address, options = {})
9
+ @email_address = email_address
10
+
11
+ @from = options[:from] || "foo@bar"
12
+ @max_concurrency = options[:max_concurrency] || 4
13
+ @verbose = !!options[:verbose]
14
+
15
+ @on_importing = options[:on_importing]
16
+ @on_imported = options[:on_imported]
17
+ @on_failed = options[:on_failed]
18
+ end
19
+
20
+ def import(urls)
21
+ urls_set = []
22
+ urls.each_slice(MAX_URLS_IN_MAIL){|urls| urls_set << urls}
23
+
24
+ EventMachine.run do
25
+ iterator = EventMachine::Iterator.new(urls_set, @max_concurrency)
26
+ iterator.each(proc do |urls, iterator|
27
+ @on_importing.call(urls) if @on_importing
28
+ EventMachine::Protocols::SmtpClient.send({
29
+ :verbose => @verbose,
30
+ :domain => domain,
31
+ :host => host,
32
+ :from => @from,
33
+ :to => @email_address,
34
+ :header => {"From" => @from, "To" => @email_address},
35
+ :body => urls.join("\r\n")
36
+ }).tap do |job|
37
+ job.callback do
38
+ @on_imported.call(urls) if @on_imported
39
+ iterator.next
40
+ end
41
+ job.errback do
42
+ @on_failed.call(urls) if @on_failed
43
+ iterator.next
44
+ end
45
+ end
46
+ end, proc do
47
+ EventMachine.stop
48
+ end)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def domain
55
+ @domain ||= $1 if /@([^@]+)$/ === @email_address
56
+ end
57
+
58
+ def host
59
+ @host ||= begin
60
+ resource = Resolv::DNS.new.getresource(domain, Resolv::DNS::Resource::IN::MX)
61
+ if Resolv::DNS::Resource::IN::MX === resource
62
+ resource.exchange.to_s
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,69 @@
1
+ require 'csv'
2
+
3
+ module ReadabilityImporter
4
+ module Loader
5
+ def self.loaders
6
+ @loaders ||= constants.inject({}) do |loaders, klass_name|
7
+ if /Loader$/ === klass_name
8
+ klass = const_get(klass_name)
9
+ name = klass_name.to_s.tap do |s|
10
+ s.gsub!(/Loader$/, '')
11
+ s.gsub!(/([A-Z+])([A-Z][a-z])/, '\1_\2')
12
+ s.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
13
+ s.tr!("-", "_")
14
+ s.downcase!
15
+ end.to_sym
16
+ loaders[name] = klass
17
+ end
18
+ loaders
19
+ end
20
+ end
21
+
22
+ class Base
23
+ def self.desc
24
+ end
25
+
26
+ def self.type
27
+ :string
28
+ end
29
+ end
30
+
31
+ class UrlLoader < Base
32
+ def self.desc
33
+ "List of URLs."
34
+ end
35
+
36
+ def self.type
37
+ :array
38
+ end
39
+
40
+ def initialize(urls)
41
+ @urls = urls
42
+ end
43
+
44
+ def load
45
+ if @urls.empty?
46
+ STDIN.read.split(/\s+/)
47
+ else
48
+ @urls
49
+ end
50
+ end
51
+ end
52
+
53
+ class InstapaperCsvLoader < Base
54
+ def self.desc
55
+ "Path to CSV file"
56
+ end
57
+
58
+ def initialize(path)
59
+ @path = path
60
+ end
61
+
62
+ def load
63
+ CSV.read(@path).map do |line|
64
+ line[0]
65
+ end.reverse
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module ReadabilityImporter
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,8 @@
1
+ require 'readability_importer/version'
2
+ require 'readability_importer/event_machine_patch'
3
+
4
+ module ReadabilityImporter
5
+ autoload :Importer, "readability_importer/importer"
6
+ autoload :Loader, "readability_importer/loader"
7
+ autoload :Command, "readability_importer/command"
8
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: readability_importer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Yoshimasa Niwa
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: &70180650792080 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0.beta.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70180650792080
25
+ - !ruby/object:Gem::Dependency
26
+ name: thor
27
+ requirement: &70180650791560 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70180650791560
36
+ - !ruby/object:Gem::Dependency
37
+ name: bundler
38
+ requirement: &70180650790860 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70180650790860
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &70180650790120 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70180650790120
58
+ description: Import many URLs to Readability.
59
+ email:
60
+ - niw@niw.at
61
+ executables:
62
+ - readability_importer
63
+ extensions: []
64
+ extra_rdoc_files:
65
+ - README.md
66
+ files:
67
+ - bin/readability_importer
68
+ - lib/readability_importer.rb
69
+ - lib/readability_importer/command.rb
70
+ - lib/readability_importer/event_machine_patch.rb
71
+ - lib/readability_importer/importer.rb
72
+ - lib/readability_importer/loader.rb
73
+ - lib/readability_importer/version.rb
74
+ - README.md
75
+ homepage: https://github.com/niw/readability_importer
76
+ licenses: []
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 1.8.11
96
+ signing_key:
97
+ specification_version: 3
98
+ summary: Import many URLs to Readability.
99
+ test_files: []