saber 1.1.1 → 1.2.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.
Files changed (68) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +8 -1
  3. data/Gemfile +6 -1
  4. data/Gemfile.lock +58 -14
  5. data/README.md +40 -125
  6. data/bin/saber +4 -2
  7. data/bin/saber.bb +3 -0
  8. data/bin/saber.stp +3 -0
  9. data/lib/saber.rb +5 -0
  10. data/lib/saber/autofetcher/server.rb +1 -1
  11. data/lib/saber/book.rb +36 -0
  12. data/lib/saber/cli.rb +45 -19
  13. data/lib/saber/core_ext.rb +68 -0
  14. data/lib/saber/fetcher.rb +2 -2
  15. data/lib/saber/rc.rb +6 -0
  16. data/lib/saber/task.rb +3 -0
  17. data/lib/saber/task/base.rb +6 -1
  18. data/lib/saber/task/chd.rb +10 -2
  19. data/lib/saber/task/clean.rb +2 -2
  20. data/lib/saber/task/find_uploads.rb +53 -0
  21. data/lib/saber/task/generate.rb +37 -20
  22. data/lib/saber/task/make.rb +20 -8
  23. data/lib/saber/task/send.rb +1 -1
  24. data/lib/saber/task/upload.rb +29 -19
  25. data/lib/saber/tracker.rb +8 -4
  26. data/lib/saber/tracker/base.rb +36 -15
  27. data/lib/saber/tracker/bb.rb +4 -10
  28. data/lib/saber/tracker/bib.rb +27 -11
  29. data/lib/saber/tracker/chd.rb +4 -4
  30. data/lib/saber/tracker/gazelle.rb +7 -0
  31. data/lib/saber/tracker/ptp.rb +2 -2
  32. data/lib/saber/tracker/stp.rb +53 -0
  33. data/lib/saber/tracker/what.rb +2 -2
  34. data/lib/saber/tracker2.rb +31 -0
  35. data/lib/saber/tracker2/base.rb +126 -0
  36. data/lib/saber/tracker2/bb.rb +211 -0
  37. data/lib/saber/tracker2/bib.rb +162 -0
  38. data/lib/saber/tracker2/gazelle.rb +52 -0
  39. data/lib/saber/tracker2/stp.rb +136 -0
  40. data/lib/saber/tracker2/what.rb +51 -0
  41. data/lib/saber/version.rb +1 -1
  42. data/lib/saber/watir_ext.rb +95 -0
  43. data/saber.gemspec +6 -1
  44. data/spec/saber/autofetcher/server_spec.rb +1 -1
  45. data/spec/saber/task_spec.rb +1 -1
  46. data/systemd/saber-chd@.service +12 -0
  47. data/systemd/saber-client@.service +12 -0
  48. data/systemd/saber-server@.service +12 -0
  49. data/templates/_saberrc +29 -8
  50. data/templates/article.yml +18 -0
  51. data/templates/bib/application.yml +8 -8
  52. data/templates/bib/audiobook.yml +12 -14
  53. data/templates/comic.yml +25 -0
  54. data/templates/ebook.yml +35 -0
  55. data/templates/journal.yml +19 -0
  56. data/templates/magazine.yml +41 -0
  57. data/templates/manual.yml +6 -0
  58. data/templates/newspaper.yml +6 -0
  59. metadata +109 -13
  60. data/templates/bb/comic.yml +0 -7
  61. data/templates/bb/ebook.yml +0 -8
  62. data/templates/bb/magazine.yml +0 -6
  63. data/templates/bib/article.yml +0 -18
  64. data/templates/bib/comic.yml +0 -22
  65. data/templates/bib/ebook.yml +0 -20
  66. data/templates/bib/journal.yml +0 -18
  67. data/templates/bib/magazine.yml +0 -18
  68. data/templates/what/ebook.yml +0 -6
@@ -8,8 +8,6 @@ module Saber
8
8
  # -----
9
9
  #
10
10
  # Task["make"].invoke(:make, ["bib", "a.epub", "b.epub"])
11
- # > create a.epub.torrent and send it to local and remote watch directory.
12
- # > create b.epub.torrent ..
13
11
  #
14
12
  class Make < Base
15
13
  desc "make", "make"
@@ -29,13 +27,27 @@ module Saber
29
27
  end
30
28
  end
31
29
 
