kemonomachi-wanko 0.4.2
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.
- checksums.yaml +7 -0
- data/LICENSE +14 -0
- data/README +49 -0
- data/bin/wanko +8 -0
- data/lib/wanko.rb +7 -0
- data/lib/wanko/command.rb +73 -0
- data/lib/wanko/data.rb +70 -0
- data/lib/wanko/exception.rb +8 -0
- data/lib/wanko/fetch.rb +178 -0
- data/lib/wanko/read.rb +88 -0
- data/lib/wanko/utility.rb +59 -0
- data/lib/wanko/wanko.rb +111 -0
- data/lib/wanko/write.rb +27 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c386e5f07a045d367717650f2f6fe54e2223b080
|
4
|
+
data.tar.gz: 90358f22e960700a46024b874a129cee3f2bfe17
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4f70b0fd6c5fff57da833b5599a756f1256e4f55a38bdb1b15637d203b541ec8f82a62df6379c8d73281de4260ade511f9aa29cfa6862d0830818a1748566029
|
7
|
+
data.tar.gz: 88b87e0b9b1f8f1b86d91cb78f879be33f52a56cdc62b7dbf34105c02f7dc3ad6bccc652270a7d528d0897dc79e2c71b50896e89385cdafb9586509e8489ae7e
|
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
|
+
Version 2, December 2004
|
3
|
+
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
5
|
+
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
7
|
+
copies of this license document, and changing it is allowed as long
|
8
|
+
as the name is changed.
|
9
|
+
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
12
|
+
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
14
|
+
|
data/README
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
Wanko - RSS Torrent Fetcher
|
2
|
+
|
3
|
+
Description
|
4
|
+
-----------
|
5
|
+
|
6
|
+
Fetches torrent file links from RSS feeds based on user-specified rules. The
|
7
|
+
links can be printed to stdout or sent to Transmission for download, or the
|
8
|
+
torrent files can be downloaded to a watchdir for processing by other torrent
|
9
|
+
clients.
|
10
|
+
|
11
|
+
Installation
|
12
|
+
------------
|
13
|
+
|
14
|
+
wanko can be installed from RubyGems:
|
15
|
+
|
16
|
+
gem install kemonomachi+wanko
|
17
|
+
|
18
|
+
'kemonomachi+' is used as a ghetto namespace, since RubyGems doesn't support
|
19
|
+
proper ones.
|
20
|
+
|
21
|
+
Usage
|
22
|
+
-----
|
23
|
+
|
24
|
+
wanko [options]
|
25
|
+
|
26
|
+
The optional -c switch specifies the configuration directory, which defaults to
|
27
|
+
$HOME/.wanko.
|
28
|
+
|
29
|
+
See 'wanko --help' for usage info.
|
30
|
+
|
31
|
+
Can be run manually, but creating a cron job to run it at regular intervals is
|
32
|
+
recommended. It is not a daemon.
|
33
|
+
|
34
|
+
Configuration
|
35
|
+
-------------
|
36
|
+
|
37
|
+
The config file is named 'config.yaml'. The example folder contains a full
|
38
|
+
example config. RSS feeds, default fetch directory, fetch method and rules can
|
39
|
+
be specified. Regexen are used to match torrents against rules.
|
40
|
+
|
41
|
+
The config directory also holds 'history.yaml', which contains already read RSS
|
42
|
+
items. This prevents fetching the same torrent multiple times.
|
43
|
+
|
44
|
+
About the Name
|
45
|
+
--------------
|
46
|
+
|
47
|
+
Wanko (わんこ) is Japanese for 'doggy'. Because it's small and it fetches
|
48
|
+
stuff for you.
|
49
|
+
|
data/bin/wanko
ADDED
data/lib/wanko.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
|
3
|
+
require 'wanko/read'
|
4
|
+
require 'wanko/wanko'
|
5
|
+
require 'wanko/write'
|
6
|
+
|
7
|
+
module Wanko
|
8
|
+
module Command
|
9
|
+
def self.fetch(options)
|
10
|
+
config = begin
|
11
|
+
Wanko::Read.config options[:config_dir]
|
12
|
+
rescue Errno::ENOENT
|
13
|
+
abort 'Config file not found, aborting...'
|
14
|
+
end
|
15
|
+
|
16
|
+
[:feeds, :rules].each do |key|
|
17
|
+
warn 'WARN: No #{key} specified.' if config[key].empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
history = Wanko::Read.history options[:config_dir]
|
21
|
+
|
22
|
+
torrents, new_history = Wanko::check_feeds config[:feeds], config[:rules], history
|
23
|
+
|
24
|
+
config[:fetcher].call torrents
|
25
|
+
|
26
|
+
Wanko::Write.history options[:config_dir], new_history
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.add(options)
|
30
|
+
config = begin
|
31
|
+
Wanko::Read.raw_config options[:config_dir]
|
32
|
+
rescue Errno::ENOENT
|
33
|
+
abort 'Config file not found, aborting...'
|
34
|
+
end
|
35
|
+
|
36
|
+
id = if config[:rules].empty?
|
37
|
+
0
|
38
|
+
else
|
39
|
+
config[:rules].map {|x| x[:id]}.max + 1
|
40
|
+
end
|
41
|
+
|
42
|
+
rule = [id: id, regex: options[:regex], dir: options[:dir] || config[:base_dir]]
|
43
|
+
|
44
|
+
new_config = config.merge rules: config[:rules] + rule
|
45
|
+
|
46
|
+
Wanko::Write.config options[:config_dir], Wanko::Utility.stringify_keys(new_config)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.list(options)
|
50
|
+
config = begin
|
51
|
+
Wanko::Read.raw_config options[:config_dir]
|
52
|
+
rescue Errno::ENOENT
|
53
|
+
abort 'Config file not found, aborting...'
|
54
|
+
end
|
55
|
+
|
56
|
+
puts Terminal::Table.new(rows: config[:rules].map {|r| [r[:id], r[:regex], r[:dir]]},
|
57
|
+
headings: ['ID', 'Regex', 'Directory']) {align_column 0, :right}
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.remove(options)
|
61
|
+
config = begin
|
62
|
+
Wanko::Read.raw_config options[:config_dir]
|
63
|
+
rescue Errno::ENOENT
|
64
|
+
abort 'Config file not found, aborting...'
|
65
|
+
end
|
66
|
+
|
67
|
+
new_rules = config[:rules].reject {|rule| options[:ids].include? rule[:id]}
|
68
|
+
|
69
|
+
Wanko::Write.config options[:config_dir], Wanko::Utility.stringify_keys(config.merge rules: new_rules)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
data/lib/wanko/data.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Wanko
|
4
|
+
|
5
|
+
# Data representations.
|
6
|
+
module Data
|
7
|
+
|
8
|
+
# Class for torrent data. Contains the name of and the link to a torrent,
|
9
|
+
# as well as the directory the torrent will be downloaded to.
|
10
|
+
class Torrent
|
11
|
+
def initialize(name, link, dir)
|
12
|
+
@name = name
|
13
|
+
@link = link
|
14
|
+
@dir = dir
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :name, :link, :dir
|
18
|
+
|
19
|
+
# Returns a Hash representation of this Torrent
|
20
|
+
def to_h()
|
21
|
+
{
|
22
|
+
name: name,
|
23
|
+
link: link,
|
24
|
+
dir: dir
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a JSON representation of this Torrent
|
29
|
+
def to_json(state = nil)
|
30
|
+
to_h.to_json state
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Rule for matching against RSS items. Contains a regex for matching and a
|
35
|
+
# directory to download matched torrents to.
|
36
|
+
class Rule
|
37
|
+
|
38
|
+
# Public: Initialize a Rule object.
|
39
|
+
#
|
40
|
+
#regex - String or Regexp for matching.
|
41
|
+
#dir - Directory to download matched torrents to.
|
42
|
+
def initialize(regex, dir)
|
43
|
+
@regex = Regexp.new regex, Regexp::IGNORECASE
|
44
|
+
@dir = dir
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :regex, :dir
|
48
|
+
|
49
|
+
# Public: Match the regex of this Rule against a String
|
50
|
+
#
|
51
|
+
# str - String to match.
|
52
|
+
#
|
53
|
+
# Returns true if str matches, false otherwise.
|
54
|
+
def =~(str)
|
55
|
+
regex =~ str
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public: Compare this Rule to another object.
|
59
|
+
#
|
60
|
+
# other - Object to compare this Rule to.
|
61
|
+
#
|
62
|
+
# Returns true if the regex and dir fields of this Rule are equal to
|
63
|
+
# the corresponding fields in other, false otherwise.
|
64
|
+
def ==(other)
|
65
|
+
regex == other.regex && dir == other.dir
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/lib/wanko/fetch.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'wanko/exception'
|
7
|
+
require 'wanko/utility'
|
8
|
+
|
9
|
+
module Wanko
|
10
|
+
|
11
|
+
# Functions for fetching torrents in different ways. Several functions have
|
12
|
+
# side-effects, and ::fetcher_for returns Lambdas that have side-effects.
|
13
|
+
module Fetch
|
14
|
+
|
15
|
+
# Public: Create a lambda that fetches torrents when called.
|
16
|
+
#
|
17
|
+
# config - Hash with options for how to fetch torrents. :name is always
|
18
|
+
# required and should be one of 'stdout', 'watchdir' and
|
19
|
+
# 'transmission'. Other options vary depending on the value of
|
20
|
+
# :name, as follows:
|
21
|
+
#
|
22
|
+
# name: 'stdout'
|
23
|
+
# :format - Serialization format. See ::serialize for values.
|
24
|
+
# name: 'transmission'
|
25
|
+
# :host - The daemon's host
|
26
|
+
# :port - The daemon's port
|
27
|
+
# :path - The daemon's path.
|
28
|
+
# :user - Username for Basic auth (optional)
|
29
|
+
# :password - Password for Basic auth (optional)
|
30
|
+
#
|
31
|
+
# Returns a fetcher Lambda.
|
32
|
+
# Raises Wanko::ConfigError if :name option is not recognized.
|
33
|
+
def self.fetcher_for(config)
|
34
|
+
case config[:name]
|
35
|
+
when 'stdout'
|
36
|
+
->(torrents) {$stdout.puts serialize(config[:format], torrents)}
|
37
|
+
when 'watchdir'
|
38
|
+
->(torrents) {to_watchdir torrents}
|
39
|
+
when 'transmission'
|
40
|
+
url = make_transmission_url config[:host], config[:port], config[:path]
|
41
|
+
|
42
|
+
auth = if config[:user] || config[:password]
|
43
|
+
config.select {|k, _| [:user, :password].include? k}
|
44
|
+
end
|
45
|
+
|
46
|
+
->(torrents) {to_transmission url, auth, torrents}
|
47
|
+
else
|
48
|
+
raise Wanko::ConfigError, "Invalid fetcher name '#{config[:name]}'"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Internal: Serialize torrents for the stdout fetcher.
|
53
|
+
#
|
54
|
+
# format - Name of serialization method to use. Should be either 'json',
|
55
|
+
# 'simple' or 'yaml'.
|
56
|
+
# torrents - Torrents to serialize.
|
57
|
+
#
|
58
|
+
# Returns a String representation of torrents, in the given format.
|
59
|
+
# Raises Wanko::ConfigError if format is not recognized.
|
60
|
+
def self.serialize(format, torrents)
|
61
|
+
case format
|
62
|
+
when 'json'
|
63
|
+
torrents.to_json
|
64
|
+
when 'simple'
|
65
|
+
torrents.map(&:link).join "\n"
|
66
|
+
when 'yaml', nil
|
67
|
+
Utility.stringify_keys(torrents.map &:to_h).to_yaml
|
68
|
+
else
|
69
|
+
raise Wanko::ConfigError, "Invalid format '#{format}' for stdout fetcher."
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Internal: Download torrent files to watch directories. Creates any
|
74
|
+
# nonexisting directories.
|
75
|
+
#
|
76
|
+
# torrents - Torrents to download.
|
77
|
+
#
|
78
|
+
# Returns nothing.
|
79
|
+
def self.to_watchdir(torrents)
|
80
|
+
torrents.each do |tor|
|
81
|
+
FileUtils.mkdir_p tor.dir
|
82
|
+
|
83
|
+
open tor.link do |remote|
|
84
|
+
File.binwrite File.join(tor.dir, "#{tor.name}.torrent"), remote.read
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Internal: Send RPC commands to Transmission daemon for downloading
|
90
|
+
# torrents.
|
91
|
+
#
|
92
|
+
# url - URI object containing the URL of the daemon
|
93
|
+
# auth - Hash with :user and :password for Basic auth, or falsey value
|
94
|
+
# for no authorization.
|
95
|
+
# torrents - Array of torrents to download.
|
96
|
+
#
|
97
|
+
# Returns nothing.
|
98
|
+
def self.to_transmission(url, auth, torrents)
|
99
|
+
return if torrents.empty?
|
100
|
+
|
101
|
+
requests = torrents.map {|torrent| make_post_request url, make_transmission_rpc_command(torrent), auth}
|
102
|
+
|
103
|
+
Net::HTTP.start url.host, url.port do |transmission|
|
104
|
+
send_transmission_requests transmission, requests
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Internal: Send requests to a Transmission daemon. If a 409 Conflict
|
109
|
+
# response is recieved, update the 'X-transmission-Session-Id' header of
|
110
|
+
# the request and resend it.
|
111
|
+
#
|
112
|
+
# transmission - Net::HTTP connection to a Transmission daemon.
|
113
|
+
# reqs - Net::HTTP::Post requests to send to Transmission.
|
114
|
+
#
|
115
|
+
# Returns nothing.
|
116
|
+
def self.send_transmission_requests(transmission, reqs)
|
117
|
+
session_id = ''
|
118
|
+
|
119
|
+
reqs.each do |req|
|
120
|
+
req['X-Transmission-Session-Id'] = session_id
|
121
|
+
|
122
|
+
response = transmission.request req
|
123
|
+
|
124
|
+
case response
|
125
|
+
when Net::HTTPConflict
|
126
|
+
session_id = response['X-Transmission-Session-Id']
|
127
|
+
redo
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Internal: Create a URI object for a Transmission daemon URL.
|
133
|
+
#
|
134
|
+
# host - Host part of the URL.
|
135
|
+
# port - Port part of the URL.
|
136
|
+
# path - Path part of the URL.
|
137
|
+
#
|
138
|
+
# Returns a URI object.
|
139
|
+
def self.make_transmission_url(host, port, path)
|
140
|
+
URI::HTTP.build host: host || '127.0.0.1',
|
141
|
+
port: port || 9091,
|
142
|
+
path: "#{path || '/transmission/'}rpc"
|
143
|
+
end
|
144
|
+
|
145
|
+
# Internal: Generate a JSON-encoded 'torrent-add' RPC command that can be
|
146
|
+
# sent to a Transmission daemon.
|
147
|
+
#
|
148
|
+
# torrent - Torrent to generate command for.
|
149
|
+
#
|
150
|
+
# Returns a String RPC command.
|
151
|
+
def self.make_transmission_rpc_command(torrent)
|
152
|
+
JSON.generate(
|
153
|
+
{
|
154
|
+
"method" => "torrent-add",
|
155
|
+
"arguments" => {
|
156
|
+
"filename" => torrent.link,
|
157
|
+
"download-dir" => torrent.dir
|
158
|
+
}
|
159
|
+
}
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Internal: Create an HTTP POST request.
|
164
|
+
#
|
165
|
+
# url - URL this request will be sent to.
|
166
|
+
# body - String with body data.
|
167
|
+
# auth - Hash with Basic auth data, or falsey value for no authorization.
|
168
|
+
#
|
169
|
+
# Returns a Net::HTTP::Post request
|
170
|
+
def self.make_post_request(url, body, auth)
|
171
|
+
request = Net::HTTP::Post.new url
|
172
|
+
request.body = body
|
173
|
+
request.basic_auth auth[:user], auth[:password] if auth
|
174
|
+
request
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
data/lib/wanko/read.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
require 'wanko/data'
|
4
|
+
require 'wanko/fetch'
|
5
|
+
require 'wanko/utility'
|
6
|
+
|
7
|
+
module Wanko
|
8
|
+
|
9
|
+
# Functions to read in data from files, urls and similar.
|
10
|
+
# All functions can be considered to rely on external state.
|
11
|
+
module Read
|
12
|
+
|
13
|
+
# Public: Read a config file, create a fetcher lambda and convert the
|
14
|
+
# rules.
|
15
|
+
#
|
16
|
+
# dir - Path to the directory containing the config file.
|
17
|
+
#
|
18
|
+
# Returns a Hash containing the configuration.
|
19
|
+
def self.config(dir)
|
20
|
+
config = raw_config dir
|
21
|
+
|
22
|
+
config.merge(
|
23
|
+
fetcher: Fetch.fetcher_for({name: 'stdout'}.merge Hash config[:fetcher]),
|
24
|
+
rules: Array(config[:rules]).map {|rule| convert_rule rule, config[:base_dir]}
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Read a config file and supply some sensible defaults.
|
29
|
+
#
|
30
|
+
# dir - Path to the directory containing the config file.
|
31
|
+
#
|
32
|
+
# Returns a Hash containing the configuration.
|
33
|
+
def self.raw_config(dir)
|
34
|
+
config = Utility.symbolize_keys(YAML.load_file File.join(dir, 'config.yaml')) || {}
|
35
|
+
|
36
|
+
config.merge(
|
37
|
+
feeds: Array(config[:feeds]),
|
38
|
+
base_dir: config[:base_dir] || File.join(Dir.home, 'downloads'),
|
39
|
+
rules: Array(config[:rules])
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Internal: Convert a rule Hash into a Rule object, making the path
|
44
|
+
# absolute.
|
45
|
+
#
|
46
|
+
# rule - Rule to convert. Has a required :regex entry and an optional
|
47
|
+
# :dir entry.
|
48
|
+
# base_dir - Directory to use as base for relative paths, or as download
|
49
|
+
# dir if the rule has no :dir entry.
|
50
|
+
#
|
51
|
+
# Returns an Array of converted Rules.
|
52
|
+
def self.convert_rule(rule, base_dir)
|
53
|
+
Data::Rule.new rule[:regex], File.absolute_path(rule[:dir] || '', base_dir)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Read an RSS feed history file.
|
57
|
+
#
|
58
|
+
# dir - Path to the directory containing the history file.
|
59
|
+
#
|
60
|
+
# Returns a Hash containing the history, or an empty Hash if the file does
|
61
|
+
# not exist.
|
62
|
+
def self.history(dir)
|
63
|
+
history = begin
|
64
|
+
YAML.load_file File.join(dir, 'history.yaml')
|
65
|
+
rescue Errno::ENOENT
|
66
|
+
{}
|
67
|
+
end
|
68
|
+
|
69
|
+
# This is OK, since history is treated as read-only.
|
70
|
+
history.default = []
|
71
|
+
|
72
|
+
history
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Read an RSS feed.
|
76
|
+
#
|
77
|
+
# url - Location of the feed. Must be openable by OpenURI.
|
78
|
+
#
|
79
|
+
# Returns an RSS::Rss object, or nil if the feed couldn't be read.
|
80
|
+
def self.feed(url)
|
81
|
+
begin
|
82
|
+
open(url, read_timeout: 10) {|rss| RSS::Parser.parse rss}
|
83
|
+
rescue OpenURI::HTTPError, Timeout::Error, Errno::ECONNREFUSED, SocketError => ex
|
84
|
+
warn "WARN: #{url} --> #{ex}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Wanko
|
2
|
+
|
3
|
+
# Various utility functions.
|
4
|
+
module Utility
|
5
|
+
|
6
|
+
# Public: Recursively convert all keys of a Hash using the supplied block.
|
7
|
+
# Array values are traversed and each entry is converted. If arg is any
|
8
|
+
# other kind of object it is left untouched. If the conversion results in
|
9
|
+
# duplicate keys, later keys will overwrite earlier ones.
|
10
|
+
#
|
11
|
+
# Convenience functions exist for some often used operations, see
|
12
|
+
# ::symbolize_keys and ::stringify_keys.
|
13
|
+
#
|
14
|
+
# arg - Object to convert.
|
15
|
+
# block - Block for converting the keys. Will be called with each key
|
16
|
+
# individually. Should return an object that can be used as a hash key.
|
17
|
+
#
|
18
|
+
# Examples
|
19
|
+
#
|
20
|
+
# Utility.convert_keys h, &:to_sym
|
21
|
+
#
|
22
|
+
# Utility.convert_keys(h) {|key| key.downcase.squeeze}
|
23
|
+
#
|
24
|
+
# Returns an object with converted hash keys.
|
25
|
+
def self.convert_keys(arg, &block)
|
26
|
+
case arg
|
27
|
+
when Hash
|
28
|
+
arg.each_with_object({}) { |(key, val), memo|
|
29
|
+
memo[yield(key)] = convert_keys(val, &block)
|
30
|
+
}
|
31
|
+
when Array
|
32
|
+
arg.map {|val| convert_keys val, &block}
|
33
|
+
else
|
34
|
+
arg
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Recursively convert all keys in a Hash to Symbols. Does not check
|
39
|
+
# for duplicate keys. See ::convert_keys for more info.
|
40
|
+
#
|
41
|
+
# hash - Hash to convert. All keys must respond to #to_sym.
|
42
|
+
#
|
43
|
+
# Returns a structurally identical Hash with keys converted to Symbols.
|
44
|
+
def self.symbolize_keys(hash)
|
45
|
+
convert_keys hash, &:to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Recursively convert all keys in a Hash to Strings. Does not check
|
49
|
+
# for duplicate keys. See ::convert_keys for more info.
|
50
|
+
#
|
51
|
+
# hash - Hash to convert. All keys must respond to #to_s.
|
52
|
+
#
|
53
|
+
# Returns a structurally identical Hash with keys converted to Strings.
|
54
|
+
def self.stringify_keys(hash)
|
55
|
+
convert_keys hash, &:to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/lib/wanko/wanko.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'rss'
|
3
|
+
|
4
|
+
require 'wanko/command'
|
5
|
+
require 'wanko/data'
|
6
|
+
require 'wanko/read'
|
7
|
+
|
8
|
+
module Wanko
|
9
|
+
|
10
|
+
# Public: Parse cli switches. Exits and prints a usage message when given
|
11
|
+
# -h, --help or an invalid switch.
|
12
|
+
#
|
13
|
+
# args - Array of switches to parse. Will not be altered.
|
14
|
+
#
|
15
|
+
# Returns a Hash containing the parsed options.
|
16
|
+
def self.parse_cli_switches(args)
|
17
|
+
options = {
|
18
|
+
command: Command.method(:fetch),
|
19
|
+
config_dir: File.join(Dir.home, ".wanko")
|
20
|
+
}
|
21
|
+
|
22
|
+
parser = OptionParser.new do |parser|
|
23
|
+
parser.banner = 'Usage: wanko [options]'
|
24
|
+
|
25
|
+
parser.separator ''
|
26
|
+
parser.separator 'Options:'
|
27
|
+
|
28
|
+
parser.on '-c DIR', '--config_dir', 'Use a different config directory' do |dir|
|
29
|
+
options[:config_dir] = File.absolute_path dir
|
30
|
+
end
|
31
|
+
|
32
|
+
parser.on '-a REGEX', '--add', 'Add a fetch rule' do |regex|
|
33
|
+
options[:command] = Command.method :add
|
34
|
+
options[:regex] = regex
|
35
|
+
end
|
36
|
+
|
37
|
+
parser.on '-d DIR', '--directory', 'Optional directory for fetch rules added with -a' do |dir|
|
38
|
+
options[:dir] = dir
|
39
|
+
end
|
40
|
+
|
41
|
+
parser.on '-l', '--list', 'List all rules' do
|
42
|
+
options[:command] = Command.method :list
|
43
|
+
end
|
44
|
+
|
45
|
+
parser.on '-r ID', '--remove', Integer, 'Remove a fetch rule' do |id|
|
46
|
+
options[:command] = Command.method :remove
|
47
|
+
options[:ids] = [id]
|
48
|
+
end
|
49
|
+
|
50
|
+
parser.separator ''
|
51
|
+
parser.separator 'Other:'
|
52
|
+
|
53
|
+
parser.on '-h', '--help',
|
54
|
+
'Show this message' do
|
55
|
+
puts parser
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
begin
|
61
|
+
parser.parse args
|
62
|
+
rescue OptionParser::InvalidOption
|
63
|
+
puts parser
|
64
|
+
exit
|
65
|
+
end
|
66
|
+
|
67
|
+
options
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: Check RSS feeds for new torrents matching a set of rules, excluding
|
71
|
+
# already read items.
|
72
|
+
#
|
73
|
+
# urls - Array of feed urls to check.
|
74
|
+
# rules - Array of Rules to match against.
|
75
|
+
# history - Hash with urls mapped to Arrays of already read items. Will be
|
76
|
+
# accessed using the urls in the urls parameter. If a url is
|
77
|
+
# missing, an empty Array will be substituted.
|
78
|
+
#
|
79
|
+
# Returns a pair [[Torrent], Hash] of matched items and an updated history
|
80
|
+
def self.check_feeds(urls, rules, history)
|
81
|
+
matches, new_history = urls.map { |url|
|
82
|
+
feed = Read.feed(url) or next [[], {url => history[url]}]
|
83
|
+
|
84
|
+
[
|
85
|
+
match(feed, rules, Array(history[url])),
|
86
|
+
{url => feed.items.map {|item| item.guid.content}}
|
87
|
+
]
|
88
|
+
}.transpose
|
89
|
+
|
90
|
+
[matches.flatten, new_history.reduce(:merge)]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Internal: Match a set of rules against the items of an RSS feed, excluding
|
94
|
+
# already read items.
|
95
|
+
#
|
96
|
+
# feed - RSS object which items to search through.
|
97
|
+
# rules - Array of Rules.
|
98
|
+
# history - Array of GUIDs. Items found in this array will be rejected.
|
99
|
+
#
|
100
|
+
# Returns an Array of Torrents representing the matched items.
|
101
|
+
def self.match(feed, rules, history)
|
102
|
+
feed.items
|
103
|
+
.reject {|item| history.include? item.guid.content}
|
104
|
+
.product(rules)
|
105
|
+
.select {|item, rule| rule =~ item.title}
|
106
|
+
.map { |item, rule|
|
107
|
+
Data::Torrent.new item.title, item.link, rule.dir
|
108
|
+
}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
data/lib/wanko/write.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Wanko
|
4
|
+
|
5
|
+
# Functions for writing data out to files or similar. All functions can be
|
6
|
+
# considered to have destructive side-effects.
|
7
|
+
module Write
|
8
|
+
|
9
|
+
# Public: Write a YAML representation of an object to a file named
|
10
|
+
# 'history.yaml'.
|
11
|
+
#
|
12
|
+
# This function _will_ clobber an existing file.
|
13
|
+
#
|
14
|
+
# dir - Path of the directory to write the file in.
|
15
|
+
# history - Object to write out. Responds to #to_yaml.
|
16
|
+
#
|
17
|
+
# Returns nothing
|
18
|
+
def self.history(dir, history)
|
19
|
+
File.write File.join(dir, 'history.yaml'), history.to_yaml
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.config(dir, config)
|
23
|
+
File.write File.join(dir, 'config.yaml'), config.to_yaml
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kemonomachi-wanko
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ookami Kenrou
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: terminal-table
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
description: |2
|
28
|
+
Fetches torrent file links from RSS feeds based on user-specified rules. The
|
29
|
+
links can be printed to stdout or sent to Transmission for download, or the
|
30
|
+
torrent files can be downloaded to a watchdir for processing by other torrent
|
31
|
+
clients.
|
32
|
+
email: ookamikenrou@gmail.com
|
33
|
+
executables:
|
34
|
+
- wanko
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- LICENSE
|
39
|
+
- README
|
40
|
+
- bin/wanko
|
41
|
+
- lib/wanko.rb
|
42
|
+
- lib/wanko/command.rb
|
43
|
+
- lib/wanko/data.rb
|
44
|
+
- lib/wanko/exception.rb
|
45
|
+
- lib/wanko/fetch.rb
|
46
|
+
- lib/wanko/read.rb
|
47
|
+
- lib/wanko/utility.rb
|
48
|
+
- lib/wanko/wanko.rb
|
49
|
+
- lib/wanko/write.rb
|
50
|
+
homepage: https://github.com/kemonomachi/wanko
|
51
|
+
licenses:
|
52
|
+
- WTFPL
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 2.0.0
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements:
|
69
|
+
- Transmission bittorrent client (optional)
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 2.5.1
|
72
|
+
signing_key:
|
73
|
+
specification_version: 4
|
74
|
+
summary: RSS Torrent Fetcher
|
75
|
+
test_files: []
|