download_tv 2.6.5 → 2.6.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,32 +5,65 @@ module DownloadTV
5
5
  # API wrapper for MyEpisodes
6
6
  class MyEpisodes
7
7
  def initialize(user, save_cookie)
8
- @agent = Mechanize.new
9
- @agent.user_agent = DownloadTV::USER_AGENT
10
8
  @user = user
11
9
  @save_cookie = save_cookie
12
10
  @cookie_path = File.join(ENV['HOME'], '.config', 'download_tv', 'cookie')
11
+ agent.user_agent = DownloadTV::USER_AGENT
12
+ login
13
+ end
14
+
15
+ def get_shows_since(last, include_tomorrow: false)
16
+ page = agent.get 'https://www.myepisodes.com/ajax/service.php?mode=view_privatelist'
17
+ shows = page.parser.css('tr.past')
18
+ shows = filter_newer_shows(shows, last)
19
+ shows.concat(page.parser.css('tr.today')) if include_tomorrow
20
+ build_show_strings(shows)
21
+ end
22
+
23
+ private
24
+
25
+ def agent
26
+ @agent ||= Mechanize.new
13
27
  end
14
28
 
15
29
  def login
30
+ logged_in_with_cookie = load_cookie
31
+ manual_login unless logged_in_with_cookie
32
+ end
33
+
34
+ def manual_login
16
35
  pass = prompt_user_data
17
- page = @agent.get 'https://www.myepisodes.com/login.php'
36
+ page = agent.get 'https://www.myepisodes.com/login.php'
18
37
 
19
38
  login_form = page.forms[1]
20
39
  login_form.username = @user
21
40
  login_form.password = pass
22
41
 
23
- page = @agent.submit(login_form, login_form.buttons.first)
42
+ page = agent.submit(login_form, login_form.buttons.first)
24
43
 
25
44
  raise InvalidLoginError if page.filename == 'login.php'
26
45
 
27
- save_cookie if @save_cookie
46
+ store_cookie if @save_cookie
47
+ end
48
+
49
+ ##
50
+ # If there is a cookie file, tries to log in using it
51
+ # returns the result of the operation (true/false)
52
+ def load_cookie
53
+ if File.exist? @cookie_path
54
+ agent.cookie_jar.load @cookie_path
55
+ return true if logged_in?
56
+
57
+ puts 'The cookie is invalid/has expired.'
58
+ else
59
+ puts 'Cookie file not found'
60
+ end
28
61
 
29
- @agent
62
+ false
30
63
  end
31
64
 
32
65
  def prompt_user_data
33
- if !@user || @user == ''
66
+ if @user.nil? || @user.empty?
34
67
  print 'Enter your MyEpisodes username: '
35
68
  @user = $stdin.gets.chomp
36
69
  end
@@ -41,47 +74,21 @@ module DownloadTV
41
74
  pass
42
75
  end
43
76
 
44
- def load_cookie
45
- if File.exist? @cookie_path
46
- @agent.cookie_jar.load @cookie_path
47
- return @agent if logged_in?
48
-
49
- puts 'The cookie is invalid/has expired.'
50
- else
51
- puts 'Cookie file not found'
52
- end
53
-
54
- login
55
- end
56
-
57
77
  def logged_in?
58
- page = @agent.get 'https://www.myepisodes.com/login.php'
78
+ page = agent.get 'https://www.myepisodes.com/login.php'
59
79
  page.links[1].text != 'Register'
60
80
  end
61
81
 
62
- def save_cookie
63
- @agent.cookie_jar.save(@cookie_path, session: true)
64
- @agent
65
- end
66
-
67
- def get_shows_since(last)
68
- page = @agent.get 'https://www.myepisodes.com/ajax/service.php?mode=view_privatelist'
69
- shows = page.parser.css('tr.past')
70
- shows = filter_newer_shows(shows, last)
71
- build_show_strings(shows)
72
- end
73
-
74
- def today_shows
75
- page = @agent.get 'https://www.myepisodes.com/ajax/service.php?mode=view_privatelist'
76
- shows = page.parser.css('tr.today')
77
- build_show_strings(shows)
82
+ def store_cookie
83
+ agent.cookie_jar.save(@cookie_path, session: true)
78
84
  end