32
- system "mktorrent -p -a #{Rc[tracker_name].announce_url} #{file.shellescape}", show_cmd: true
33
- # cp tororent file to watch directory.
34
- Pa.cp_f torrent_file, Rc.p.watch, show_cmd: true if Rc.p._has_key?(:watch)
35
- if Rc.p._has_key?(:remote_watch)
36
- require "saber/task/send"
37
- Task["send"].invoke(:send, [torrent_file, Rc.p.remote_watch])
30
+ if not Pa.exists?(file)
31
+ Saber.ui.error "SKIP: can't find file to make -- #{file}"
32
+ next
38
33
  end
34
+
35
+ system "mktorrent -p -a #{Rc[tracker_name].announce_url} #{file.shellescape} #{options['option']}", show_cmd: true
36
+
37
+ # cp tororent file
38
+ if Rc._has_key?("make.watch")
39
+ Pa.cp_f torrent_file, Rc.make.watch, show_cmd: "$"
40
+ end
41
+
42
+ if Rc._has_key?("make.remote_watch")
43
+ Task["send"].invoke(:send1, [torrent_file, Rc.make.remote_watch])
44
+ end
45
+
46
+ # move torrent file
47
+ if Rc._has_key?("make.dir")
48
+ Pa.mv_f torrent_file, Rc.make.dir, show_cmd: "$"
49
+ end
50
+
39
51
  Saber.ui.say ""
40
52
  }
41
53
  end
@@ -14,7 +14,7 @@ module Saber
14
14
  end
15
15
 
16
16
  *files, dest = args
17
- system "rsync -Phr #{files.shelljoin} #{Rc.server.user}@#{Rc.server.host}:#{dest}", show_cmd: true
17
+ system "rsync -ahP #{files.shelljoin} #{Rc.server.user}@#{Rc.server.host}:#{dest}", show_cmd: "$"
18
18
  end
19
19
  end
20
20
  end
@@ -2,35 +2,45 @@ module Saber
2
2
  module Task
3
3
  # Usage
4
4
  #
5
- # Task["upload"].invoke(:upload, ["site", "Hello.epub"]) # make hello.epub.torrent
6
- # Task["upload"].invoke(:upload, ["site", "Hello.epub.torrent"])
5
+ # Task["upload"].invoke(:upload, ["site", "Hello.epub"])
7
6
  #
8
7
  class Upload < Base
9
8
  desc "upload", "upload"
10
9
  # @param [String] tracker_name
11
- # @param [String] file/torrent_file "foo" or "foo.torrent"
12
- def upload(tracker_name, *torrent_files)
13
- require "saber/tracker/#{tracker_name}"
10
+ # @param [String] file
11
+ def upload(tracker_name, format, *files)
12
+ filemap = {} # {file => torrent_file}
14
13
 
15
- torrent_files.map!{|v| Pa.add_ext2(v, ".torrent")}
14
+ files.each{|file|
15
+ torrent_file = [
16
+ "#{file}.#{format}.torrent",
17
+ "#{tracker_name}/#{file}.#{format}.torrent",
18
+ "#{file}.torrent",
19
+ "#{tracker_name}/#{file}.torrent"
20
+ ].find {|v| Pa.exists?(v)}
16
21
 
17
- ensure_torrent_file(tracker_name, *torrent_files)
18
-
19
- tracker = Tracker[tracker_name].new
20
- tracker.login
21
- tracker.upload(*torrent_files)
22
- end
23
-
24
- private
22
+ torrent_file ||= Pa.directory?(file) ? "#{file}.torrent" : "#{file}.#{format}.torrent"
23
+ filemap[file] = torrent_file
24
+ }
25
25
 
