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