79
85
 
80
86
  # Only keep the shows that have aired since the given date
81
87
  def filter_newer_shows(shows, date)
82
88
  shows.select do |i|
83
89
  airdate = i.css('td.date')[0].text
84
- Date.parse(airdate) >= date
90
+ viewed_checkbox = i.css('td.status input').last
91
+ Date.parse(airdate) >= date && viewed_checkbox&.attribute('checked').nil?
85
92
  end
86
93
  end
87
94
 
@@ -4,26 +4,49 @@ module DownloadTV
4
4
  ##
5
5
  # Class in charge of managing the link grabbers
6
6
  class Torrent
7
- attr_reader :g_instances, :tries
7
+ class << self
8
+ def grabbers
9
+ %w[TorrentAPI Torrentz Eztv]
10
+ end
8
11
 
9
- def grabbers
10
- %w[TorrentAPI Torrentz Eztv]
12
+ def healthcheck
13
+ grabbers.each do |g|
14
+ grabber = (DownloadTV.const_get g).new
15
+ puts "#{g}: #{grabber.online? ? 'online' : 'offline'}"
16
+ end
17
+ end
11
18
  end
12
19
 
13
20
  def initialize(default_grabber = nil)
14
- g_names = grabbers
15
-
16
- # Silently ignores bad names
17
- found_default = g_names.find_index(default_grabber)
18
- g_names.rotate! found_default if found_default
19
-
20
- @g_instances = g_names.map { |g| (DownloadTV.const_get g).new }
21
+ @g_instances = self.class.grabbers\
22
+ .rotate(self.class.grabbers.find_index(default_grabber) || 0)
23
+ .map { |g| (DownloadTV.const_get g).new }
21
24
  reset_tries
22
25
 
23
- check_grabber_online
26
+ remove_grabber_if_offline
24
27
  end
25
28
 
26
- def check_grabber_online
29
+ def get_links(show)
30
+ @g_instances.first.get_links(show)
31
+ rescue NoTorrentsError
32
+ if @tries.positive?
33
+ change_grabbers
34
+ retry
35
+ end
36
+ # We're out of grabbers to try
37
+ puts "No torrents found for #{show}"
38
+ []
39
+ ensure
40
+ reset_grabbers_order
41
+ end
42
+
43
+ private
44
+
45
+ ##
46
+ # This method removes the grabber from the instances list if it is not online
47
+ # It will repeat until it finds an online grabber, or exit the application
48
+ # if there are none
49
+ def remove_grabber_if_offline
27
50
  if @g_instances.empty?
28
51
  warn 'There are no available grabbers.'
29
52
  exit 1
@@ -34,27 +57,13 @@ module DownloadTV
34
57
  warn "Problem accessing #{@g_instances.first.class.name}"
35
58
  @tries -= 1
36
59
  @g_instances.shift # Removes first element
37
- check_grabber_online
60
+ remove_grabber_if_offline
38
61
  end
39
62
 
40
63
  def change_grabbers
41
64
  @tries -= 1
42
65
  @g_instances.rotate!
43
- check_grabber_online
44
- end
45
-
46
- def get_links(show)
47
- @g_instances.first.get_links(show)
48
- rescue NoTorrentsError
49
- if @tries.positive?
50
- change_grabbers
51
- retry
52
- end
53
- # We're out of grabbers to try
54
- puts "No torrents found for #{show}"
55
- []
56
- ensure
57
- reset_grabbers_order
66
+ remove_grabber_if_offline
58
67
  end
59
68
 
60
69
  def reset_tries
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DownloadTV
4
- VERSION = '2.6.5'
4
+ VERSION = '2.6.7'
5
5
  end
data/lib/download_tv.rb CHANGED
@@ -14,7 +14,6 @@ require 'download_tv/torrent'
14
14
  require 'download_tv/filterer'
15
15
  require 'download_tv/myepisodes'
16
16
  require 'download_tv/linkgrabber'
17
- require 'download_tv/subtitles'
18
17
 
19
18
  module DownloadTV
