download_tv 1.0.0

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