saber 1.1.1 → 1.2.0

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