20
19
  USER_AGENT = "DownloadTV #{DownloadTV::VERSION}"
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DownloadTV::Configuration do
4
+ let(:raw_config) { double('raw_config') }
5
+ let(:parsed_config) { { version: DownloadTV::VERSION, pending: [] } }
6
+ let(:opts) { {} }
7
+ subject { described_class.new(opts) }
8
+
9
+ before :each do
10
+ allow(File).to receive(:exist?).and_return true
11
+ allow(File).to receive(:read).and_return raw_config
12
+ allow(JSON).to receive(:parse).and_return parsed_config
13
+ end
14
+
15
+ describe '#[] and #[]=' do
16
+ it 'will set and get values of the underlying hash' do
17
+ subject[:test] = :any
18
+ expect(subject[:test]).to eq(:any)
19
+ end
20
+ end
21
+
22
+ describe '#initialize' do
23
+ context 'when the config file exists' do
24
+ context 'when options are given' do
25
+ let(:opts) { { myepisodes_user: 'test', pending: [1], ignored: ['aAAa'] } }
26
+ it 'will apply them to the final config' do
27
+ expect(subject[:myepisodes_user]).to eq opts[:myepisodes_user]
28
+ expect(subject[:pending]).to eq opts[:pending]
29
+ end
30
+
31
+ it 'will downcase strings in :ignored' do
32
+ expect(subject[:ignored].first).to eq opts[:ignored].first.downcase
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'when the config file does not exist' do
38
+ before :each do
39
+ allow(File).to receive(:exist?).and_return false
40
+ allow(FileUtils).to receive(:mkdir_p)
41
+ allow_any_instance_of(described_class).to receive(:change_configuration)
42
+ allow_any_instance_of(described_class).to receive(:serialize)
43
+ end
44
+
45
+ context 'when options are given' do
46
+ let(:opts) { { myepisodes_user: 'test' } }
47
+
48
+ it 'will override the other values' do
49
+ expect(subject[:myepisodes_user]).to eq opts[:myepisodes_user]
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#change_configuration' do
56
+ let(:myepisodes_user) { 'myep' }
57
+ let(:cookies) { 'n' }
58
+ let(:ignored) { 'ignored1,ignored2' }
59
+
60
+ before :each do
61
+ allow(File).to receive(:exist?).and_return false
62
+ allow(FileUtils).to receive(:mkdir_p)
63
+ allow_any_instance_of(described_class).to receive(:serialize)
64
+ allow($stdin).to receive(:gets).and_return(myepisodes_user, cookies, ignored, '', '')
65
+ end
66
+
67
+ it 'will create a new config with the given and the default values' do
68
+ expect(subject[:myepisodes_user]).to eq myepisodes_user
69
+ expect(subject[:cookie]).to be false
70
+ expect(subject[:auto]).to be true
71
+ expect(subject[:ignored].size).to eq 2
72
+ expect(subject[:date]).to eq(Date.today - 1)
73
+ expect(subject[:filters]).not_to be_nil
74
+ expect(subject[:version]).not_to be_nil
75
+ expect(subject[:pending]).not_to be_nil
76
+ expect(subject[:grabber]).not_to be_nil
77
+ end
78
+ end
79
+
80
+ describe '#serialize' do
81
+ let(:parsed_config) { { version: DownloadTV::VERSION, pending: [1, 1, 2] } }
82
+
83
+ before :each do
84
+ allow(File).to receive(:write).and_return nil
85
+ end
86
+
87
+ it 'will remove duplicates from :pending' do
88
+ subject.serialize
89
+ expect(subject[:pending].size).to eq 2
90
+ end
91
+
92
+ it 'will write to a file' do
93
+ expect(File).to receive(:write)
94
+ subject.serialize
95
+ end
96
+
97
+ context 'when a path is given in the options' do
98
+ let(:opts) { { path: '/tmp/test' } }
99
+ it 'will write to a file in our given path' do
100
+ config = double('config')
101
+ expect(JSON).to receive(:generate).and_return(config)
102
+ expect(File).to receive(:write).with(opts[:path], config)
103
+ subject.serialize
104
+ end
105
+ end
106
+ end
107
+
108
+ describe '#to_s' do
109
+ it 'will form a string with each (key, value) pair in a new line' do
110
+ expected = "version: #{DownloadTV::VERSION}\n"\
111
+ "pending: []\n"
112
+ expect(subject.to_s).to eq expected
113
+ end
114
+ end
115
+
116
+ describe '#clear_pending' do
117
+ it 'will clear :pending and call serialize' do
118
+ subject[:pending] << double
119
+ expect(subject).to receive(:serialize)
120
+ expect(subject[:pending].size).to eq 1
121
+ subject.clear_pending
122
+ expect(subject[:pending].size).to eq 0
123
+ end
124
+ end
125
+
126
+ describe '#queue_pending' do
127
+ it 'will add an item to :pending and serialize' do
128
+ expect(subject).to receive(:serialize)
129
+ expect(subject[:pending].size).to eq 0
130
+ subject.queue_pending(double)
131
+ expect(subject[:pending].size).to eq 1
132
+ end
133
+ end
134
+
135
+ context 'breaking changes:' do
136
+ let(:version) { nil }
137
+ let(:parsed_config) do
138
+ {
139
+ version: version
140
+ }
141
+ end
142
+
143
+ before :each do
144
+ stub_const('DownloadTV::VERSION', '2.1.10')
145
+ allow(File).to receive(:exist?).and_return true
146
+ allow(File).to receive(:read).and_return raw_config
147
+ allow(JSON).to receive(:parse).and_return parsed_config
148
+ end
149
+
150
+ describe 'when the config does not have a version' do
151
+ it 'will trigger a config update' do
152
+ expect_any_instance_of(described_class).to receive(:change_configuration).once.and_return nil
153
+ subject
154
+ end
155
+ end
156
+
157
+ describe 'when the app version is newer (patch)' do
158
+ let(:version) { '2.1.9' }
159
+ it 'will NOT trigger a config update' do
160
+ expect_any_instance_of(described_class).not_to receive(:change_configuration)
161
+ subject
162
+ end
163
+ end
164
+
165
+ describe 'when the app version is the same' do
166
+ let(:version) { '2.1.10' }
167
+ it 'will NOT trigger a config update' do
168
+ expect_any_instance_of(described_class).not_to receive(:change_configuration)
169
+ subject
170
+ end
171
+ end
172
+
173
+ describe 'when the app version is newer (minor)' do
174
+ let(:version) { '2.0.19' }
175
+
176
+ it 'will trigger a config update' do
177
+ expect_any_instance_of(described_class).to receive(:change_configuration).once.and_return nil
178
+ subject
179
+ end
180
+ end
181
+
182
+ describe 'when the app version is newer (major)' do
183
+ let(:version) { '1.20.999' }
184
+
185
+ it 'will trigger a config update' do
186
+ expect_any_instance_of(described_class).to receive(:change_configuration).once.and_return nil
187
+ subject
188
+ end
189
+ end
190
+
191
+ describe 'when the app version is older (any)' do
192
+ let(:version) { '2.1.11' }
193
+ it 'will trigger a config update' do
194
+ expect_any_instance_of(described_class).to receive(:change_configuration).once.and_return nil
195
+ subject
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DownloadTV::Filterer do
4
+ let(:excludes) { [] }
5
+ let(:includes) { [] }
6
+ let(:filters_config) { { excludes: excludes, includes: includes } }
7
+
8
+ subject { described_class.new(filters_config) }
9
+
10
+ describe '#filter' do
11
+ let(:test_data) do
12
+ [
13
+ 'Test 12',
14
+ 'Test 10',
15
+ 'Exclude'
16
+ ]
17
+ end
18
+
19
+ context 'when there are no filters' do
20
+ it 'will return the given list' do
21
+ expect(subject.filter(test_data)).to eq test_data
22
+ end
23
+ end
24
+
25
+ context 'when there are exclude filters' do
26
+ describe 'when there is only one entry not matching (one filter)' do
27
+ let(:excludes) { ['TEST'] }
28
+ it 'will return it' do
29
+ filtered = subject.filter(test_data)
30
+ expect(filtered.size).to eq 1
31
+ expect(filtered.first).to eq 'Exclude'
32
+ end
33
+ end
34
+
35
+ describe 'when there is only one entry not matching (multiple filter)' do
36
+ let(:excludes) { ['2', '0'] }
37
+ it 'will return it' do
38
+ filtered = subject.filter(test_data)
39
+ expect(filtered.size).to eq 1
40
+ expect(filtered.first).to eq 'Exclude'
41
+ end
42
+ end
43
+
44
+ describe 'when only one filter matches' do
45
+ let(:excludes) { ['0'] }
46
+ it 'will not return that element' do
47
+ filtered = subject.filter(test_data)
48
+ expect(filtered.size).to eq 2
49
+ expect(filtered.include?('Test 10')).to be false
50
+ end
51
+ end
52
+
53
+ describe 'when no entries match' do
54
+ let(:excludes) { ['zzzz'] }
55
+ it 'will return the original' do
56
+ filtered = subject.filter(test_data)
57
+ expect(filtered).to eq test_data
58
+ end
59
+ end
60
+
61
+ describe 'when all entries match (one filter)' do
62
+ let(:excludes) { ['E'] }
63
+ it 'will return the original' do
64
+ filtered = subject.filter(test_data)
65
+ expect(filtered).to eq test_data
66
+ end
67
+ end
68
+
69
+ describe 'when all entries match (more filters)' do
70
+ let(:excludes) { ['TEST', 'EXCLUDE'] }
71
+ it 'will only apply filters until there would be no values left' do
72
+ filtered = subject.filter(test_data)
73
+ expect(filtered.size).to eq 1
74
+ expect(filtered.first).to eq 'Exclude'
75
+ end
76
+ end
77
+ end
78
+
79
+ context 'when there are include filters' do
80
+ let(:includes) { ['TEST'] }
81
+ it 'will filter out entries not matching' do
82
+ filtered = subject.filter(test_data)
83
+ expect(filtered.size).to eq 2
84
+ expect(filtered.include?('Exclude')).to be false
85
+ end
86
+ end
87
+
88
+ context 'when there are both types of filters' do
89
+ let(:excludes) { ['EXCLUDE'] }
90
+ let(:includes) { ['EXCLUDE'] }
91
+
92
+ it 'will apply "includes" filters first' do
93
+ filtered = subject.filter(test_data)
94
+ expect(filtered.size).to eq 1
95
+ expect(filtered.first).to eq 'Exclude'
96
+ end
97
+
98
+ describe 'if the filters are not capitalised' do
99
+ let(:excludes) { ['exclude'] }
100
+ let(:includes) { ['test'] }
101
+ it 'will not apply the filter successfully' do
102
+ expect(subject.filter(test_data)).to eq test_data
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DownloadTV::LinkGrabber do
4
+ # TODO: Write specs for the individual grabbers (see #4)
5
+ # grabbers = DownloadTV::Torrent.grabbers
6
+ # instances = grabbers.map { |g| (DownloadTV.const_get g).new }
7
+
8
+ # instances.each do |grabber|
9
+ # describe grabber do
10
+ # end
11
+ # end
12
+
13
+ it "raises an error if the instance doesn't implement get_links" do
14
+ expect { DownloadTV::LinkGrabber.new(double).get_links(double) }.to raise_error(NotImplementedError)
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe DownloadTV::MyEpisodes do
4
+ let(:save_cookie) { true }
5
+ let(:page) { double('page') }
6
+ let(:agent) { double('agent', :user_agent= => nil, get: page) }
7
+ let(:cookie_jar) { double('cookie_jar') }
8
+ subject { described_class.new('user', true) }
9
+
10
+ before :each do
11
+ allow(Mechanize).to receive(:new).and_return agent
12
+ allow(agent).to receive(:cookie_jar).and_return cookie_jar
13
+ allow(cookie_jar).to receive(:load)
14
+ allow(cookie_jar).to receive(:save)
15
+ end
16
+
17
+ describe '#initialize' do
18
+ context 'when cookie does not load' do
19
+ it 'will execute a user/password login' do
20
+ allow_any_instance_of(described_class).to receive(:load_cookie).and_return false
21
+ expect_any_instance_of(described_class).to receive(:manual_login).once.and_return nil
22
+ subject
23
+ end
24
+ end
25
+
26
+ context 'when using a valid cookie' do
27
+ it 'will log in via cookie' do
28
+ allow_any_instance_of(described_class).to receive(:load_cookie).and_return true
29
+ expect_any_instance_of(described_class).not_to receive(:manual_login)
30
+ subject
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#get_shows_since' do
36
+ # TODO
37
+ end
38
+ end