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.
- data/.gitignore +1 -0
- data/CHANGELOG.md +8 -1
- data/Gemfile +6 -1
- data/Gemfile.lock +58 -14
- data/README.md +40 -125
- data/bin/saber +4 -2
- data/bin/saber.bb +3 -0
- data/bin/saber.stp +3 -0
- data/lib/saber.rb +5 -0
- data/lib/saber/autofetcher/server.rb +1 -1
- data/lib/saber/book.rb +36 -0
- data/lib/saber/cli.rb +45 -19
- data/lib/saber/core_ext.rb +68 -0
- data/lib/saber/fetcher.rb +2 -2
- data/lib/saber/rc.rb +6 -0
- data/lib/saber/task.rb +3 -0
- data/lib/saber/task/base.rb +6 -1
- data/lib/saber/task/chd.rb +10 -2
- data/lib/saber/task/clean.rb +2 -2
- data/lib/saber/task/find_uploads.rb +53 -0
- data/lib/saber/task/generate.rb +37 -20
- data/lib/saber/task/make.rb +20 -8
- data/lib/saber/task/send.rb +1 -1
- data/lib/saber/task/upload.rb +29 -19
- data/lib/saber/tracker.rb +8 -4
- data/lib/saber/tracker/base.rb +36 -15
- data/lib/saber/tracker/bb.rb +4 -10
- data/lib/saber/tracker/bib.rb +27 -11
- data/lib/saber/tracker/chd.rb +4 -4
- data/lib/saber/tracker/gazelle.rb +7 -0
- data/lib/saber/tracker/ptp.rb +2 -2
- data/lib/saber/tracker/stp.rb +53 -0
- data/lib/saber/tracker/what.rb +2 -2
- data/lib/saber/tracker2.rb +31 -0
- data/lib/saber/tracker2/base.rb +126 -0
- data/lib/saber/tracker2/bb.rb +211 -0
- data/lib/saber/tracker2/bib.rb +162 -0
- data/lib/saber/tracker2/gazelle.rb +52 -0
- data/lib/saber/tracker2/stp.rb +136 -0
- data/lib/saber/tracker2/what.rb +51 -0
- data/lib/saber/version.rb +1 -1
- data/lib/saber/watir_ext.rb +95 -0
- data/saber.gemspec +6 -1
- data/spec/saber/autofetcher/server_spec.rb +1 -1
- data/spec/saber/task_spec.rb +1 -1
- data/systemd/saber-chd@.service +12 -0
- data/systemd/saber-client@.service +12 -0
- data/systemd/saber-server@.service +12 -0
- data/templates/_saberrc +29 -8
- data/templates/article.yml +18 -0
- data/templates/bib/application.yml +8 -8
- data/templates/bib/audiobook.yml +12 -14
- data/templates/comic.yml +25 -0
- data/templates/ebook.yml +35 -0
- data/templates/journal.yml +19 -0
- data/templates/magazine.yml +41 -0
- data/templates/manual.yml +6 -0
- data/templates/newspaper.yml +6 -0
- metadata +109 -13
- data/templates/bb/comic.yml +0 -7
- data/templates/bb/ebook.yml +0 -8
- data/templates/bb/magazine.yml +0 -6
- data/templates/bib/article.yml +0 -18
- data/templates/bib/comic.yml +0 -22
- data/templates/bib/ebook.yml +0 -20
- data/templates/bib/journal.yml +0 -18
- data/templates/bib/magazine.yml +0 -18
- data/templates/what/ebook.yml +0 -6
data/lib/saber/task/make.rb
CHANGED
@@ -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
|
-
|
33
|
-
|
34
|
-
|
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
|
data/lib/saber/task/send.rb
CHANGED
@@ -14,7 +14,7 @@ module Saber
|
|
14
14
|
end
|
15
15
|
|
16
16
|
*files, dest = args
|
17
|
-
system "rsync -
|
17
|
+
system "rsync -ahP #{files.shelljoin} #{Rc.server.user}@#{Rc.server.host}:#{dest}", show_cmd: "$"
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
data/lib/saber/task/upload.rb
CHANGED
@@ -2,35 +2,45 @@ module Saber
|
|
2
2
|
module Task
|
3
3
|
# Usage
|
4
4
|
#
|
5
|
-
# Task["upload"].invoke(:upload, ["site", "Hello.epub"])
|
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
|
12
|
-
def upload(tracker_name, *
|
13
|
-
|
10
|
+
# @param [String] file
|
11
|
+
def upload(tracker_name, format, *files)
|
12
|
+
filemap = {} # {file => torrent_file}
|
14
13
|
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
27
|
-
|
26
|
+
# make torrent if torrent_file not exists.
|
27
|
+
filemap.each { |file, torrent_file|
|
28
|
+
if not Pa.exists?(torrent_file)
|
28
29
|
|
29
|
-
|
30
|
-
|
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
|
-
|
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
|
data/lib/saber/tracker.rb
CHANGED
@@ -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
|
+
|
data/lib/saber/tracker/base.rb
CHANGED
@@ -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
|
-
|
21
|
+
POPULATE_TYPES = []
|
12
22
|
|
13
23
|
def self.can_populate?(type)
|
14
|
-
|
24
|
+
self::POPULATE_TYPES.include?(type.to_s)
|
15
25
|
end
|
16
26
|
|
17
27
|
# implement
|
18
|
-
|
19
|
-
|
28
|
+
BASE_URL = ""
|
29
|
+
LOGIN_CHECK_PATH = ""
|
20
30
|
|
21
31
|
attr_reader :agent
|
22
|
-
attr_reader :
|
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
|
-
|
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}/#{
|
90
|
-
open("#{Rc.p.home}/#{
|
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(
|
115
|
+
ret = agent.get(self.class::LOGIN_CHECK_PATH)
|
95
116
|
|
96
|
-
if ret.uri.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(["#{
|
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}/#{
|
133
|
+
open("#{Rc.p.home}/#{name}.cookies", "w") { |f|
|
113
134
|
agent.cookie_jar.dump_cookiestxt(f)
|
114
135
|
}
|
115
136
|
return true
|
data/lib/saber/tracker/bb.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
data/lib/saber/tracker/bib.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
6
|
-
|
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
|
data/lib/saber/tracker/chd.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Saber
|
2
2
|
module Tracker
|
3
3
|
class CHD < Base
|
4
|
-
|
5
|
-
|
6
|
-
CACHE_FILE = "#{ENV['HOME']}/.
|
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.
|
25
|
+
agent.pluggable_parser["application/x-bittorrent"] = Mechanize::DirectorySaver.save_to(Rc.chd.dir)
|
26
26
|
end
|
27
27
|
|
28
28
|
def add_torrents
|
data/lib/saber/tracker/ptp.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
data/lib/saber/tracker/what.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
5
|
+
BASE_URL = "https://what.cd"
|
6
|
+
LOGIN_CHECK_PATH = "/inbox.php"
|
7
7
|
|
8
8
|
FIELDS = {
|
9
9
|
"E-Books" => {
|