26
- def ensure_torrent_file(tracker_name, *torrent_files)
27
- require "saber/task/make"
26
+ # make torrent if torrent_file not exists.
27
+ filemap.each { |file, torrent_file|
28
+ if not Pa.exists?(torrent_file)
28
29
 
29
- torrent_files.each { |torrent_file|
30
- next if Pa.exists?(torrent_file)
30
+ if not Pa.exists?(file)
31
+ Saber.ui.error "SKIP: Can't find torrent_file nor file -- #{file}"
32
+ filemap.delete(file)
33
+ next
34
+ end
31
35
 
32
- Task["make"].invoke(:make, [tracker_name, Pa.delete_ext2(torrent_file, ".torrent")])
36
+ Saber.ui.say "Can't find torrent_file, begin to make it. -- #{torrent_file}"
37
+ Task["make"].invoke(:make, [tracker_name, file])
38
+ end
33
39
  }
40
+
41
+ tracker = Tracker2[tracker_name].new(options)
42
+ #tracker.login
43
+ tracker.upload(format, filemap, {add: options["add"]})
34
44
  end
35
45
  end
36
46
  end
@@ -12,17 +12,21 @@ module Saber
12
12
  # bib.upload("Hello.epub.torrent")
13
13
  #
14
14
  module Tracker
15
- autoload :Base, "saber/tracker/base"
16
- autoload :What, "saber/tracker/what"
17
- autoload :BIB, "saber/tracker/bib"
18
-
19
15
  @@trackers = {}
20
16
  mattr_reader :trackers
21
17
 
22
18
  class << self
23
19
  def [](name)
20
+ require "saber/tracker/#{name}"
21
+
24
22
  trackers[name]
25
23
  end
26
24
  end
27
25
  end
28
26
  end
27
+
28
+ require "saber/tracker/base"
29
+ require "saber/tracker/what"
30
+ require "saber/tracker/bib"
31
+ require "saber/tracker/stp"
32
+
@@ -3,32 +3,45 @@ require "active_support/core_ext/string/inflections"
3
3
  module Saber
4
4
  module Tracker
5
5
  class Base
6
+ DELEGATE_METHODS = [:get]
7
+
6
8
  def self.inherited(child)
7
9
  Tracker.trackers[child.name.demodulize.underscore] = child
8
10
  end
9
11
 
12
+ class << self
13
+ attr_reader :tracker_name
14
+
15
+ def tracker_name
16
+ @tracker_name ||= self.name.demodulize.underscore
17
+ end
18
+ end
19
+
10
20
  # implement
11
- @@POPULATE_TYPES = []
21
+ POPULATE_TYPES = []
12
22
 
13
23
  def self.can_populate?(type)
14
- @@POPULATE_TYPES.include?(type.to_s)
24
+ self::POPULATE_TYPES.include?(type.to_s)
15
25
  end
16
26
 
17
27
  # implement
18
- @@BASE_URL = ""
19
- @@LOGIN_CHECK_PATH = ""
28
+ BASE_URL = ""
29
+ LOGIN_CHECK_PATH = ""
20
30
 
21
31
  attr_reader :agent
22
- attr_reader :site_name
23
-
32
+ attr_reader :name
33
+
24
34
  def initialize
25
- @site_name = self.class.name.demodulize.underscore
26
35
  @agent = Mechanize.new
36
+ end
27
37
 
28
- @agent.get(@@BASE_URL)
38
+ def name
39
+ self.class.tracker_name
29
40
  end
30
41
 
31
42
  def login
43
+ @agent.get(self.class::BASE_URL)
44
+
32
45
  if login_with_cookie
33
46
  return
34
47
  end
@@ -69,6 +82,14 @@ module Saber
69
82
  end
70
83
  end
71
84
 
85
+ DELEGATE_METHODS.each {|mth|
86
+ eval <<-EOF
87
+ def #{mth}(*args, &blk)
88
+ agent.#{mth}(*args, &blk)
89
+ end
90
+ EOF
91
+ }
92
+
72
93
  protected
73
94
 
74
95
  # Implement
@@ -86,14 +107,14 @@ module Saber
86
107
  end
87
108
 
88
109
  def login_with_cookie
89
- if Pa.exists?("#{Rc.p.home}/#{site_name}.cookies") then
90
- open("#{Rc.p.home}/#{site_name}.cookies") { |io|
110
+ if Pa.exists?("#{Rc.p.home}/#{name}.cookies") then
111
+ open("#{Rc.p.home}/#{name}.cookies") { |io|
91
112
  agent.cookie_jar.load_cookiestxt(io)
92
113
  }
93
114
 
94
- ret = agent.get(@@LOGIN_CHECK_PATH)
115
+ ret = agent.get(self.class::LOGIN_CHECK_PATH)
95
116
 
96
- if ret.uri.path == @@LOGIN_CHECK_PATH
117
+ if ret.uri.path == self.class::LOGIN_CHECK_PATH
97
118
  true
98
119
  else
99
120
  Saber.ui.say "Login with cookie failed."
@@ -103,13 +124,13 @@ module Saber
103
124
  end
104
125
 
105
126
  def login_with_username
106
- username = Rc._fetch(["#{site_name}.username", "username"], nil)
127
+ username = Rc._fetch(["#{name}.username", "username"], nil)
107
128
 
108
- Saber.ui.say "Begin to login manually."
129
+ Saber.ui.say "Begin to login #{name} manually."
109
130
  Saber.ui.say "Username: #{username}" if username
110
131
  loop do
111
132
  if do_login_with_username(username)
112
- open("#{Rc.p.home}/#{site_name}.cookies", "w") { |f|
133
+ open("#{Rc.p.home}/#{name}.cookies", "w") { |f|
113
134
  agent.cookie_jar.dump_cookiestxt(f)
114
135
  }
115
136
  return true
@@ -2,9 +2,8 @@ module Saber
2
2
  module Tracker
3
3
  # DOESN'T WORK for mechanize does not support javascript.
4
4
  class BB < Base
5
- @@BASE_URL = "https://baconbits.org"
6
-
7
- @@LOGIN_CHECK_PATH = "/inbox.php"
5
+ BASE_URL = "https://baconbits.org"
6
+ LOGIN_CHECK_PATH = "/inbox.php"
8
7
 
9
8
  FIELDS = {
10
9
  "Musics" => {
@@ -180,11 +179,6 @@ module Saber
180
179
  },
181
180
  }
182
181
 
183
- # We have below attributes:
184
- #
185
- # * agent: a Mechanize object
186
- # * site_name: "bib"
187
-
188
182
  # Upload one torrent file to the site.
189
183
  #
190
184
  # @param [String] file a filename
@@ -202,8 +196,8 @@ module Saber
202
196
  }.submit
203
197
 
204
198
  if ret.uri.path == "/upload.php"
205
- msg = ReverseMarkdown.parse(ret.at("//*[@id='content']/div[2]/p[2]")
206
- Saber.ui.error "ERROR:\n#{msg}"
199
+ msg = ReverseMarkdown.parse(ret.at("//*[@id='content']/div[2]/p[2]"))
200
+ Saber.ui.error "ERROR: #{msg.to_s.strip}\n"
207
201
  return false
208
202
  else
209
203
  return true
@@ -1,12 +1,11 @@
1
+ require "active_support/core_ext/object/try"
2
+
1
3
  module Saber
2
4
  module Tracker
3
5
  class BIB < Base
4
- @@POPULATE_TYPES = %w[ebook audiobook]
5
-
6
- @@BASE_URL = "http://bibliotik.org"
7
-
8
- # used by login-with-cookie to check if it's succeed.
9
- @@LOGIN_CHECK_PATH = "/conversations"
6
+ POPULATE_TYPES = %w[ebook audiobook]
7
+ BASE_URL = "http://bibliotik.org"
8
+ LOGIN_CHECK_PATH = "/conversations"
10
9
 
11
10
  FIELDS = {
12
11
  "applications" => {
@@ -128,11 +127,6 @@ module Saber
128
127
  }
129
128
  }
130
129
 
131
- # We have below attributes:
132
- #
133
- # * agent: a Mechanize object
134
- # * site_name: "bib"
135
-
136
130
  # Upload one torrent file to the site.
137
131
  #
138
132
  # @param [String] file a filename
@@ -166,6 +160,28 @@ module Saber
166
160
  }
167
161
  end
168
162
 
163
+ def browse(page, &blk)
164
+ ret = []
165
+ path = "/torrents/advanced/?search=&cat[0]=5&y1=&y2=&p1=&p2=&size1=&size2=&for[0]=15&orderby=added&order=desc&page=#{page}"
166
+
167
+ p = agent.get(path)
168
+ p.search("//td[contains(string(), '[Retail]')]/span[@class='title']/a").each {|a|
169
+ page = agent.get(a["href"])
170
+
171
+ title = page.at("//*[@id='title']").inner_text.strip
172
+ tags = page.search("//*[@class='taglist']/a").map{|n| n.inner_text}
173
+ isbn = page.at("//*[@id='details_content_info']").inner_text.match(/\((\d+)\)/).try(:[], 1) || ""
174
+ download_link = page.at("//*[@id='details_links']/a[@title='Download']")["href"]
175
+ filenames = page.search("//*[@id='files']//td[1]").map{|n| n.inner_text}
176
+
177
+ torrent = {title: title, tags: tags, isbn: isbn, download_link: download_link, filenames: filenames}
178
+ blk.call(torrent) if blk
179
+ ret << torrent
180
+ }
181
+
182
+ ret
183
+ end
184
+
169
185
  protected
170
186
 
171
187
  # Attpened to login the site with username and password. this happens
@@ -1,9 +1,9 @@
1
1
  module Saber
2
2
  module Tracker
3
3
  class CHD < Base
4
- @@BASE_URL = "https://chdbits.org"
5
- @@LOGIN_CHECK_PATH = "/messages.php"
6
- CACHE_FILE = "#{ENV['HOME']}/.cache/saber.chd"
4
+ BASE_URL = "https://chdbits.org"
5
+ LOGIN_CHECK_PATH = "/messages.php"
6
+ CACHE_FILE = "#{ENV['HOME']}/.saber/chd.cache"
7
7
 
8
8
  # cache {id: is_download}
9
9
  attr_accessor :cache
@@ -22,7 +22,7 @@ module Saber
22
22
  File.write(CACHE_FILE, Marshal.dump(cache))
23
23
  }
24
24
 
25
- agent.pluggable_parser["application/x-bittorrent"] = Mechanize::DirectorySaver.save_to(Rc.p.watch.p)
25
+ agent.pluggable_parser["application/x-bittorrent"] = Mechanize::DirectorySaver.save_to(Rc.chd.dir)
26
26
  end
27
27
 
28
28
  def add_torrents
@@ -0,0 +1,7 @@
1
+ module Saber
2
+ module Tracker
3
+ class Gazelle
4
+
5
+ end
6
+ end
7
+ end
@@ -3,8 +3,8 @@ require "active_support/core_ext/object/try"
3
3
  module Saber
4
4
  module Tracker
5
5
  class PTP < Base
6
- @@BASE_URL = "https://tls.passthepopcorn.me"
7
- @@LOGIN_CHECK_PATH = "/inbox.php"
6
+ BASE_URL = "https://tls.passthepopcorn.me"
7
+ LOGIN_CHECK_PATH = "/inbox.php"
8
8
 
9
9
  FIELDS = {
10
10
  "new" => {
@@ -0,0 +1,53 @@
1
+ module Saber
2
+ module Tracker
3
+ class STP < Base
4
+ BASE_URL = "https://stopthepress.es"
5
+ LOGIN_CHECK_PATH = "/inbox.php"
6
+
7
+ TAG_MAP = {
8
+ "nonfiction" => "non.fiction"
9
+ }
10
+
11
+ def exists?(o={})
12
+ url = "/torrents.php?cataloguenumber=#{o[:isbn]}"
13
+ page = agent.get(url)
14
+
15
+ not page.at("//*[@id='content']/div[2]/h2[contains(text(), 'Your search did not match anything.')]")
16
+ end
17
+
18
+ def convert_tags(*tags)
19
+ tags.map{|tag|
20
+ tag = tag.downcase.gsub(/&/, "and").gsub(/\s+/, ".").gsub(/'/, "")
21
+
22
+ TAG_MAP.fetch(tag, tag)
23
+ }
24
+ end
25
+
26
+ protected
27
+
28
+ def do_login_with_username(username)
29
+ agent.get("/login.php") {|p|
30
+ ret = p.form_with(action: "login.php" ) {|f|
31
+ # error
32
+ unless f
33
+ Saber.ui.error! p.at("//body").inner_text
34
+ end
35
+
36
+ f.username = username || ask("Username: ")
37
+ f.password = ask("Password: "){|q| q.echo = false}
38
+ f.checkbox(name: "keeplogged").check
39
+ }.submit
40
+
41
+ # error
42
+ if ret.uri.path == "/login.php"
43
+ msg = ret.at("//*[@id='loginform']/span[2]").inner_text
44
+ Saber.ui.error "Failed. You have #{msg} attempts remaining."
45
+ return false
46
+ else
47
+ return true
48
+ end
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -2,8 +2,8 @@ module Saber
2
2
  module Tracker
3
3
  # DON'T WORK for mechanize does not support javascript.
4
4
  class What < Base
5
- @@BASE_URL = "https://what.cd"
6
- @@LOGIN_CHECK_PATH = "/inbox.php"
5
+ BASE_URL = "https://what.cd"
6
+ LOGIN_CHECK_PATH = "/inbox.php"
7
7
 
8
8
  FIELDS = {
9
9
  "E-Books" => {