readability_importer 0.1.0

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