download_tv 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 16bd7893a980f0e82a8679f7c3325e9b31e91841
4
+ data.tar.gz: 7ed719b758086d4f0bc5811fc27f8e212dd592f7
5
+ SHA512:
6
+ metadata.gz: c4354d813ded87ee149c79cd99112a1a08d5abf86a52e8e0b04f32445186c2806454412f3cd52bd32e9587d79bff3e48a941aaf8426bace28676466e2795e2bd
7
+ data.tar.gz: f6e4ae1b8e80a3f9c0f7be4f8a19ec0e7426b9470caf8cc006d6eb14fca7e9d164eac42c5a4599ad95f023281144c48a657daba0d0b19f2f73084a78fd0682ac
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ date
2
+ config.rb
3
+ cookie
4
+ /Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ sudo: false
2
+ language: ruby
3
+
4
+ rvm:
5
+ - ruby-head
6
+
7
+ before_install: cp lib/download_tv/config_example.rb lib/download_tv/config.rb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # download_tv
2
+
3
+ [![Build Status](https://travis-ci.org/guille/daily-shows.svg?branch=master)](https://travis-ci.org/guille/daily-shows)
4
+
5
+ download_tv is a Ruby command line application that automatically downloads the new episodes from the shows you follow. It grabs the list of shows from your MyEpisodes account.
6
+
7
+ ### Installation
8
+
9
+ Clone the repository.
10
+
11
+ Rename the config_example.rb to config.rb and modify it if needed.
12
+
13
+ ### Usage
14
+
15
+ A binary is provided in /bin/tv.
16
+
17
+ ```
18
+ Usage: tv [options]
19
+
20
+ Specific options:
21
+ -o, --offset OFFSET Move back the last run offset
22
+ -f, --file PATH Download shows from a file
23
+ -d, --download SHOW Downloads given show
24
+ --dry-run Don't write to the date file
25
+ -h, --help Show this message
26
+ ```
27
+
28
+ Three actions are recognised:
29
+
30
+ * By default, it fetches the list of episodes from MyEpisodes that have aired since the program was run for the last time and tries to download them. The -o flag can be used in order to re-download the episodes from previous days.
31
+
32
+ * In order to download a single episode, use the -d flag. Example: *tv -d Breaking Bad S04E01*
33
+
34
+ * Finally, the -f flag can be used to download a set of episodes. This option takes a text file as an argument. Each line of the file is interpreted as a episode to download. Example: *tv -f /path/to/listofeps*
35
+
36
+ ### Configuration
37
+
38
+ * myepisodes_user: String containing the username that will be used to log in to MyEpisodes. Set to an empty string to have the application ask for it in each execution.
39
+
40
+ * auto: Boolean value (true/false). Determines whether the application will try to automatically select a torrent using pre-determined filters or show the list to the user and let him choose.
41
+
42
+ * subs: Not implemented yet. Boolean value (true/false). Determines whether the application will try to find subtitles for the shows being downloaded.
43
+
44
+ * cookie_path: String containing a path to where the session cookie for MyEpisodes will be stored. Set it to "" to prevent the cookie from being stored.
45
+
46
+ * ignored: Array containing names of TV shows you follow in MyEpisodes but don't want to download. The strings should match the name of the show as displayed by MyEpisodes. Example: ["Boring Show 1", "Boring Show 2"],
47
+
48
+ * tpb_proxy: Base URL of the ThePirateBay proxy to use.
49
+
50
+ * grabbers: String containing names of the sources where to look for torrents in ascending order of preference. Useful for activating or deactivating specific sites, reordering them or for plugin developers.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "lib"
8
+ t.libs << "test"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ t.verbose = false
11
+ end
12
+
13
+ task :clean do
14
+ rm_rf "config.rb"
15
+ rm_rf "cookie"
16
+ rm_rf "date"
17
+ end
data/bin/tv ADDED
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'download_tv'
5
+
6
+ options = {}
7
+ options[:offset] = 0
8
+ options[:dry] = false
9
+ options[:cmd] = "run"
10
+
11
+ opt_parser = OptionParser.new do |opts|
12
+ opts.banner = "Usage: tv [options]"
13
+
14
+ opts.separator ""
15
+ opts.separator "Specific options:"
16
+
17
+ opts.on("-o", "--offset OFFSET", Integer, "Move back the last run offset") do |o|
18
+ options[:offset] = o
19
+ end
20
+
21
+ opts.on("-f", "--file PATH", "Download shows from a file") do |f|
22
+ options[:cmd] = "file"
23
+ options[:arg] = f
24
+ end
25
+
26
+ opts.on("-d", "--download SHOW", "Downloads given show") do |s|
27
+ options[:cmd] = "dl"
28
+ options[:arg] = s
29
+ end
30
+
31
+ opts.on("--dry-run", "Don't write to the date file") do |n|
32
+ options[:dry] = n
33
+ end
34
+
35
+ opts.on_tail("-h", "--help", "Show this message") do
36
+ puts opts
37
+ exit
38
+ end
39
+ end
40
+
41
+ opt_parser.parse!(ARGV)
42
+
43
+ begin
44
+ dl = DownloadTV::Downloader.new(options[:offset])
45
+ case options[:cmd]
46
+ when "run"
47
+ dl.run(options[:dry])
48
+ when "dl"
49
+ dl.download_single_show(options[:arg])
50
+ when "file"
51
+ dl.download_from_file(options[:arg])
52
+ end
53
+ rescue Interrupt
54
+ puts "Interrupt signal detected. Exiting..."
55
+ exit 1
56
+ end
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "download_tv/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "download_tv"
8
+ s.version = DownloadTV::VERSION
9
+ s.authors = ["guille"]
10
+ s.email = ["guillerg96@gmail.com"]
11
+
12
+ s.summary = %q{DownloadTV finds the next episodes from shows you follow on MyEpisodes and downloads them.}
13
+ s.homepage = "https://github.com/guille/download_tv"
14
+
15
+ s.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test)/})
17
+ end
18
+ # s.files = `git ls-files -- lib/*`.split($/)
19
+ s.test_files = `git ls-files -- test/*`.split($/)
20
+ s.require_paths = ["lib"]
21
+
22
+ # s.bindir = "exe"
23
+ s.executables = ["tv"]
24
+ s.default_executable = 'tv'
25
+
26
+ s.add_development_dependency "bundler", "~> 1.15"
27
+ s.add_development_dependency "rake", "~> 10.0"
28
+ s.add_development_dependency "minitest", "~> 5.0"
29
+
30
+ s.add_dependency("json")
31
+ s.add_dependency("mechanize")
32
+ s.add_dependency("date")
33
+ s.add_dependency("io-console")
34
+
35
+ s.has_rdoc = false
36
+ end
@@ -0,0 +1,12 @@
1
+ require "json"
2
+ require "mechanize"
3
+ require "date"
4
+ require "io/console"
5
+
6
+ require "download_tv/version"
7
+ require "download_tv/downloader"
8
+ require "download_tv/torrent"
9
+ require "download_tv/myepisodes"
10
+ require "download_tv/linkgrabber"
11
+ require "download_tv/subtitles"
12
+ Dir[File.join(__dir__, 'download_tv', 'grabbers', '*.rb')].each {|file| require file }
@@ -0,0 +1,12 @@
1
+ module DownloadTV
2
+ CONFIG = {
3
+ myepisodes_user: "", # MyEpisodes login username
4
+ auto: true, # Try to automatically select the torrents
5
+ subs: true, # Download subtitles (not implemented yet)
6
+ cookie_path: "cookie", # Leave blank to prevent the app from storing cookies
7
+ ignored: [], # list of strings that match show names as written in myepisodes
8
+ tpb_proxy: "https://pirateproxy.cc", # URL of the TPB proxy to use
9
+ grabbers: ["Eztv", "ThePirateBay", "TorrentAPI"], # names of the classes in /grabbers
10
+ }
11
+
12
+ end
@@ -0,0 +1,125 @@
1
+ begin
2
+ require_relative 'config'
3
+ rescue LoadError
4
+ puts "Config file not found. Try renaming the config_example file to config.rb"
5
+ exit
6
+ end
7
+
8
+ module DownloadTV
9
+
10
+ class Downloader
11
+
12
+ attr_reader :offset, :auto, :subs
13
+
14
+ def initialize(offset)
15
+ @offset = offset.abs
16
+ @auto = DownloadTV::CONFIG[:auto]
17
+ # @subs = DownloadTV::CONFIG[:subs]
18
+ Thread.abort_on_exception = true
19
+ end
20
+
21
+ def download_single_show(show)
22
+ t = Torrent.new
23
+ download(t.get_link(show, @auto))
24
+ end
25
+
26
+
27
+ def download_from_file(filename)
28
+ raise "File doesn't exist" if !File.exists? filename
29
+ t = Torrent.new
30
+ File.readlines(filename).each { |show| download(t.get_link(show, @auto)) }
31
+
32
+ end
33
+
34
+ ##
35
+ # Gets the links.
36
+ def run(dont_write_to_date_file)
37
+ # Change to installation directory
38
+ Dir.chdir(__dir__)
39
+
40
+ date = check_date
41
+
42
+ myepisodes = MyEpisodes.new(DownloadTV::CONFIG[:myepisodes_user], DownloadTV::CONFIG[:cookie_path])
43
+ # Log in using cookie by default
44
+ myepisodes.load_cookie
45
+ shows = myepisodes.get_shows(date)
46
+
47
+ if shows.empty?
48
+ puts "Nothing to download"
49
+
50
+ else
51
+ t = Torrent.new
52
+ to_download = fix_names(shows)
53
+
54
+ queue = Queue.new
55
+
56
+ # Adds a link (or empty string to the queue)
57
+ link_t = Thread.new do
58
+ to_download.each { |show| queue << t.get_link(show, @auto) }
59
+ end
60
+
61
+ # Downloads the links as they are added
62
+ download_t = Thread.new do
63
+ to_download.size.times do
64
+ magnet = queue.pop
65
+ next if magnet == "" # Doesn't download if no torrents are found
66
+ download(magnet)
67
+ end
68
+ end
69
+
70
+ # Downloading the subtitles
71
+ # subs_t = @subs and Thread.new do
72
+ # to_download.each { |show| @s.get_subs(show) }
73
+ # end
74
+
75
+ link_t.join
76
+ download_t.join
77
+ # subs_t.join
78
+
79
+ puts "Completed. Exiting..."
80
+ end
81
+
82
+ File.write("date", Date.today) unless dont_write_to_date_file
83
+
84
+ rescue InvalidLoginError
85
+ puts "Wrong username/password combination"
86
+ end
87
+
88
+
89
+ def check_date
90
+ content = File.read("date")
91
+
92
+ last = Date.parse(content)
93
+ if last - @offset != Date.today
94
+ last - @offset
95
+ else
96
+ puts "Everything up to date"
97
+ exit
98
+ end
99
+
100
+ rescue Errno::ENOENT
101
+ File.write("date", Date.today-1)
102
+ retry
103
+ end
104
+
105
+
106
+ def fix_names(shows)
107
+ # Ignored shows
108
+ s = shows.reject do |i|
109
+ # Remove season+episode
110
+ DownloadTV::CONFIG[:ignored].include?(i.split(" ")[0..-2].join(" "))
111
+ end
112
+
113
+ # Removes apostrophes and parens
114
+ s.map { |t| t.gsub(/ \(.+\)|[']/, "") }
115
+ end
116
+
117
+
118
+ def download(link)
119
+ exec = "xdg-open \"#{link}\""
120
+
121
+ Process.detach(Process.spawn(exec, [:out, :err]=>"/dev/null"))
122
+
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,67 @@
1
+ module DownloadTV
2
+
3
+ class Addic7ed < LinkGrabber
4
+ def initialize
5
+ super("http://www.addic7ed.com/search.php?search=%s&Submit=Search", "+")
6
+ end
7
+
8
+ def get_subs(show)
9
+ url = get_url(show)
10
+ download_file(url)
11
+ end
12
+
13
+ def get_url(show)
14
+ # Change spaces for the separator
15
+ s = show.gsub(" ", @sep)
16
+
17
+ # Format the url
18
+ search = @url % [s]
19
+
20
+ agent = Mechanize.new
21
+ res = agent.get(search)
22
+
23
+ # No redirection means no subtitle found
24
+ raise NoSubtitlesError if res.uri.to_s == search
25
+
26
+ ##########
27
+ # DO OPENSUBTITLES FIRST (see subtitles.rb)
28
+ #####
29
+
30
+ # We now have an URL like:
31
+ # http://www.addic7ed.com/serie/Mr._Robot/2/3/eps2.1k3rnel-pan1c.ksd
32
+
33
+ # To find the real links:
34
+ # see comments at the end of file
35
+
36
+
37
+ end
38
+
39
+ def download_file(url)
40
+ # Url must be like "http://www.addic7ed.com/updated/1/115337/0"
41
+
42
+ # ADDIC7ED PROVIDES RSS
43
+
44
+ agent = Mechanize.new
45
+ page = agent.get(url2, [], @url)
46
+ puts page.save("Hi")
47
+
48
+ end
49
+
50
+ end
51
+ end
52
+
53
+
54
+ # subtitles = {}
55
+ # html.css(".tabel95 .newsDate").each do |td|
56
+ # if downloads = td.text.match(/\s(\d*)\sDownloads/i)
57
+ # done = false
58
+ # td.parent.parent.xpath("./tr/td/a[@class='buttonDownload']/@href").each do |link|
59
+ # if md = link.value.match(/updated/i)
60
+ # subtitles[downloads[1].to_i] = link.value
61
+ # done = true
62
+ # elsif link.value.match(/original/i) && done == false
63
+ # subtitles[downloads[1].to_i] = link.value
64
+ # done = true
65
+ # end
66
+ # end
67
+ # end
@@ -0,0 +1,30 @@
1
+ module DownloadTV
2
+ class Eztv < LinkGrabber
3
+ def initialize
4
+ super("https://eztv.ag/search/%s")
5
+ end
6
+
7
+ def get_links(s)
8
+
9
+ # Format the url
10
+ search = @url % [s]
11
+
12
+ data = @agent.get(search).search("a.magnet")
13
+
14
+ # Torrent name in data[i].attribute "title"
15
+ # "Suits S04E01 HDTV x264-LOL Torrent: Magnet Link"
16
+
17
+ # EZTV shows 50 latest releases if it can't find the torrent
18
+ raise NoTorrentsError if data.size == 50
19
+
20
+ names = data.collect { |i| i.attribute("title").text.chomp(" Magnet Link") }
21
+ links = data.collect { |i| i.attribute("href").text }
22
+
23
+ names.zip(links)
24
+
25
+ end
26
+
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,65 @@
1
+ module DownloadTV
2
+
3
+ class TorrentAPI < LinkGrabber
4
+
5
+ attr_accessor :token
6
+ attr_reader :wait
7
+
8
+ def initialize
9
+ super("https://torrentapi.org/pubapi_v2.php?mode=search&search_string=%s&token=%s")
10
+ @token = get_token
11
+ @wait = 2.1
12
+
13
+ end
14
+
15
+ ##
16
+ # Connects to Torrentapi.org and requests a token.
17
+ # Returns said token.
18
+ def get_token
19
+ page = @agent.get("https://torrentapi.org/pubapi_v2.php?get_token=get_token").content
20
+
21
+ obj = JSON.parse(page)
22
+
23
+ @token = obj['token']
24
+
25
+ end
26
+
27
+ def get_links(s)
28
+
29
+ # Format the url
30
+ search = @url % [s, @token]
31
+
32
+ page = @agent.get(search).content
33
+ obj = JSON.parse(page)
34
+
35
+ if obj["error_code"]==4 # Token expired
36
+ get_token
37
+ search = @url % [s, @token]
38
+ page = @agent.get(search).content
39
+ obj = JSON.parse(page)
40
+ end
41
+
42
+ while obj["error_code"]==5 # Violate 1req/2s limit
43
+ # puts "Torrentapi request limit hit. Wait a few seconds..."
44
+ sleep(@wait)
45
+ page = @agent.get(search).content
46
+ obj = JSON.parse(page)
47
+
48
+ end
49
+
50
+ raise NoTorrentsError if obj["error"]
51
+
52
+ names = obj["torrent_results"].collect {|i| i["filename"]}
53
+ links = obj["torrent_results"].collect {|i| i["download"]}
54
+
55
+ names.zip(links)
56
+
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ # Tokens automaticly expire in 15 minutes.
64
+ # The api has a 1req/2s limit.
65
+ # http://torrentapi.org/apidocs_v2.txt
@@ -0,0 +1,34 @@
1
+ module DownloadTV
2
+
3
+ class ThePirateBay < LinkGrabber
4
+
5
+ def initialize()
6
+ proxy = DownloadTV::CONFIG[:tpb_proxy].gsub(/\/+$/, "") || "https://thepiratebay.cr"
7
+
8
+ super("#{proxy}/search/%s/0/7/0")
9
+
10
+ end
11
+
12
+ def get_links(s)
13
+
14
+ # Format the url
15
+ search = @url % [s]
16
+
17
+ data = @agent.get(search).search("#searchResult tr")
18
+ # Skip the header
19
+ data = data.drop 1
20
+
21
+ raise NoTorrentsError if data.size == 0
22
+
23
+ # Second cell of each row contains links and name
24
+ results = data.map { |d| d.search("td")[1] }
25
+
26
+ names = results.collect {|i| i.search(".detName").text.strip }
27
+ links = results.collect {|i| i.search("a")[1].attribute("href").text }
28
+
29
+ names.zip(links)
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ module DownloadTV
2
+
3
+ class LinkGrabber
4
+ attr_reader :url
5
+
6
+ def initialize(url)
7
+ @url = url
8
+ @agent = Mechanize.new
9
+
10
+ end
11
+
12
+ def test_connection
13
+ agent = Mechanize.new
14
+ agent.read_timeout = 2
15
+ agent.get(@url)
16
+ end
17
+
18
+ def get_links(s)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ end
23
+
24
+ class NoTorrentsError < StandardError
25
+
26
+ end
27
+
28
+ class NoSubtitlesError < StandardError
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,87 @@
1
+ module DownloadTV
2
+
3
+ class MyEpisodes
4
+
5
+ def initialize(user=nil, cookie_path="")
6
+ @agent = Mechanize.new
7
+ @user = user
8
+ @cookie_path = cookie_path
9
+ @save_cookie = cookie_path != ""
10
+ end
11
+
12
+ def login
13
+ if !@user || @user==""
14
+ print "Enter your MyEpisodes username: "
15
+ @user = STDIN.gets.chomp
16
+ end
17
+
18
+ print "Enter your MyEpisodes password: "
19
+ pass = STDIN.noecho(&:gets).chomp
20
+ puts
21
+
22
+ page = @agent.get "https://www.myepisodes.com/login.php"
23
+
24
+ login_form = page.forms[1]
25
+ login_form.username = @user
26
+ login_form.password = pass
27
+
28
+ page = @agent.submit(login_form, login_form.buttons.first)
29
+
30
+ raise InvalidLoginError if page.filename == "login.php"
31
+
32
+ save_cookie() if @save_cookie
33
+
34
+ @agent
35
+
36
+ end
37
+
38
+ def load_cookie
39
+ if File.exists? @cookie_path
40
+ @agent.cookie_jar.load @cookie_path
41
+ page = @agent.get "https://www.myepisodes.com/login.php"
42
+ if page.links[1].text == "Register"
43
+ puts "The cookie is invalid/has expired."
44
+ login
45
+ end
46
+ @agent
47
+ else
48
+ puts "Cookie file not found"
49
+ login
50
+ end
51
+
52
+ end
53
+
54
+ def save_cookie
55
+ @agent.cookie_jar.save(@cookie_path, session: true)
56
+ @agent
57
+
58
+ end
59
+
60
+ def get_shows(last)
61
+ page = @agent.get "https://www.myepisodes.com/ajax/service.php?mode=view_privatelist"
62
+ shows = page.parser.css('tr.past')
63
+
64
+ s = shows.select do |i|
65
+ airdate = i.css('td.date')[0].text
66
+ Date.parse(airdate) >= last
67
+ end
68
+
69
+ s.map do |i|
70
+ name = i.css('td.showname').text
71
+ ep = i.css('td.longnumber').text
72
+
73
+ ep.insert(0, "S")
74
+ ep.sub!("x", "E")
75
+
76
+ "#{name} #{ep}"
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+
83
+ class InvalidLoginError < StandardError
84
+
85
+ end
86
+
87
+ end
@@ -0,0 +1,20 @@
1
+ module DownloadTV
2
+
3
+ class Subtitles
4
+
5
+ def initialize
6
+ @a = Addic7ed.new
7
+
8
+ end
9
+
10
+ def get_subs(show)
11
+ @a.get_subs(show)
12
+
13
+ rescue NoSubtitlesError
14
+ puts "No subtitles found for #{show}"
15
+
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,123 @@
1
+ module DownloadTV
2
+
3
+ class Torrent
4
+
5
+ attr_reader :g_names, :g_instances, :n_grabbers
6
+
7
+ def initialize
8
+ @g_names = DownloadTV::CONFIG[:grabbers].clone
9
+ @g_instances = Array.new
10
+ @n_grabbers = @g_names.size # Initial size
11
+ @tries = @n_grabbers - 1
12
+
13
+ @filters = [
14
+ ->(n){n.include?("2160")},
15
+ ->(n){n.include?("1080")},
16
+ ->(n){n.include?("720")},
17
+ ->(n){n.include?("WEB")},
18
+ ->(n){!n.include?("PROPER") || !n.include?("REPACK")},
19
+ ]
20
+
21
+ change_grabbers
22
+
23
+ end
24
+
25
+
26
+ def change_grabbers
27
+ if !@g_names.empty?
28
+ # Instantiates the last element from g_names, popping it
29
+ newt = (DownloadTV.const_get @g_names.pop).new
30
+ newt.test_connection
31
+
32
+ @g_instances.unshift newt
33
+
34
+ else
35
+ # Rotates the instantiated grabbers
36
+ @g_instances.rotate!
37
+
38
+ end
39
+
40
+ rescue Mechanize::ResponseCodeError, Net::HTTP::Persistent::Error
41
+
42
+ puts "Problem accessing #{newt.class.name}"
43
+ # We won't be using this grabber
44
+ @n_grabbers = @n_grabbers-1
45
+ @tries = @tries - 1
46
+
47
+ change_grabbers
48
+
49
+ rescue SocketError, Errno::ECONNRESET, Net::OpenTimeout
50
+ puts "Connection error."
51
+ exit
52
+
53
+ end
54
+
55
+
56
+ def get_link(show, auto)
57
+ links = @g_instances.first.get_links(show)
58
+
59
+ if !auto
60
+ links.each_with_index do |data, i|
61
+ puts "#{i}\t\t#{data[0]}"
62
+
63
+ end
64
+
65
+ puts
66
+ print "Select the torrent you want to download [-1 to skip]: "
67
+
68
+ i = $stdin.gets.chomp.to_i
69
+
70
+ while i >= links.size || i < -1
71
+ puts "Index out of bounds. Try again: "
72
+ i = $stdin.gets.chomp.to_i
73
+ end
74
+
75
+ # Reset the counter
76
+ @tries = @n_grabbers - 1
77
+
78
+ # Use -1 to skip the download
79
+ i == -1 ? "" : links[i][1]
80
+
81
+ else # Automatically get the links
82
+
83
+ links = filter_shows(links)
84
+
85
+ # Reset the counter
86
+ @tries = @n_grabbers - 1
87
+
88
+ # Get the first result left
89
+ links[0][1]
90
+
91
+ end
92
+
93
+ rescue NoTorrentsError
94
+ puts "No torrents found for #{show} using #{@g_instances.first.class.name}"
95
+
96
+ # Use next grabber
97
+ if @tries > 0
98
+ @tries-=1
99
+ change_grabbers
100
+ retry
101
+
102
+ else # Reset the counter
103
+ @tries = @n_grabbers - 1
104
+ # TODO: Handle show not found here!!
105
+ return ""
106
+
107
+ end
108
+
109
+ end
110
+
111
+ def filter_shows(links)
112
+ @filters.each do |f| # Apply each filter
113
+ new_links = links.reject {|name, link| f.(name)}
114
+ # Stop if the filter removes every release
115
+ break if new_links.size == 0
116
+
117
+ links = new_links
118
+ end
119
+ links
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,3 @@
1
+ module DownloadTV
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,64 @@
1
+ require "test_helper"
2
+
3
+ describe DownloadTV::Downloader do
4
+ before do
5
+ Dir.chdir(File.dirname(__FILE__))
6
+ File.delete("date") if File.exist?("date")
7
+ end
8
+
9
+ describe "when creating the object" do
10
+ it "should receive an integer" do
11
+ ->{ DownloadTV::Downloader.new("foo") }.must_raise NoMethodError
12
+ end
13
+
14
+ it "should store the first argument as @offset" do
15
+ DownloadTV::Downloader.new(3).offset.must_equal 3
16
+ end
17
+
18
+ end
19
+
20
+ describe "the fix_names function" do
21
+ it "should remove apostrophes and parens" do
22
+ shows = ["Mr. Foo S01E02", "Bar (UK) S00E22", "Let's S05E03"]
23
+ result = ["Mr. Foo S01E02", "Bar S00E22", "Lets S05E03"]
24
+ DownloadTV::Downloader.new(0).fix_names(shows).must_equal result
25
+ end
26
+
27
+ it "should remove ignored shows" do
28
+ DownloadTV::CONFIG[:ignored] = ["Ignored"]
29
+ shows = ["Mr. Foo S01E02", "Bar (UK) S00E22", "Ignored S20E22", "Let's S05E03"]
30
+ result = ["Mr. Foo S01E02", "Bar S00E22", "Lets S05E03"]
31
+ DownloadTV::Downloader.new(0).fix_names(shows).must_equal result
32
+ end
33
+ end
34
+
35
+
36
+ describe "the date file" do
37
+ dl = DownloadTV::Downloader.new(0)
38
+
39
+
40
+
41
+ it "should be created if it doesn't exist" do
42
+ dl.check_date
43
+ File.exist?("date").must_equal true
44
+ end
45
+
46
+ it "contains a date after running the method" do
47
+ date = dl.check_date
48
+ date.must_equal (Date.today-1)
49
+ Date.parse(File.read("date")).must_equal Date.today-1
50
+ end
51
+
52
+ it "exits the script when up to date" do
53
+ File.write("date", Date.today)
54
+ begin
55
+ dl.check_date
56
+ flunk
57
+ rescue SystemExit
58
+
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,53 @@
1
+ require "test_helper"
2
+
3
+ describe DownloadTV::LinkGrabber do
4
+
5
+ grabbers = DownloadTV::CONFIG[:grabbers].clone
6
+ instances = grabbers.map { |g| (DownloadTV.const_get g).new }
7
+
8
+ instances.each do |grabber|
9
+ describe grabber do
10
+ # grabber = g#(Object.const_get "DownloadTV::#{g}").new
11
+
12
+ it "will have a url attribute on creation" do
13
+ # instance_eval("#{g}.new")
14
+
15
+ grabber.url.wont_be_nil
16
+ end
17
+
18
+ it "should get a 200 code response" do
19
+ # grabber = (Object.const_get g).new
20
+ grabber.test_connection.code.must_equal "200"
21
+ end
22
+
23
+ it "will raise NoTorrentsError when torrent can't be found" do
24
+ # grabber = (Object.const_get g).new
25
+ notfound = ->{ grabber.get_links("Totally Fake Show askjdgsaudas") }
26
+ notfound.must_raise DownloadTV::NoTorrentsError
27
+
28
+ end
29
+
30
+ it "will return an array with names and links of results when a torrent can be found" do
31
+ # grabber = (Object.const_get g).new
32
+ result = grabber.get_links("Game Of Thrones S04E01")
33
+ result.must_be_instance_of Array
34
+ result.wont_be :empty?
35
+ result.each do |r|
36
+ r.size.must_equal 2
37
+ r[0].must_be_instance_of String
38
+ r[0].upcase.must_include "THRONES"
39
+ r[1].must_be_instance_of String
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+ it "raises an error if the instance doesn't implement get_links" do
48
+ ->{ DownloadTV::LinkGrabber.new("").get_links("test") }.must_raise NotImplementedError
49
+
50
+ end
51
+
52
+
53
+ end
@@ -0,0 +1,5 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+ require "download_tv"
3
+
4
+ require "minitest/autorun"
5
+
@@ -0,0 +1,33 @@
1
+ require "test_helper"
2
+
3
+ describe DownloadTV::Torrent do
4
+ before do
5
+ @t = DownloadTV::Torrent.new
6
+ end
7
+
8
+ describe "when creating the object" do
9
+ it "will have some grabbers" do
10
+ @t.g_names.empty?.must_equal false
11
+ @t.g_instances.empty?.must_equal false
12
+ @t.n_grabbers.must_be :>, 0
13
+ end
14
+
15
+ it "will have the right amount of grabbers" do
16
+ # Initiakize calls change_grabbers
17
+ @t.n_grabbers.must_equal @t.g_names.size + 1
18
+ @t.g_instances.size.must_equal 1
19
+
20
+ end
21
+
22
+ it "will populate the instances" do
23
+ @t.n_grabbers.times.each { @t.change_grabbers }
24
+ @t.g_names.empty?.must_equal true
25
+ @t.g_instances.empty?.must_equal false
26
+ @t.g_instances.size.must_equal @t.n_grabbers
27
+
28
+ end
29
+
30
+ # TODO: Test filter_shows(links)
31
+
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: download_tv
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - guille
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mechanize
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: date
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: io-console
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description:
112
+ email:
113
+ - guillerg96@gmail.com
114
+ executables:
115
+ - tv
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - README.md
123
+ - Rakefile
124
+ - bin/tv
125
+ - download_tv.gemspec
126
+ - lib/download_tv.rb
127
+ - lib/download_tv/config_example.rb
128
+ - lib/download_tv/downloader.rb
129
+ - lib/download_tv/grabbers/addic7ed.rb
130
+ - lib/download_tv/grabbers/eztv.rb
131
+ - lib/download_tv/grabbers/torrentapi.rb
132
+ - lib/download_tv/grabbers/tpb.rb
133
+ - lib/download_tv/linkgrabber.rb
134
+ - lib/download_tv/myepisodes.rb
135
+ - lib/download_tv/subtitles.rb
136
+ - lib/download_tv/torrent.rb
137
+ - lib/download_tv/version.rb
138
+ - test/downloader_test.rb
139
+ - test/grabbers_test.rb
140
+ - test/test_helper.rb
141
+ - test/torrent_test.rb
142
+ homepage: https://github.com/guille/download_tv
143
+ licenses: []
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.6.12
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: DownloadTV finds the next episodes from shows you follow on MyEpisodes and
165
+ downloads them.
166
+ test_files:
167
+ - test/downloader_test.rb
168
+ - test/grabbers_test.rb
169
+ - test/test_helper.rb
170
+ - test/torrent_test.rb