From f8328594f41eaa43a8e231336b2b8e2453f0aee5 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Wed, 15 Jun 2016 08:21:20 +0400 Subject: [PATCH 01/89] Move download logic to a service object --- README.md | 13 +++- bin/addic7ed | 15 ++-- lib/addic7ed.rb | 1 + lib/addic7ed/episode.rb | 41 ++--------- lib/addic7ed/errors.rb | 15 ++-- lib/addic7ed/services/subtitle_downloader.rb | 54 ++++++++++++++ spec/lib/addic7ed/episode_spec.rb | 63 +++++------------ .../services/subtitle_downloader_spec.rb | 70 +++++++++++++++++++ 8 files changed, 172 insertions(+), 100 deletions(-) create mode 100644 lib/addic7ed/services/subtitle_downloader.rb create mode 100644 spec/lib/addic7ed/services/subtitle_downloader_spec.rb diff --git a/README.md b/README.md index 4768209..d02aacd 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,17 @@ Ruby command-line script to fetch subtitles on Addic7ed +### Refactoring TODO list + +* [x] move download logic to a service object +* [ ] move compatibility logic to a service object +* [ ] move best subtitle logic to a service object +* [ ] refactor how `Episode` holds `Subtitle`s +* [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) +* [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") +* [ ] write documentation +* [ ] move CLI to a separate gem (and use Thor or similar) + ### Is it working ? Until next time Addic7ed break their HTML/CSS structure, yes :smile: @@ -64,7 +75,7 @@ Don't get mad, they have to pay for their servers, you know. Ho, and by the way, please, **please**: do not hammer their servers, play fair ! -### Roadmap +### Contribution ideas There's some work remaining: - Support registered users diff --git a/bin/addic7ed b/bin/addic7ed index 6d28161..d09159f 100755 --- a/bin/addic7ed +++ b/bin/addic7ed @@ -127,18 +127,11 @@ options[:filenames].each do |filename| rescue Addic7ed::NoSubtitleFound puts "No (acceptable) subtitle has been found on Addic7ed for #{filename}. Maybe try again later.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] next - rescue Addic7ed::DownloadError - puts "The subtitle could not be downloaded. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - rescue Addic7ed::DownloadLimitReached - puts "You exceeded your daily download count. Exiting.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] + rescue Addic7ed::DailyLimitExceeded => e + puts "#{e.message}. Exiting.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] break - rescue Addic7ed::SubtitleCannotBeSaved - puts "The downloaded subtitle could not be saved as #{filename.gsub(/\.\w{3}$/, '.srt')}. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - rescue Addic7ed::HTTPError => e - puts "Network error: #{e.message}".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] + rescue Addic7ed::DownloadError => e + puts "The subtitle could not be saved: #{e.message}. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] next end - end diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index 5418194..381fd1e 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -2,6 +2,7 @@ require 'addic7ed/common' require 'addic7ed/services/addic7ed_version_normalizer' require 'addic7ed/services/addic7ed_comment_normalizer' +require 'addic7ed/services/subtitle_downloader' require 'addic7ed/errors' require 'addic7ed/show_list' require 'addic7ed/video_file' diff --git a/lib/addic7ed/episode.rb b/lib/addic7ed/episode.rb index ece3f6a..aa95d4a 100644 --- a/lib/addic7ed/episode.rb +++ b/lib/addic7ed/episode.rb @@ -29,15 +29,11 @@ def best_subtitle(lang = 'fr', no_hi = false) return @best_subtitle[lang] end - def download_best_subtitle!(lang, no_hi = false, http_redirect_limit = 8) - raise HTTPError.new('Too many HTTP redirects') unless http_redirect_limit > 0 - uri = URI(best_subtitle(lang, no_hi).url) - response = get_http_response(uri, url(lang)) - if response.kind_of?(Net::HTTPRedirection) - follow_redirection(lang, no_hi, response['location'], http_redirect_limit) - else - save_subtitle(response.body, lang) - end + def download_best_subtitle!(lang, no_hi = false) + subtitle_url = best_subtitle(lang, no_hi).url + subtitle_filename = video_file.basename.gsub(/\.\w{3}$/, untagged ? ".srt" : ".#{lang}.srt") + referer = url(lang) + SubtitleDownloader.call(subtitle_url, subtitle_filename, referer) end protected @@ -64,32 +60,5 @@ def initialize_language(lang) @subtitles ||= {} @subtitles[lang] ||= [] end - - def get_http_response(uri, referer) - Net::HTTP.start(uri.hostname, uri.port) do |http| - request = Net::HTTP::Get.new(uri.request_uri) - # Addic7ed needs the Referer to be correct. User-agent is just here to fake a real browser request. - request['Referer'] = referer - request['User-Agent'] = USER_AGENTS.sample - http.request(request) - end - rescue - raise DownloadError - end - - def follow_redirection(lang, no_hi, new_uri, http_redirect_limit) - # Addic7ed is serving redirection URL not-encoded, but Ruby does not support it (see http://bugs.ruby-lang.org/issues/7396) - best_subtitle(lang).url = URI.escape(new_uri) - raise DownloadLimitReached if /^\/downloadexceeded.php/.match best_subtitle(lang).url - download_best_subtitle!(lang, no_hi, http_redirect_limit - 1) - end - - def save_subtitle(content, lang) - Kernel.open "#{video_file}".gsub(/\.\w{3}$/, untagged ? ".srt" : ".#{lang}.srt"), 'w' do |f| - f << content - end - rescue - raise SubtitleCannotBeSaved - end end end diff --git a/lib/addic7ed/errors.rb b/lib/addic7ed/errors.rb index b22f5bf..0034850 100644 --- a/lib/addic7ed/errors.rb +++ b/lib/addic7ed/errors.rb @@ -1,9 +1,10 @@ module Addic7ed - exceptions = [ - :InvalidFilename, :ShowNotFound, :EpisodeNotFound, :LanguageNotSupported, - :ParsingError, :NoSubtitleFound, :DownloadError, :DownloadLimitReached, - :SubtitleCannotBeSaved, :HTTPError - ] - - exceptions.each { |e| const_set(e, Class.new(StandardError)) } + class InvalidFilename < StandardError; end + class ShowNotFound < StandardError; end + class EpisodeNotFound < StandardError; end + class LanguageNotSupported < StandardError; end + class ParsingError < StandardError; end + class NoSubtitleFound < StandardError; end + class DownloadError < StandardError; end + class DailyLimitExceeded < DownloadError; end end diff --git a/lib/addic7ed/services/subtitle_downloader.rb b/lib/addic7ed/services/subtitle_downloader.rb new file mode 100644 index 0000000..8e785f0 --- /dev/null +++ b/lib/addic7ed/services/subtitle_downloader.rb @@ -0,0 +1,54 @@ +module Addic7ed + class SubtitleDownloader + HTTP_REDIRECT_LIMIT = 8 + + attr_reader :url, :filename, :referer, :http_redirect_count + + def initialize(url, filename, referer, http_redirect_count) + @url = url + @filename = filename + @referer = referer + @http_redirect_count = http_redirect_count + end + + def self.call(url, filename, referer, http_redirect_count = 0) + new(url, filename, referer, http_redirect_count).call + end + + def call + raise DownloadError.new("Too many HTTP redirections") if http_redirect_count >= HTTP_REDIRECT_LIMIT + raise DailyLimitExceeded.new("Daily limit exceeded") if /^\/downloadexceeded.php/.match url + if response.kind_of? Net::HTTPRedirection + new_url = URI.escape(response["location"]) # Addic7ed is serving redirection URL not-encoded, but Ruby does not support it (see http://bugs.ruby-lang.org/issues/7396) + SubtitleDownloader.call(new_url, filename, url, http_redirect_count + 1) + else + write(response.body) + end + end + + private + + def uri + @uri ||= URI(url) + end + + def response + @response ||= Net::HTTP.start(uri.hostname, uri.port) do |http| + request = Net::HTTP::Get.new(uri.request_uri) + request["Referer"] = referer # Addic7ed needs the Referer to be correct + request["User-Agent"] = USER_AGENTS.sample # User-agent is just here to fake a real browser request + http.request(request) + end + rescue + raise DownloadError.new("A network error occured") + end + + def write(content) + Kernel.open(filename, "w") do |f| + f << content + end + rescue + raise DownloadError.new("Cannot write to disk") + end + end +end diff --git a/spec/lib/addic7ed/episode_spec.rb b/spec/lib/addic7ed/episode_spec.rb index 22ffc5f..03faa89 100644 --- a/spec/lib/addic7ed/episode_spec.rb +++ b/spec/lib/addic7ed/episode_spec.rb @@ -102,64 +102,37 @@ end describe '#download_best_subtitle!' do - let(:episode) { Addic7ed::Episode.new(@filename) } + let(:videofilename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } + let(:lang) { "fr" } + let(:episode) { Addic7ed::Episode.new(@filename) } + let(:best_subtitle) { episode.best_subtitle(lang) } - before do - WebMock.reset! - @page_stub = stub_request(:get, 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8') - .to_return File.new('spec/responses/walking-dead-3-2-8.http') - @sub_stub = stub_request(:get, 'http://www.addic7ed.com/original/68018/4') - .to_return File.new('spec/responses/walking-dead-3-2-8_best_subtitle.http') - # Prevent actual disk writing - allow(Kernel).to receive(:open) - end + subject { episode.download_best_subtitle!(lang) } - it 'gets the best subtitle candidate with a network request' do - expect(episode).to receive(:best_subtitle).once.and_call_original - episode.download_best_subtitle!('fr') - expect(@page_stub).to have_been_requested - expect(@sub_stub).to have_been_requested - end + before { stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8").to_return File.new("spec/responses/walking-dead-3-2-8.http") } - it 'raises DownloadError when a network error happens' do - stub_request(:get, 'http://www.addic7ed.com/original/68018/4').to_timeout - expect{ episode.download_best_subtitle!('fr') }.to raise_error Addic7ed::DownloadError + it "calls SubtitleDownloader on the best subtitle's URL" do + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(episode.best_subtitle("fr").url, anything(), anything()) + subject end - it 'is called recursively' do - stub_request(:get, 'http://www.addic7ed.com/original/68018/4').to_return File.new('spec/responses/basic_redirection.http') - stub_request(:get, 'http://www.addic7ed.com/original/68018/4.redirected').to_return File.new('spec/responses/walking-dead-3-2-8_best_subtitle.http') - expect(episode).to receive(:download_best_subtitle!).twice.and_call_original - episode.download_best_subtitle!('fr') + it "calls SubtitleDownloader with episode's page as referer" do + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), anything(), episode.url) + subject end - it 'raises HTTPError when stuck in a HTTP redirections loop' do - stub_request(:get, 'http://www.addic7ed.com/original/68018/4') - .to_return File.new('spec/responses/redirection_loop.http') - expect{ episode.download_best_subtitle!('fr') }.to raise_error Addic7ed::HTTPError - end - - it 'creates a new file on disk' do - file = double('file') - expect(Kernel).to receive(:open).with('The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.fr.srt', 'w').and_yield(file) - expect(file).to receive(:<<) - episode.download_best_subtitle!('fr') + it "calls SubtitleDownloader with videofile's filename with language-prefixed .srt extension" do + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), File.basename(videofilename, ".*") + ".#{lang}.srt", anything()) + subject end context "when untagged option is set" do let(:episode) { Addic7ed::Episode.new(@filename, true) } - it "does not include language code in subtitle filename" do - file = double('file') - expect(Kernel).to receive(:open).with('The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.srt', 'w').and_yield(file) - expect(file).to receive(:<<) - episode.download_best_subtitle!('fr') + it "calls SubtitleDownloader with videofile's filename with .srt extension" do + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), File.basename(videofilename, ".*") + ".srt", anything()) + subject end end - - it 'raises SubtitleCannotBeSaved when a disk error happens' do - expect(Kernel).to receive(:open).with('The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.fr.srt', 'w').and_raise('Persmission denied') - expect{ episode.download_best_subtitle!('fr') }.to raise_error Addic7ed::SubtitleCannotBeSaved - end end end diff --git a/spec/lib/addic7ed/services/subtitle_downloader_spec.rb b/spec/lib/addic7ed/services/subtitle_downloader_spec.rb new file mode 100644 index 0000000..6dc0b30 --- /dev/null +++ b/spec/lib/addic7ed/services/subtitle_downloader_spec.rb @@ -0,0 +1,70 @@ +require "spec_helper" +require "./lib/addic7ed" + +describe Addic7ed::SubtitleDownloader do + let(:url) { "http://www.addic7ed.com/original/68018/4" } + let(:referer) { "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" } + let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.fr.srt" } + + subject { described_class.new(url, filename, referer, 0) } + + describe "#call" do + let!(:http_stub) { stub_request(:get, url).to_return File.new("spec/responses/walking-dead-3-2-8_best_subtitle.http") } + + before { allow_any_instance_of(described_class).to receive(:write) } + + it "fetches the subtitle" do + subject.call + expect(http_stub).to have_been_requested + end + + it "writes the subtitle to disk" do + subject.call + expect(subject).to have_received(:write) + end + + context "when the HTTP request returns a HTTP redirection" do + let!(:http_stub) { stub_request(:get, url).to_return File.new("spec/responses/basic_redirection.http") } + let!(:redirect_url) { "http://www.addic7ed.com/original/68018/4.redirected" } + let!(:redirect_stub) { stub_request(:get, redirect_url).to_return File.new("spec/responses/walking-dead-3-2-8_best_subtitle.http") } + + it "follows the redirection by calling itself with redirection URL" do + subject.call + expect(http_stub).to have_been_requested.once + expect(redirect_stub).to have_been_requested.once + end + + context "when it gets stuck in a redirection loop" do + let!(:redirect_stub) { stub_request(:get, redirect_url).to_return File.new("spec/responses/basic_redirection.http") } + + it "follows up to HTTP_REDIRECT_LIMIT redirections then raises a Addic7ed::DownloadError" do + expect(described_class).to receive(:new).exactly(described_class.const_get(:HTTP_REDIRECT_LIMIT) + 1).times.and_call_original + expect{ subject.call }.to raise_error Addic7ed::DownloadError + end + end + end + + context "when a network error occurs" do + let!(:http_stub) { stub_request(:get, url).to_timeout } + + it "raises Addic7ed::DownloadError" do + expect{ subject.call }.to raise_error Addic7ed::DownloadError + end + end + end + + describe "#write(content)" do + it "creates a new file on disk" do + expect(Kernel).to receive(:open).with(filename, "w") + subject.send(:write, "some content") + end + + context "when an error occurs" do + before { allow(Kernel).to receive(:open).and_raise(IOError) } + + it "raises a Addic7ed::DownloadError error" do + expect{ subject.send(:write, "some content") }.to raise_error Addic7ed::DownloadError + end + end + end +end From 79a4050fa2a4ad1f53b5b547ba487ea630469ae4 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Wed, 15 Jun 2016 08:50:06 +0400 Subject: [PATCH 02/89] Small refactors and renamings in Addic7ed::Episode --- lib/addic7ed/episode.rb | 26 +++++++++++--------------- lib/addic7ed/parser.rb | 2 +- spec/lib/addic7ed/episode_spec.rb | 12 ++++++------ 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lib/addic7ed/episode.rb b/lib/addic7ed/episode.rb index aa95d4a..6cbe5ec 100644 --- a/lib/addic7ed/episode.rb +++ b/lib/addic7ed/episode.rb @@ -7,47 +7,48 @@ class Episode attr_reader :video_file, :untagged def initialize(filename, untagged = false) - @video_file = Addic7ed::VideoFile.new(filename) - @untagged = untagged + @video_file = Addic7ed::VideoFile.new(filename) + @untagged = untagged + @subtitles = {} + @best_subtitle = {} + @localized_urls = {} end - def url(lang = 'fr') + def localized_url(lang = 'fr') check_language_availability(lang) - @localized_urls ||= {} @localized_urls[lang] ||= "http://www.addic7ed.com/serie/#{ShowList.url_segment_for(video_file.showname)}/#{video_file.season}/#{video_file.episode}/#{LANGUAGES[lang][:id]}" end def subtitles(lang = 'fr') check_language_availability(lang) - find_subtitles(lang) unless @subtitles and @subtitles[lang] + find_subtitles(lang) unless @subtitles && @subtitles[lang] return @subtitles[lang] end def best_subtitle(lang = 'fr', no_hi = false) check_language_availability(lang) - find_best_subtitle(lang, no_hi) unless @best_subtitle and @best_subtitle[lang] + find_best_subtitle(lang, no_hi) unless @best_subtitle && @best_subtitle[lang] return @best_subtitle[lang] end def download_best_subtitle!(lang, no_hi = false) subtitle_url = best_subtitle(lang, no_hi).url subtitle_filename = video_file.basename.gsub(/\.\w{3}$/, untagged ? ".srt" : ".#{lang}.srt") - referer = url(lang) + referer = localized_url(lang) SubtitleDownloader.call(subtitle_url, subtitle_filename, referer) end protected def find_subtitles(lang) - initialize_language(lang) + @subtitles[lang] ||= [] parser = Addic7ed::Parser.new(self, lang) @subtitles[lang] = parser.extract_subtitles end def find_best_subtitle(lang, no_hi = false) - @best_subtitle ||= {} subtitles(lang).each do |sub| - @best_subtitle[lang] = sub if sub.works_for?(video_file.group, no_hi) and sub.can_replace? @best_subtitle[lang] + @best_subtitle[lang] = sub if sub.works_for?(video_file.group, no_hi) && sub.can_replace?(@best_subtitle[lang]) end raise NoSubtitleFound unless @best_subtitle[lang] end @@ -55,10 +56,5 @@ def find_best_subtitle(lang, no_hi = false) def check_language_availability(lang) raise LanguageNotSupported unless LANGUAGES[lang] end - - def initialize_language(lang) - @subtitles ||= {} - @subtitles[lang] ||= [] - end end end diff --git a/lib/addic7ed/parser.rb b/lib/addic7ed/parser.rb index bb39881..78ae3b2 100644 --- a/lib/addic7ed/parser.rb +++ b/lib/addic7ed/parser.rb @@ -20,7 +20,7 @@ def extract_subtitles protected def subtitles_page_dom - uri = URI(@episode.url(@lang)) + uri = URI(@episode.localized_url(@lang)) response = Net::HTTP.start(uri.hostname, uri.port) do |http| request = Net::HTTP::Get.new(uri.request_uri) request["User-Agent"] = USER_AGENTS.sample diff --git a/spec/lib/addic7ed/episode_spec.rb b/spec/lib/addic7ed/episode_spec.rb index 03faa89..b4b8611 100644 --- a/spec/lib/addic7ed/episode_spec.rb +++ b/spec/lib/addic7ed/episode_spec.rb @@ -15,18 +15,18 @@ expect{ Addic7ed::Episode.new(@filename) }.to_not raise_error end - describe '#url' do + describe '#localized_url' do it 'returns a show localized URL given existing episode' do - expect(@episode.url('fr')).to eq 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8' - expect(@episode.url('es')).to eq 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4' + expect(@episode.localized_url('fr')).to eq 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8' + expect(@episode.localized_url('es')).to eq 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4' end it 'uses French as default language' do - expect(@episode.url).to eq @episode.url('fr') + expect(@episode.localized_url).to eq @episode.localized_url('fr') end it 'raises LanguageNotSupported given an unsupported language code' do - expect{ @episode.url('aa') }.to raise_error Addic7ed::LanguageNotSupported + expect{ @episode.localized_url('aa') }.to raise_error Addic7ed::LanguageNotSupported end end @@ -117,7 +117,7 @@ end it "calls SubtitleDownloader with episode's page as referer" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), anything(), episode.url) + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), anything(), episode.localized_url) subject end From 4198f2a21e58f89cc8cbc87d5e8d81265efe6d47 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 05:43:05 +0400 Subject: [PATCH 03/89] Rename Addic7edVersionNormalizer and Addic7edCommentNormalizer to VersionNormalizer and CommentNormalizer --- lib/addic7ed.rb | 4 ++-- .../{addic7ed_comment_normalizer.rb => comment_normalizer.rb} | 2 +- .../{addic7ed_version_normalizer.rb => version_normalizer.rb} | 2 +- lib/addic7ed/subtitle.rb | 4 ++-- ..._comment_normalizer_spec.rb => comment_normalizer_spec.rb} | 2 +- ..._version_normalizer_spec.rb => version_normalizer_spec.rb} | 2 +- spec/lib/addic7ed/subtitle_spec.rb | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) rename lib/addic7ed/services/{addic7ed_comment_normalizer.rb => comment_normalizer.rb} (87%) rename lib/addic7ed/services/{addic7ed_version_normalizer.rb => version_normalizer.rb} (92%) rename spec/lib/addic7ed/services/{addic7ed_comment_normalizer_spec.rb => comment_normalizer_spec.rb} (82%) rename spec/lib/addic7ed/services/{addic7ed_version_normalizer_spec.rb => version_normalizer_spec.rb} (97%) diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index 381fd1e..57d086e 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -1,7 +1,7 @@ require 'addic7ed/version' require 'addic7ed/common' -require 'addic7ed/services/addic7ed_version_normalizer' -require 'addic7ed/services/addic7ed_comment_normalizer' +require 'addic7ed/services/version_normalizer' +require 'addic7ed/services/comment_normalizer' require 'addic7ed/services/subtitle_downloader' require 'addic7ed/errors' require 'addic7ed/show_list' diff --git a/lib/addic7ed/services/addic7ed_comment_normalizer.rb b/lib/addic7ed/services/comment_normalizer.rb similarity index 87% rename from lib/addic7ed/services/addic7ed_comment_normalizer.rb rename to lib/addic7ed/services/comment_normalizer.rb index 34b2f89..e2222c5 100644 --- a/lib/addic7ed/services/addic7ed_comment_normalizer.rb +++ b/lib/addic7ed/services/comment_normalizer.rb @@ -1,5 +1,5 @@ module Addic7ed - class Addic7edCommentNormalizer + class CommentNormalizer attr_reader :comment def initialize(comment) diff --git a/lib/addic7ed/services/addic7ed_version_normalizer.rb b/lib/addic7ed/services/version_normalizer.rb similarity index 92% rename from lib/addic7ed/services/addic7ed_version_normalizer.rb rename to lib/addic7ed/services/version_normalizer.rb index 4744a82..c8f7aa0 100644 --- a/lib/addic7ed/services/addic7ed_version_normalizer.rb +++ b/lib/addic7ed/services/version_normalizer.rb @@ -1,5 +1,5 @@ module Addic7ed - class Addic7edVersionNormalizer + class VersionNormalizer attr_reader :version def initialize(version) diff --git a/lib/addic7ed/subtitle.rb b/lib/addic7ed/subtitle.rb index f888cfc..301bdeb 100644 --- a/lib/addic7ed/subtitle.rb +++ b/lib/addic7ed/subtitle.rb @@ -5,14 +5,14 @@ class Subtitle attr_accessor :url def initialize(options = {}) - @version = Addic7edVersionNormalizer.call(options[:version]) + @version = VersionNormalizer.call(options[:version]) @language = options[:language] @status = options[:status] @url = options[:url] @via = options[:via] @hi = options[:hi] @downloads = options[:downloads].to_i || 0 - @comment = Addic7edCommentNormalizer.call(options[:comment]) + @comment = CommentNormalizer.call(options[:comment]) end def to_s diff --git a/spec/lib/addic7ed/services/addic7ed_comment_normalizer_spec.rb b/spec/lib/addic7ed/services/comment_normalizer_spec.rb similarity index 82% rename from spec/lib/addic7ed/services/addic7ed_comment_normalizer_spec.rb rename to spec/lib/addic7ed/services/comment_normalizer_spec.rb index a186728..09d679f 100644 --- a/spec/lib/addic7ed/services/addic7ed_comment_normalizer_spec.rb +++ b/spec/lib/addic7ed/services/comment_normalizer_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" require "./lib/addic7ed" -describe Addic7ed::Addic7edCommentNormalizer do +describe Addic7ed::CommentNormalizer do def normalized_comment(comment) described_class.call(comment) end diff --git a/spec/lib/addic7ed/services/addic7ed_version_normalizer_spec.rb b/spec/lib/addic7ed/services/version_normalizer_spec.rb similarity index 97% rename from spec/lib/addic7ed/services/addic7ed_version_normalizer_spec.rb rename to spec/lib/addic7ed/services/version_normalizer_spec.rb index 06174b5..9650daa 100644 --- a/spec/lib/addic7ed/services/addic7ed_version_normalizer_spec.rb +++ b/spec/lib/addic7ed/services/version_normalizer_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" require "./lib/addic7ed" -describe Addic7ed::Addic7edVersionNormalizer do +describe Addic7ed::VersionNormalizer do def normalized_version(version) described_class.call(version) end diff --git a/spec/lib/addic7ed/subtitle_spec.rb b/spec/lib/addic7ed/subtitle_spec.rb index 187c914..5495081 100644 --- a/spec/lib/addic7ed/subtitle_spec.rb +++ b/spec/lib/addic7ed/subtitle_spec.rb @@ -8,12 +8,12 @@ subject { described_class.new(version: version, comment: comment) } it "normalizes Addic7ed version" do - expect(Addic7ed::Addic7edVersionNormalizer).to receive(:call).with(version) + expect(Addic7ed::VersionNormalizer).to receive(:call).with(version) subject end it "normalizes Addic7ed comment" do - expect(Addic7ed::Addic7edCommentNormalizer).to receive(:call).with(comment) + expect(Addic7ed::CommentNormalizer).to receive(:call).with(comment) subject end end From 8fed8412ba2132573fe39e90f5fc58ee098a43e7 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 05:45:42 +0400 Subject: [PATCH 04/89] Refactor the parser into 2 service objects --- lib/addic7ed.rb | 3 +- lib/addic7ed/parser.rb | 105 ------------------ lib/addic7ed/services/page_parser.rb | 46 ++++++++ lib/addic7ed/services/subtitle_parser.rb | 78 +++++++++++++ .../lib/addic7ed/services/page_parser_spec.rb | 5 + .../addic7ed/services/subtitle_parser_spec.rb | 5 + 6 files changed, 136 insertions(+), 106 deletions(-) delete mode 100644 lib/addic7ed/parser.rb create mode 100644 lib/addic7ed/services/page_parser.rb create mode 100644 lib/addic7ed/services/subtitle_parser.rb create mode 100644 spec/lib/addic7ed/services/page_parser_spec.rb create mode 100644 spec/lib/addic7ed/services/subtitle_parser_spec.rb diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index 57d086e..62b9dd7 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -2,10 +2,11 @@ require 'addic7ed/common' require 'addic7ed/services/version_normalizer' require 'addic7ed/services/comment_normalizer' +require 'addic7ed/services/subtitle_parser' +require 'addic7ed/services/page_parser' require 'addic7ed/services/subtitle_downloader' require 'addic7ed/errors' require 'addic7ed/show_list' require 'addic7ed/video_file' require 'addic7ed/episode' require 'addic7ed/subtitle' -require 'addic7ed/parser' diff --git a/lib/addic7ed/parser.rb b/lib/addic7ed/parser.rb deleted file mode 100644 index 78ae3b2..0000000 --- a/lib/addic7ed/parser.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'nokogiri' -require 'net/http' -require 'open-uri' - -module Addic7ed - class Parser - - def initialize(episode, lang) - @episode, @lang = episode, lang - @subtitles = [] - end - - def extract_subtitles - @dom = subtitles_page_dom - check_subtitles_presence - parse_subtitle_nodes_list - @subtitles - end - - protected - - def subtitles_page_dom - uri = URI(@episode.localized_url(@lang)) - response = Net::HTTP.start(uri.hostname, uri.port) do |http| - request = Net::HTTP::Get.new(uri.request_uri) - request["User-Agent"] = USER_AGENTS.sample - http.request(request) - end - raise EpisodeNotFound unless response.body - Nokogiri::HTML(response.body) - end - - def check_subtitles_presence - raise NoSubtitleFound unless @dom.css('select#filterlang ~ font[color="yellow"]').empty? - end - - def parse_subtitle_nodes_list - sublist_node = @dom.css('#container95m table.tabel95 table.tabel95') - raise NoSubtitleFound if sublist_node.size == 0 - sublist_node.each do |sub_node| - @subtitles << parse_subtitle_node(sub_node) - end - end - - def parse_subtitle_node(sub_node) - Addic7ed::Subtitle.new( - version: extract_version(sub_node), - language: extract_language(sub_node), - status: extract_status(sub_node), - url: extract_url(sub_node), - source: extract_source(sub_node), - hi: extract_hi(sub_node), - downloads: extract_downloads(sub_node), - comment: extract_comment(sub_node) - ) - end - - def extract_version(sub_node) - version_node = sub_node.css('.NewsTitle').first - raise Addic7ed::ParsingError unless version_node - version_node.content - end - - def extract_language(sub_node) - language_node = sub_node.css('.language').first - raise Addic7ed::ParsingError unless language_node - language_node.content.gsub(/\A\W*/, '').gsub(/[^\w\)]*\z/, '') - end - - def extract_status(sub_node) - status_node = sub_node.css('tr:nth-child(3) td:nth-child(4) b').first - raise Addic7ed::ParsingError unless status_node - status_node.content.strip - end - - def extract_url(sub_node) - url_node = sub_node.css('a.buttonDownload').last - raise Addic7ed::ParsingError unless url_node - 'http://www.addic7ed.com' + url_node['href'] - end - - def extract_source(sub_node) - source_node = sub_node.css('tr:nth-child(3) td:first-child a').first - source_node['href'] if source_node - end - - def extract_hi(sub_node) - hi_node = sub_node.css('tr:nth-child(4) td.newsDate').children[1] - raise Addic7ed::ParsingError unless hi_node - !hi_node.attribute("title").nil? - end - - def extract_downloads(sub_node) - downloads_node = sub_node.css('tr:nth-child(4) td.newsDate').first - raise Addic7ed::ParsingError unless downloads_node - /(?\d*) Downloads/.match(downloads_node.content)[:downloads] - end - - def extract_comment(sub_node) - comment_node = sub_node.css('tr:nth-child(2) td.newsDate').first - raise Addic7ed::ParsingError unless comment_node - comment_node.content.gsub(/]+\>/i, "") - end - end -end diff --git a/lib/addic7ed/services/page_parser.rb b/lib/addic7ed/services/page_parser.rb new file mode 100644 index 0000000..bc5d4fb --- /dev/null +++ b/lib/addic7ed/services/page_parser.rb @@ -0,0 +1,46 @@ +require 'nokogiri' +require 'net/http' +require 'open-uri' + +module Addic7ed + class PageParser + + attr_reader :uri + + def initialize(url) + @uri = URI(url) + end + + def self.call(url) + new(url).call + end + + def call + check_subtitles_presence! + subtitles_nodes.map { |subtitle_node| Addic7ed::SubtitleParser.call(subtitle_node) } + end + + private + + def page_dom + raise EpisodeNotFound if server_response.body.nil? || server_response.body.empty? + @page_dom ||= Nokogiri::HTML(server_response.body) + end + + def subtitles_nodes + @subtitles_nodes ||= page_dom.css('#container95m table.tabel95 table.tabel95') + end + + def server_response + @server_response ||= Net::HTTP.start(uri.hostname, uri.port) do |http| + request = Net::HTTP::Get.new(uri.request_uri) + request["User-Agent"] = USER_AGENTS.sample + http.request(request) + end + end + + def check_subtitles_presence! + raise NoSubtitleFound unless page_dom.css('select#filterlang ~ font[color="yellow"]').empty? && subtitles_nodes.size > 0 + end + end +end diff --git a/lib/addic7ed/services/subtitle_parser.rb b/lib/addic7ed/services/subtitle_parser.rb new file mode 100644 index 0000000..bf9e623 --- /dev/null +++ b/lib/addic7ed/services/subtitle_parser.rb @@ -0,0 +1,78 @@ +require 'nokogiri' + +module Addic7ed + class SubtitleParser + + attr_reader :subtitle_node + + def initialize(subtitle_node) + @subtitle_node = subtitle_node + end + + def self.call(subtitle_node) + new(subtitle_node).call + end + + def call + Addic7ed::Subtitle.new( + version: extract_version(subtitle_node), + language: extract_language(subtitle_node), + status: extract_status(subtitle_node), + url: extract_url(subtitle_node), + source: extract_source(subtitle_node), + hi: extract_hi(subtitle_node), + downloads: extract_downloads(subtitle_node), + comment: extract_comment(subtitle_node) + ) + end + + private + + def extract_version(subtitle_node) + version_node = subtitle_node.css('.NewsTitle').first + raise Addic7ed::ParsingError unless version_node + version_node.content + end + + def extract_language(subtitle_node) + language_node = subtitle_node.css('.language').first + raise Addic7ed::ParsingError unless language_node + language_node.content.gsub(/\A\W*/, '').gsub(/[^\w\)]*\z/, '') + end + + def extract_status(subtitle_node) + status_node = subtitle_node.css('tr:nth-child(3) td:nth-child(4) b').first + raise Addic7ed::ParsingError unless status_node + status_node.content.strip + end + + def extract_url(subtitle_node) + url_node = subtitle_node.css('a.buttonDownload').last + raise Addic7ed::ParsingError unless url_node + 'http://www.addic7ed.com' + url_node['href'] + end + + def extract_source(subtitle_node) + source_node = subtitle_node.css('tr:nth-child(3) td:first-child a').first + source_node['href'] if source_node + end + + def extract_hi(subtitle_node) + hi_node = subtitle_node.css('tr:nth-child(4) td.newsDate').children[1] + raise Addic7ed::ParsingError unless hi_node + !hi_node.attribute("title").nil? + end + + def extract_downloads(subtitle_node) + downloads_node = subtitle_node.css('tr:nth-child(4) td.newsDate').first + raise Addic7ed::ParsingError unless downloads_node + /(?\d*) Downloads/.match(downloads_node.content)[:downloads] + end + + def extract_comment(subtitle_node) + comment_node = subtitle_node.css('tr:nth-child(2) td.newsDate').first + raise Addic7ed::ParsingError unless comment_node + comment_node.content.gsub(/]+\>/i, "") + end + end +end diff --git a/spec/lib/addic7ed/services/page_parser_spec.rb b/spec/lib/addic7ed/services/page_parser_spec.rb new file mode 100644 index 0000000..ed0aca1 --- /dev/null +++ b/spec/lib/addic7ed/services/page_parser_spec.rb @@ -0,0 +1,5 @@ +require "spec_helper" + +describe Addic7ed::PageParser do + it "should be tested" +end diff --git a/spec/lib/addic7ed/services/subtitle_parser_spec.rb b/spec/lib/addic7ed/services/subtitle_parser_spec.rb new file mode 100644 index 0000000..2f7b011 --- /dev/null +++ b/spec/lib/addic7ed/services/subtitle_parser_spec.rb @@ -0,0 +1,5 @@ +require "spec_helper" + +describe Addic7ed::SubtitleParser do + it "should be tested" +end From 3cef49893bacac8ce66ce01a7131e03bf34194ba Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 05:47:14 +0400 Subject: [PATCH 05/89] Refactor how Episode initializes and holds subtitles --- README.md | 2 +- lib/addic7ed/episode.rb | 46 +++--- spec/lib/addic7ed/episode_spec.rb | 227 ++++++++++++++++-------------- 3 files changed, 141 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index d02aacd..52f9c3c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Ruby command-line script to fetch subtitles on Addic7ed * [x] move download logic to a service object * [ ] move compatibility logic to a service object * [ ] move best subtitle logic to a service object -* [ ] refactor how `Episode` holds `Subtitle`s +* [x] refactor how `Episode` holds `Subtitle`s * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") * [ ] write documentation diff --git a/lib/addic7ed/episode.rb b/lib/addic7ed/episode.rb index 6cbe5ec..fa92ddc 100644 --- a/lib/addic7ed/episode.rb +++ b/lib/addic7ed/episode.rb @@ -7,43 +7,31 @@ class Episode attr_reader :video_file, :untagged def initialize(filename, untagged = false) - @video_file = Addic7ed::VideoFile.new(filename) - @untagged = untagged - @subtitles = {} - @best_subtitle = {} - @localized_urls = {} + @video_file = Addic7ed::VideoFile.new(filename) + @untagged = untagged + @subtitles = languages_hash { |code, _| {code => nil} } + @best_subtitle = languages_hash { |code, _| {code => nil} } end - def localized_url(lang = 'fr') - check_language_availability(lang) - @localized_urls[lang] ||= "http://www.addic7ed.com/serie/#{ShowList.url_segment_for(video_file.showname)}/#{video_file.season}/#{video_file.episode}/#{LANGUAGES[lang][:id]}" + def subtitles(lang = "en") + @subtitles[lang] ||= Addic7ed::PageParser.call(localized_urls[lang]) end - def subtitles(lang = 'fr') - check_language_availability(lang) - find_subtitles(lang) unless @subtitles && @subtitles[lang] - return @subtitles[lang] - end - - def best_subtitle(lang = 'fr', no_hi = false) - check_language_availability(lang) - find_best_subtitle(lang, no_hi) unless @best_subtitle && @best_subtitle[lang] - return @best_subtitle[lang] + def best_subtitle(lang = "en", no_hi = false) + @best_subtitle[lang] ||= find_best_subtitle(lang, no_hi) end def download_best_subtitle!(lang, no_hi = false) subtitle_url = best_subtitle(lang, no_hi).url subtitle_filename = video_file.basename.gsub(/\.\w{3}$/, untagged ? ".srt" : ".#{lang}.srt") - referer = localized_url(lang) + referer = localized_urls[lang] SubtitleDownloader.call(subtitle_url, subtitle_filename, referer) end protected - def find_subtitles(lang) - @subtitles[lang] ||= [] - parser = Addic7ed::Parser.new(self, lang) - @subtitles[lang] = parser.extract_subtitles + def localized_urls + @localized_urls ||= languages_hash { |code, lang| {code => localized_url(lang[:id])} } end def find_best_subtitle(lang, no_hi = false) @@ -53,8 +41,16 @@ def find_best_subtitle(lang, no_hi = false) raise NoSubtitleFound unless @best_subtitle[lang] end - def check_language_availability(lang) - raise LanguageNotSupported unless LANGUAGES[lang] + def url_segment + @url_segment ||= ShowList.url_segment_for(video_file.showname) + end + + def localized_url(lang) + "http://www.addic7ed.com/serie/#{url_segment}/#{video_file.season}/#{video_file.episode}/#{lang}" + end + + def languages_hash(&block) + Hash.new { raise LanguageNotSupported }.merge(LANGUAGES.map(&block).reduce(&:merge)) end end end diff --git a/spec/lib/addic7ed/episode_spec.rb b/spec/lib/addic7ed/episode_spec.rb index b4b8611..bf145b9 100644 --- a/spec/lib/addic7ed/episode_spec.rb +++ b/spec/lib/addic7ed/episode_spec.rb @@ -1,138 +1,149 @@ -require 'spec_helper' -require './lib/addic7ed' - -describe Addic7ed::Episode do - before :all do - @filename = 'The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv' - @filename_show_not_found = 'Show.Not.Found.S03E02.720p.HDTV.x264-EVOLVE.mkv' - @filename_episode_not_found = 'The.Walking.Dead.S03E42.720p.HDTV.x264-EVOLVE.mkv' - @filename_compatible_group = 'The.Walking.Dead.S03E04.HDTV.XviD-ASAP.mkv' - @filename_no_hi = 'The.Walking.Dead.S03E02.720p.HDTV.x264-KILLERS.mkv' - @episode = Addic7ed::Episode.new(@filename) - end +require "spec_helper" +require "./lib/addic7ed" - it 'should create valid instance given valid argument' do - expect{ Addic7ed::Episode.new(@filename) }.to_not raise_error - end +describe Addic7ed::Episode, "#localized_urls" do + let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } + let(:episode) { described_class.new(filename) } - describe '#localized_url' do - it 'returns a show localized URL given existing episode' do - expect(@episode.localized_url('fr')).to eq 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8' - expect(@episode.localized_url('es')).to eq 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4' - end + subject { episode.send(:localized_urls) } - it 'uses French as default language' do - expect(@episode.localized_url).to eq @episode.localized_url('fr') - end + it "returns a hash of episode page localized URL" do + expect(subject["fr"]).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" + expect(subject["es"]).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4" + end - it 'raises LanguageNotSupported given an unsupported language code' do - expect{ @episode.localized_url('aa') }.to raise_error Addic7ed::LanguageNotSupported - end + it "raises LanguageNotSupported given an unsupported language code" do + expect{ subject["aa"] }.to raise_error Addic7ed::LanguageNotSupported end +end - describe '#subtitles' do - it 'should return an array of Addic7ed::Subtitle given valid episode and language' do - %w{fr en it}.each do |lang| - lang_id = Addic7ed::LANGUAGES[lang][:id] - stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/#{lang_id}") - .to_return File.new("spec/responses/walking-dead-3-2-#{lang_id}.http") - end - expect(@episode.subtitles('fr').size).to eq 4 - expect(@episode.subtitles('en').size).to eq 3 - expect(@episode.subtitles('it').size).to eq 1 - end +describe Addic7ed::Episode, "#subtitles" do + let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } + let(:episode) { described_class.new(filename) } - it 'uses French as default language' do - expect(@episode.subtitles).to eq @episode.subtitles('fr') + before do + %w{fr en it}.each do |lang| + lang_id = Addic7ed::LANGUAGES[lang][:id] + stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/#{lang_id}") + .to_return File.new("spec/responses/walking-dead-3-2-#{lang_id}.http") end + end - it 'raises LanguageNotSupported given an unsupported language code' do - expect{ @episode.subtitles('aa') }.to raise_error Addic7ed::LanguageNotSupported - end + it "should return an array of Addic7ed::Subtitle given valid episode and language" do + expect(episode.subtitles("fr").size).to eq 4 + expect(episode.subtitles("en").size).to eq 3 + expect(episode.subtitles("it").size).to eq 1 + end - it 'raises EpisodeNotFound given not existing episode' do - stub_request(:get, 'http://www.addic7ed.com/serie/The_Walking_Dead/3/42/8') - .to_return File.new('spec/responses/walking-dead-3-42-8.http') - expect{ Addic7ed::Episode.new(@filename_episode_not_found).subtitles }.to raise_error Addic7ed::EpisodeNotFound - end + it "uses English as default language" do + expect(episode.subtitles).to eq episode.subtitles("en") + end - it 'raises NoSubtitleFound given valid episode which has no subtitle on Addic7ed' do - stub_request(:get, 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48') - .to_return File.new('spec/responses/walking-dead-3-2-48.http') - expect{ @episode.subtitles('az') }.to raise_error Addic7ed::NoSubtitleFound - end + it "raises LanguageNotSupported given an unsupported language code" do + expect{ episode.subtitles("aa") }.to raise_error Addic7ed::LanguageNotSupported + end - it 'may raise a ParsingError, but I\'m not sure how...' + it "memoizes results to minimize network requests" do + 2.times { episode.subtitles } + expect(a_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1")).to have_been_made.times(1) end - describe '#best_subtitle' do - it 'finds the subtitle with status completed and same group name' do - stub_request(:get, 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8') - .to_return File.new('spec/responses/walking-dead-3-2-8.http') - expect(@episode.best_subtitle('fr').url).to eq 'http://www.addic7ed.com/original/68018/4' - end + context "when episode does not exist" do + let(:filename) { "The.Walking.Dead.S03E42.720p.HDTV.x264-EVOLVE.mkv" } - it 'finds the subtitle with status completed, compatible group name and as many downloads as possible' do - stub_request(:get, 'http://www.addic7ed.com/serie/The_Walking_Dead/3/4/8') - .to_return File.new('spec/responses/walking-dead-3-4-8.http') - compatible_episode = Addic7ed::Episode.new(@filename_compatible_group) - expect(compatible_episode.best_subtitle('fr').url).to eq 'http://www.addic7ed.com/updated/8/68508/3' - end + before { stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/42/8").to_return File.new("spec/responses/walking-dead-3-42-8.http") } - it 'finds the subtitle with status completed, same group name and not hearing impaired' do - stub_request(:get, 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1') - .to_return File.new('spec/responses/walking-dead-3-2-1.http') - episode = Addic7ed::Episode.new(@filename_no_hi) - expect(episode.best_subtitle('en', true).url).to eq 'http://www.addic7ed.com/updated/1/68018/0' - end - - it 'uses French as default language' do - expect(@episode.best_subtitle).to eq @episode.best_subtitle('fr') + it "raises EpisodeNotFound given not existing episode" do + expect{ described_class.new(filename).subtitles("fr") }.to raise_error Addic7ed::EpisodeNotFound end + end - it 'raises LanguageNotSupported given an unsupported language code' do - expect{ @episode.best_subtitle('aa') }.to raise_error Addic7ed::LanguageNotSupported - end + context "when episode exists but has no subtitles" do + before { stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48").to_return File.new("spec/responses/walking-dead-3-2-48.http") } - it 'raises NoSubtitleFound given valid episode which has no subtitle on Addic7ed' do - stub_request(:get, 'http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48') - .to_return File.new('spec/responses/walking-dead-3-2-48.http') - expect{ @episode.best_subtitle('az') }.to raise_error Addic7ed::NoSubtitleFound + it "raises NoSubtitleFound given valid episode which has no subtitle on Addic7ed" do + expect{ episode.subtitles("az") }.to raise_error Addic7ed::NoSubtitleFound end end +end - describe '#download_best_subtitle!' do - let(:videofilename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } - let(:lang) { "fr" } - let(:episode) { Addic7ed::Episode.new(@filename) } - let(:best_subtitle) { episode.best_subtitle(lang) } - - subject { episode.download_best_subtitle!(lang) } +### +### THE FOLLOWING WILL BE MOVED TO A NEW SERVICE OBJECT AND REWRITTEN +### + +# describe Addic7ed::Episode, "#best_subtitle" do +# let(:lang) { "fr" } +# let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } +# let(:filename_no_hi) { "The.Walking.Dead.S03E02.720p.HDTV.x264-KILLERS.mkv" } +# let(:filename_compatible_group) { "The.Walking.Dead.S03E04.HDTV.XviD-ASAP.mkv" } +# let(:episode) { Addic7ed::Episode.new(filename) } + +# it "finds the subtitle with status completed and same group name" do +# stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8") +# .to_return File.new("spec/responses/walking-dead-3-2-8.http") +# expect(episode.best_subtitle("fr").url).to eq "http://www.addic7ed.com/original/68018/4" +# end + +# it "finds the subtitle with status completed, compatible group name and as many downloads as possible" do +# stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/4/8") +# .to_return File.new("spec/responses/walking-dead-3-4-8.http") +# compatible_episode = Addic7ed::Episode.new(filename_compatible_group) +# expect(compatible_episode.best_subtitle("fr").url).to eq "http://www.addic7ed.com/updated/8/68508/3" +# end + +# it "finds the subtitle with status completed, same group name and not hearing impaired" do +# stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1") +# .to_return File.new("spec/responses/walking-dead-3-2-1.http") +# episode = Addic7ed::Episode.new(filename_no_hi) +# expect(episode.best_subtitle("en", true).url).to eq "http://www.addic7ed.com/updated/1/68018/0" +# end + +# it "uses English as default language" do +# expect(episode.best_subtitle).to eq episode.best_subtitle("en") +# end + +# it "raises LanguageNotSupported given an unsupported language code" do +# expect{ episode.best_subtitle("aa") }.to raise_error Addic7ed::LanguageNotSupported +# end + +# it "raises NoSubtitleFound given valid episode which has no subtitle on Addic7ed" do +# stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48") +# .to_return File.new("spec/responses/walking-dead-3-2-48.http") +# expect{ episode.best_subtitle("az") }.to raise_error Addic7ed::NoSubtitleFound +# end +# end + +describe Addic7ed::Episode, "#download_best_subtitle!" do + let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } + let(:lang) { "fr" } + let(:episode) { described_class.new(filename) } + let(:best_subtitle) { double(:best_subtitle, url: "http://best.subtitle.url") } + + subject { episode.download_best_subtitle!(lang) } + + before { allow(episode).to receive(:best_subtitle).and_return(best_subtitle) } + + it "calls SubtitleDownloader on the best subtitle's URL" do + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(best_subtitle.url, anything(), anything()) + subject + end - before { stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8").to_return File.new("spec/responses/walking-dead-3-2-8.http") } + it "calls SubtitleDownloader with episode's page as referer" do + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), anything(), episode.send(:localized_urls)[lang]) + subject + end - it "calls SubtitleDownloader on the best subtitle's URL" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(episode.best_subtitle("fr").url, anything(), anything()) - subject - end + it "calls SubtitleDownloader with videofile's filename with language-prefixed .srt extension" do + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), File.basename(filename, ".*") + ".#{lang}.srt", anything()) + subject + end - it "calls SubtitleDownloader with episode's page as referer" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), anything(), episode.localized_url) - subject - end + context "when untagged option is set" do + let(:episode) { described_class.new(filename, true) } - it "calls SubtitleDownloader with videofile's filename with language-prefixed .srt extension" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), File.basename(videofilename, ".*") + ".#{lang}.srt", anything()) + it "calls SubtitleDownloader with videofile's filename with .srt extension" do + expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), File.basename(filename, ".*") + ".srt", anything()) subject end - - context "when untagged option is set" do - let(:episode) { Addic7ed::Episode.new(@filename, true) } - - it "calls SubtitleDownloader with videofile's filename with .srt extension" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), File.basename(videofilename, ".*") + ".srt", anything()) - subject - end - end end end From 62f0c988e2b46853a07773fe9ea27d6bca6edf0c Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 05:48:00 +0400 Subject: [PATCH 06/89] Minor: update refactor TODO list --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 52f9c3c..44f9ef9 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,16 @@ Ruby command-line script to fetch subtitles on Addic7ed ### Refactoring TODO list * [x] move download logic to a service object -* [ ] move compatibility logic to a service object +* [ ] add a new `SubtitleSearch` entry class (that holds lang and search options like hi) +* [ ] move compatibility logic to a `CheckCompatibility` service object * [ ] move best subtitle logic to a service object * [x] refactor how `Episode` holds `Subtitle`s +* [ ] rename `ShowList` and make it a service object * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") -* [ ] write documentation +* [ ] write code documentation +* [ ] Update README +* [ ] add specs for parsing * [ ] move CLI to a separate gem (and use Thor or similar) ### Is it working ? From d51a0c15ed09b504b644a456c49b23bc87630cd4 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 06:14:21 +0400 Subject: [PATCH 07/89] Rename all service objects to start with a verb --- lib/addic7ed.rb | 10 +++++----- lib/addic7ed/episode.rb | 4 ++-- ...btitle_downloader.rb => download_subtitle.rb} | 4 ++-- ...omment_normalizer.rb => normalize_comment.rb} | 2 +- ...ersion_normalizer.rb => normalize_version.rb} | 2 +- .../services/{page_parser.rb => parse_page.rb} | 4 ++-- .../{subtitle_parser.rb => parse_subtitle.rb} | 2 +- lib/addic7ed/subtitle.rb | 4 ++-- spec/lib/addic7ed/episode_spec.rb | 16 ++++++++-------- ...nloader_spec.rb => download_subtitle_spec.rb} | 2 +- ...malizer_spec.rb => normalize_comment_spec.rb} | 2 +- ...malizer_spec.rb => normalize_version_spec.rb} | 2 +- .../{page_parser_spec.rb => parse_page_spec.rb} | 2 +- ...tle_parser_spec.rb => parse_subtitle_spec.rb} | 2 +- spec/lib/addic7ed/subtitle_spec.rb | 4 ++-- 15 files changed, 31 insertions(+), 31 deletions(-) rename lib/addic7ed/services/{subtitle_downloader.rb => download_subtitle.rb} (93%) rename lib/addic7ed/services/{comment_normalizer.rb => normalize_comment.rb} (89%) rename lib/addic7ed/services/{version_normalizer.rb => normalize_version.rb} (94%) rename lib/addic7ed/services/{page_parser.rb => parse_page.rb} (90%) rename lib/addic7ed/services/{subtitle_parser.rb => parse_subtitle.rb} (99%) rename spec/lib/addic7ed/services/{subtitle_downloader_spec.rb => download_subtitle_spec.rb} (98%) rename spec/lib/addic7ed/services/{comment_normalizer_spec.rb => normalize_comment_spec.rb} (84%) rename spec/lib/addic7ed/services/{version_normalizer_spec.rb => normalize_version_spec.rb} (98%) rename spec/lib/addic7ed/services/{page_parser_spec.rb => parse_page_spec.rb} (60%) rename spec/lib/addic7ed/services/{subtitle_parser_spec.rb => parse_subtitle_spec.rb} (57%) diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index 62b9dd7..2868288 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -1,10 +1,10 @@ require 'addic7ed/version' require 'addic7ed/common' -require 'addic7ed/services/version_normalizer' -require 'addic7ed/services/comment_normalizer' -require 'addic7ed/services/subtitle_parser' -require 'addic7ed/services/page_parser' -require 'addic7ed/services/subtitle_downloader' +require 'addic7ed/services/normalize_version' +require 'addic7ed/services/normalize_comment' +require 'addic7ed/services/parse_subtitle' +require 'addic7ed/services/parse_page' +require 'addic7ed/services/download_subtitle' require 'addic7ed/errors' require 'addic7ed/show_list' require 'addic7ed/video_file' diff --git a/lib/addic7ed/episode.rb b/lib/addic7ed/episode.rb index fa92ddc..f1b50c1 100644 --- a/lib/addic7ed/episode.rb +++ b/lib/addic7ed/episode.rb @@ -14,7 +14,7 @@ def initialize(filename, untagged = false) end def subtitles(lang = "en") - @subtitles[lang] ||= Addic7ed::PageParser.call(localized_urls[lang]) + @subtitles[lang] ||= Addic7ed::ParsePage.call(localized_urls[lang]) end def best_subtitle(lang = "en", no_hi = false) @@ -25,7 +25,7 @@ def download_best_subtitle!(lang, no_hi = false) subtitle_url = best_subtitle(lang, no_hi).url subtitle_filename = video_file.basename.gsub(/\.\w{3}$/, untagged ? ".srt" : ".#{lang}.srt") referer = localized_urls[lang] - SubtitleDownloader.call(subtitle_url, subtitle_filename, referer) + DownloadSubtitle.call(subtitle_url, subtitle_filename, referer) end protected diff --git a/lib/addic7ed/services/subtitle_downloader.rb b/lib/addic7ed/services/download_subtitle.rb similarity index 93% rename from lib/addic7ed/services/subtitle_downloader.rb rename to lib/addic7ed/services/download_subtitle.rb index 8e785f0..ec757aa 100644 --- a/lib/addic7ed/services/subtitle_downloader.rb +++ b/lib/addic7ed/services/download_subtitle.rb @@ -1,5 +1,5 @@ module Addic7ed - class SubtitleDownloader + class DownloadSubtitle HTTP_REDIRECT_LIMIT = 8 attr_reader :url, :filename, :referer, :http_redirect_count @@ -20,7 +20,7 @@ def call raise DailyLimitExceeded.new("Daily limit exceeded") if /^\/downloadexceeded.php/.match url if response.kind_of? Net::HTTPRedirection new_url = URI.escape(response["location"]) # Addic7ed is serving redirection URL not-encoded, but Ruby does not support it (see http://bugs.ruby-lang.org/issues/7396) - SubtitleDownloader.call(new_url, filename, url, http_redirect_count + 1) + DownloadSubtitle.call(new_url, filename, url, http_redirect_count + 1) else write(response.body) end diff --git a/lib/addic7ed/services/comment_normalizer.rb b/lib/addic7ed/services/normalize_comment.rb similarity index 89% rename from lib/addic7ed/services/comment_normalizer.rb rename to lib/addic7ed/services/normalize_comment.rb index e2222c5..f8a9878 100644 --- a/lib/addic7ed/services/comment_normalizer.rb +++ b/lib/addic7ed/services/normalize_comment.rb @@ -1,5 +1,5 @@ module Addic7ed - class CommentNormalizer + class NormalizeComment attr_reader :comment def initialize(comment) diff --git a/lib/addic7ed/services/version_normalizer.rb b/lib/addic7ed/services/normalize_version.rb similarity index 94% rename from lib/addic7ed/services/version_normalizer.rb rename to lib/addic7ed/services/normalize_version.rb index c8f7aa0..f9ed7ea 100644 --- a/lib/addic7ed/services/version_normalizer.rb +++ b/lib/addic7ed/services/normalize_version.rb @@ -1,5 +1,5 @@ module Addic7ed - class VersionNormalizer + class NormalizeVersion attr_reader :version def initialize(version) diff --git a/lib/addic7ed/services/page_parser.rb b/lib/addic7ed/services/parse_page.rb similarity index 90% rename from lib/addic7ed/services/page_parser.rb rename to lib/addic7ed/services/parse_page.rb index bc5d4fb..b610e87 100644 --- a/lib/addic7ed/services/page_parser.rb +++ b/lib/addic7ed/services/parse_page.rb @@ -3,7 +3,7 @@ require 'open-uri' module Addic7ed - class PageParser + class ParsePage attr_reader :uri @@ -17,7 +17,7 @@ def self.call(url) def call check_subtitles_presence! - subtitles_nodes.map { |subtitle_node| Addic7ed::SubtitleParser.call(subtitle_node) } + subtitles_nodes.map { |subtitle_node| Addic7ed::ParseSubtitle.call(subtitle_node) } end private diff --git a/lib/addic7ed/services/subtitle_parser.rb b/lib/addic7ed/services/parse_subtitle.rb similarity index 99% rename from lib/addic7ed/services/subtitle_parser.rb rename to lib/addic7ed/services/parse_subtitle.rb index bf9e623..0f722f9 100644 --- a/lib/addic7ed/services/subtitle_parser.rb +++ b/lib/addic7ed/services/parse_subtitle.rb @@ -1,7 +1,7 @@ require 'nokogiri' module Addic7ed - class SubtitleParser + class ParseSubtitle attr_reader :subtitle_node diff --git a/lib/addic7ed/subtitle.rb b/lib/addic7ed/subtitle.rb index 301bdeb..7ec853c 100644 --- a/lib/addic7ed/subtitle.rb +++ b/lib/addic7ed/subtitle.rb @@ -5,14 +5,14 @@ class Subtitle attr_accessor :url def initialize(options = {}) - @version = VersionNormalizer.call(options[:version]) + @version = NormalizeVersion.call(options[:version]) @language = options[:language] @status = options[:status] @url = options[:url] @via = options[:via] @hi = options[:hi] @downloads = options[:downloads].to_i || 0 - @comment = CommentNormalizer.call(options[:comment]) + @comment = NormalizeComment.call(options[:comment]) end def to_s diff --git a/spec/lib/addic7ed/episode_spec.rb b/spec/lib/addic7ed/episode_spec.rb index bf145b9..1d0ae8c 100644 --- a/spec/lib/addic7ed/episode_spec.rb +++ b/spec/lib/addic7ed/episode_spec.rb @@ -123,26 +123,26 @@ before { allow(episode).to receive(:best_subtitle).and_return(best_subtitle) } - it "calls SubtitleDownloader on the best subtitle's URL" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(best_subtitle.url, anything(), anything()) + it "calls DownloadSubtitle on the best subtitle's URL" do + expect(Addic7ed::DownloadSubtitle).to receive(:call).with(best_subtitle.url, anything(), anything()) subject end - it "calls SubtitleDownloader with episode's page as referer" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), anything(), episode.send(:localized_urls)[lang]) + it "calls DownloadSubtitle with episode's page as referer" do + expect(Addic7ed::DownloadSubtitle).to receive(:call).with(anything(), anything(), episode.send(:localized_urls)[lang]) subject end - it "calls SubtitleDownloader with videofile's filename with language-prefixed .srt extension" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), File.basename(filename, ".*") + ".#{lang}.srt", anything()) + it "calls DownloadSubtitle with videofile's filename with language-prefixed .srt extension" do + expect(Addic7ed::DownloadSubtitle).to receive(:call).with(anything(), File.basename(filename, ".*") + ".#{lang}.srt", anything()) subject end context "when untagged option is set" do let(:episode) { described_class.new(filename, true) } - it "calls SubtitleDownloader with videofile's filename with .srt extension" do - expect(Addic7ed::SubtitleDownloader).to receive(:call).with(anything(), File.basename(filename, ".*") + ".srt", anything()) + it "calls DownloadSubtitle with videofile's filename with .srt extension" do + expect(Addic7ed::DownloadSubtitle).to receive(:call).with(anything(), File.basename(filename, ".*") + ".srt", anything()) subject end end diff --git a/spec/lib/addic7ed/services/subtitle_downloader_spec.rb b/spec/lib/addic7ed/services/download_subtitle_spec.rb similarity index 98% rename from spec/lib/addic7ed/services/subtitle_downloader_spec.rb rename to spec/lib/addic7ed/services/download_subtitle_spec.rb index 6dc0b30..d42a6ed 100644 --- a/spec/lib/addic7ed/services/subtitle_downloader_spec.rb +++ b/spec/lib/addic7ed/services/download_subtitle_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" require "./lib/addic7ed" -describe Addic7ed::SubtitleDownloader do +describe Addic7ed::DownloadSubtitle do let(:url) { "http://www.addic7ed.com/original/68018/4" } let(:referer) { "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" } let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.fr.srt" } diff --git a/spec/lib/addic7ed/services/comment_normalizer_spec.rb b/spec/lib/addic7ed/services/normalize_comment_spec.rb similarity index 84% rename from spec/lib/addic7ed/services/comment_normalizer_spec.rb rename to spec/lib/addic7ed/services/normalize_comment_spec.rb index 09d679f..32d7d60 100644 --- a/spec/lib/addic7ed/services/comment_normalizer_spec.rb +++ b/spec/lib/addic7ed/services/normalize_comment_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" require "./lib/addic7ed" -describe Addic7ed::CommentNormalizer do +describe Addic7ed::NormalizeComment do def normalized_comment(comment) described_class.call(comment) end diff --git a/spec/lib/addic7ed/services/version_normalizer_spec.rb b/spec/lib/addic7ed/services/normalize_version_spec.rb similarity index 98% rename from spec/lib/addic7ed/services/version_normalizer_spec.rb rename to spec/lib/addic7ed/services/normalize_version_spec.rb index 9650daa..92cae39 100644 --- a/spec/lib/addic7ed/services/version_normalizer_spec.rb +++ b/spec/lib/addic7ed/services/normalize_version_spec.rb @@ -1,7 +1,7 @@ require "spec_helper" require "./lib/addic7ed" -describe Addic7ed::VersionNormalizer do +describe Addic7ed::NormalizeVersion do def normalized_version(version) described_class.call(version) end diff --git a/spec/lib/addic7ed/services/page_parser_spec.rb b/spec/lib/addic7ed/services/parse_page_spec.rb similarity index 60% rename from spec/lib/addic7ed/services/page_parser_spec.rb rename to spec/lib/addic7ed/services/parse_page_spec.rb index ed0aca1..2a68df5 100644 --- a/spec/lib/addic7ed/services/page_parser_spec.rb +++ b/spec/lib/addic7ed/services/parse_page_spec.rb @@ -1,5 +1,5 @@ require "spec_helper" -describe Addic7ed::PageParser do +describe Addic7ed::ParsePage do it "should be tested" end diff --git a/spec/lib/addic7ed/services/subtitle_parser_spec.rb b/spec/lib/addic7ed/services/parse_subtitle_spec.rb similarity index 57% rename from spec/lib/addic7ed/services/subtitle_parser_spec.rb rename to spec/lib/addic7ed/services/parse_subtitle_spec.rb index 2f7b011..f7a0847 100644 --- a/spec/lib/addic7ed/services/subtitle_parser_spec.rb +++ b/spec/lib/addic7ed/services/parse_subtitle_spec.rb @@ -1,5 +1,5 @@ require "spec_helper" -describe Addic7ed::SubtitleParser do +describe Addic7ed::ParseSubtitle do it "should be tested" end diff --git a/spec/lib/addic7ed/subtitle_spec.rb b/spec/lib/addic7ed/subtitle_spec.rb index 5495081..dcdccc3 100644 --- a/spec/lib/addic7ed/subtitle_spec.rb +++ b/spec/lib/addic7ed/subtitle_spec.rb @@ -8,12 +8,12 @@ subject { described_class.new(version: version, comment: comment) } it "normalizes Addic7ed version" do - expect(Addic7ed::VersionNormalizer).to receive(:call).with(version) + expect(Addic7ed::NormalizeVersion).to receive(:call).with(version) subject end it "normalizes Addic7ed comment" do - expect(Addic7ed::CommentNormalizer).to receive(:call).with(comment) + expect(Addic7ed::NormalizeComment).to receive(:call).with(comment) subject end end From 1febbd6df29c20524e323890f67152611a45b095 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 06:29:49 +0400 Subject: [PATCH 08/89] Extract services class method to a module --- lib/addic7ed.rb | 1 + lib/addic7ed/service.rb | 13 +++++++++++++ lib/addic7ed/services/download_subtitle.rb | 8 +++----- lib/addic7ed/services/normalize_comment.rb | 8 ++------ lib/addic7ed/services/normalize_version.rb | 8 ++------ lib/addic7ed/services/parse_page.rb | 5 +---- lib/addic7ed/services/parse_subtitle.rb | 5 +---- 7 files changed, 23 insertions(+), 25 deletions(-) create mode 100644 lib/addic7ed/service.rb diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index 2868288..a64b4e6 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -1,5 +1,6 @@ require 'addic7ed/version' require 'addic7ed/common' +require 'addic7ed/service' require 'addic7ed/services/normalize_version' require 'addic7ed/services/normalize_comment' require 'addic7ed/services/parse_subtitle' diff --git a/lib/addic7ed/service.rb b/lib/addic7ed/service.rb new file mode 100644 index 0000000..e3c5d2e --- /dev/null +++ b/lib/addic7ed/service.rb @@ -0,0 +1,13 @@ +module Addic7ed + module Service + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def call(*args) + new(*args).call + end + end + end +end diff --git a/lib/addic7ed/services/download_subtitle.rb b/lib/addic7ed/services/download_subtitle.rb index ec757aa..3916695 100644 --- a/lib/addic7ed/services/download_subtitle.rb +++ b/lib/addic7ed/services/download_subtitle.rb @@ -1,20 +1,18 @@ module Addic7ed class DownloadSubtitle + include Service + HTTP_REDIRECT_LIMIT = 8 attr_reader :url, :filename, :referer, :http_redirect_count - def initialize(url, filename, referer, http_redirect_count) + def initialize(url, filename, referer, http_redirect_count = 0) @url = url @filename = filename @referer = referer @http_redirect_count = http_redirect_count end - def self.call(url, filename, referer, http_redirect_count = 0) - new(url, filename, referer, http_redirect_count).call - end - def call raise DownloadError.new("Too many HTTP redirections") if http_redirect_count >= HTTP_REDIRECT_LIMIT raise DailyLimitExceeded.new("Daily limit exceeded") if /^\/downloadexceeded.php/.match url diff --git a/lib/addic7ed/services/normalize_comment.rb b/lib/addic7ed/services/normalize_comment.rb index f8a9878..aef53d0 100644 --- a/lib/addic7ed/services/normalize_comment.rb +++ b/lib/addic7ed/services/normalize_comment.rb @@ -1,19 +1,15 @@ module Addic7ed class NormalizeComment + include Service + attr_reader :comment def initialize(comment) @comment = comment || "" end - def self.call(comment) - new(comment).call - end - def call comment.downcase end end - -private end diff --git a/lib/addic7ed/services/normalize_version.rb b/lib/addic7ed/services/normalize_version.rb index f9ed7ea..5258c3c 100644 --- a/lib/addic7ed/services/normalize_version.rb +++ b/lib/addic7ed/services/normalize_version.rb @@ -1,15 +1,13 @@ module Addic7ed class NormalizeVersion + include Service + attr_reader :version def initialize(version) @version = version || "" end - def self.call(version) - new(version).call - end - def call version. gsub(/[[:space:]]/, ""). @@ -19,6 +17,4 @@ def call gsub(/[- \.]/, '') end end - -private end diff --git a/lib/addic7ed/services/parse_page.rb b/lib/addic7ed/services/parse_page.rb index b610e87..ef3e5cd 100644 --- a/lib/addic7ed/services/parse_page.rb +++ b/lib/addic7ed/services/parse_page.rb @@ -4,6 +4,7 @@ module Addic7ed class ParsePage + include Service attr_reader :uri @@ -11,10 +12,6 @@ def initialize(url) @uri = URI(url) end - def self.call(url) - new(url).call - end - def call check_subtitles_presence! subtitles_nodes.map { |subtitle_node| Addic7ed::ParseSubtitle.call(subtitle_node) } diff --git a/lib/addic7ed/services/parse_subtitle.rb b/lib/addic7ed/services/parse_subtitle.rb index 0f722f9..87fdc34 100644 --- a/lib/addic7ed/services/parse_subtitle.rb +++ b/lib/addic7ed/services/parse_subtitle.rb @@ -2,6 +2,7 @@ module Addic7ed class ParseSubtitle + include Service attr_reader :subtitle_node @@ -9,10 +10,6 @@ def initialize(subtitle_node) @subtitle_node = subtitle_node end - def self.call(subtitle_node) - new(subtitle_node).call - end - def call Addic7ed::Subtitle.new( version: extract_version(subtitle_node), From b7f2d7119f3066a9f5895029c3627564317f15df Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 06:33:38 +0400 Subject: [PATCH 09/89] Move models to a subdirectory --- lib/addic7ed.rb | 6 +++--- lib/addic7ed/{ => models}/episode.rb | 0 lib/addic7ed/{ => models}/subtitle.rb | 0 lib/addic7ed/{ => models}/video_file.rb | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename lib/addic7ed/{ => models}/episode.rb (100%) rename lib/addic7ed/{ => models}/subtitle.rb (100%) rename lib/addic7ed/{ => models}/video_file.rb (100%) diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index a64b4e6..97fc4dc 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -8,6 +8,6 @@ require 'addic7ed/services/download_subtitle' require 'addic7ed/errors' require 'addic7ed/show_list' -require 'addic7ed/video_file' -require 'addic7ed/episode' -require 'addic7ed/subtitle' +require 'addic7ed/models/video_file' +require 'addic7ed/models/episode' +require 'addic7ed/models/subtitle' diff --git a/lib/addic7ed/episode.rb b/lib/addic7ed/models/episode.rb similarity index 100% rename from lib/addic7ed/episode.rb rename to lib/addic7ed/models/episode.rb diff --git a/lib/addic7ed/subtitle.rb b/lib/addic7ed/models/subtitle.rb similarity index 100% rename from lib/addic7ed/subtitle.rb rename to lib/addic7ed/models/subtitle.rb diff --git a/lib/addic7ed/video_file.rb b/lib/addic7ed/models/video_file.rb similarity index 100% rename from lib/addic7ed/video_file.rb rename to lib/addic7ed/models/video_file.rb From eb1a7056e5f6acb737b8923e5254096a636e4835 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 06:35:15 +0400 Subject: [PATCH 10/89] Move models *specs* to a subdirectory (forgotten files from last commit :confused:) --- spec/lib/addic7ed/{ => models}/episode_spec.rb | 0 spec/lib/addic7ed/{ => models}/subtitle_spec.rb | 0 spec/lib/addic7ed/{ => models}/video_file_spec.rb | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename spec/lib/addic7ed/{ => models}/episode_spec.rb (100%) rename spec/lib/addic7ed/{ => models}/subtitle_spec.rb (100%) rename spec/lib/addic7ed/{ => models}/video_file_spec.rb (100%) diff --git a/spec/lib/addic7ed/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb similarity index 100% rename from spec/lib/addic7ed/episode_spec.rb rename to spec/lib/addic7ed/models/episode_spec.rb diff --git a/spec/lib/addic7ed/subtitle_spec.rb b/spec/lib/addic7ed/models/subtitle_spec.rb similarity index 100% rename from spec/lib/addic7ed/subtitle_spec.rb rename to spec/lib/addic7ed/models/subtitle_spec.rb diff --git a/spec/lib/addic7ed/video_file_spec.rb b/spec/lib/addic7ed/models/video_file_spec.rb similarity index 100% rename from spec/lib/addic7ed/video_file_spec.rb rename to spec/lib/addic7ed/models/video_file_spec.rb From 5dd58e16d5d15cf11d9cbc96292729e9199bd065 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 07:23:09 +0400 Subject: [PATCH 11/89] Refactor `ShowList` into 2 service objects (a service that downloads and memoizes the shows list and another service that parses that show list to find out how a given show name is URL-encoded by Addic7ed) --- README.md | 2 +- lib/addic7ed.rb | 3 +- lib/addic7ed/models/episode.rb | 2 +- lib/addic7ed/services/get_shows_list.rb | 19 +++++++++ .../url_encode_show_name.rb} | 31 ++++++-------- .../addic7ed/services/get_shows_list_spec.rb | 19 +++++++++ .../services/url_encode_show_name_spec.rb | 41 ++++++++++++++++++ spec/lib/addic7ed/show_list_spec.rb | 42 ------------------- 8 files changed, 95 insertions(+), 64 deletions(-) create mode 100644 lib/addic7ed/services/get_shows_list.rb rename lib/addic7ed/{show_list.rb => services/url_encode_show_name.rb} (66%) create mode 100644 spec/lib/addic7ed/services/get_shows_list_spec.rb create mode 100644 spec/lib/addic7ed/services/url_encode_show_name_spec.rb delete mode 100644 spec/lib/addic7ed/show_list_spec.rb diff --git a/README.md b/README.md index 44f9ef9..823cdaf 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Ruby command-line script to fetch subtitles on Addic7ed * [ ] move compatibility logic to a `CheckCompatibility` service object * [ ] move best subtitle logic to a service object * [x] refactor how `Episode` holds `Subtitle`s -* [ ] rename `ShowList` and make it a service object +* [x] rename `ShowList` and make it a service object * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") * [ ] write code documentation diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index 97fc4dc..dc4b921 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -1,13 +1,14 @@ require 'addic7ed/version' require 'addic7ed/common' require 'addic7ed/service' +require 'addic7ed/services/get_shows_list' require 'addic7ed/services/normalize_version' require 'addic7ed/services/normalize_comment' +require 'addic7ed/services/url_encode_show_name' require 'addic7ed/services/parse_subtitle' require 'addic7ed/services/parse_page' require 'addic7ed/services/download_subtitle' require 'addic7ed/errors' -require 'addic7ed/show_list' require 'addic7ed/models/video_file' require 'addic7ed/models/episode' require 'addic7ed/models/subtitle' diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index f1b50c1..8a1f0f6 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -42,7 +42,7 @@ def find_best_subtitle(lang, no_hi = false) end def url_segment - @url_segment ||= ShowList.url_segment_for(video_file.showname) + @url_segment ||= URLEncodeShowName.call(video_file.showname) end def localized_url(lang) diff --git a/lib/addic7ed/services/get_shows_list.rb b/lib/addic7ed/services/get_shows_list.rb new file mode 100644 index 0000000..ff0dafc --- /dev/null +++ b/lib/addic7ed/services/get_shows_list.rb @@ -0,0 +1,19 @@ +module Addic7ed + class GetShowsList + include Service + + def call + @@shows ||= Nokogiri::HTML(addic7ed_homepage.body).css("select#qsShow option:not(:first-child)").map(&:text) + end + + private + + def addic7ed_homepage + Net::HTTP.start("www.addic7ed.com") do |http| + request = Net::HTTP::Get.new("/") + request["User-Agent"] = USER_AGENTS.sample + http.request(request) + end + end + end +end diff --git a/lib/addic7ed/show_list.rb b/lib/addic7ed/services/url_encode_show_name.rb similarity index 66% rename from lib/addic7ed/show_list.rb rename to lib/addic7ed/services/url_encode_show_name.rb index ce83c58..974f10b 100644 --- a/lib/addic7ed/show_list.rb +++ b/lib/addic7ed/services/url_encode_show_name.rb @@ -1,23 +1,24 @@ module Addic7ed - class ShowList - attr_reader :raw_name + class URLEncodeShowName + include Service - def initialize(raw_name) - @raw_name = raw_name - end + attr_reader :filename - def self.url_segment_for(raw_name) - new(raw_name).url_segment_for + def initialize(filename) + @filename = filename end - def url_segment_for + # This method is unfortunately over complex because we have to compare the given show name to + # the actual Addic7ed shows list in order to find out how Addic7ed URL-encodes this show name + # (this is due to the inconsistency of their URL-encoding policy) + def call shows_matching = shows_matching_exactly shows_matching = shows_matching_without_year if shows_matching.empty? raise ShowNotFound if shows_matching.empty? shows_matching.last.gsub(' ', '_') end - private + private def shows_matching_exactly @shows_matching_exactly ||= addic7ed_shows.select{ |addic7ed_show| is_matching? addic7ed_show } @@ -40,22 +41,14 @@ def is_matching?(addic7ed_show, comparer = :default_comparer) end def humanized_name - @humanized_name ||= raw_name. + @humanized_name ||= filename. gsub(/[_\.]+/, ' '). gsub(/ (US|UK)( |$)/i, ' (\1)\2'). gsub(/ (\d{4})( |$)/i, ' (\1)\2') end def addic7ed_shows - @@addic7ed_shows ||= Nokogiri::HTML(addic7ed_homepage.body).css("select#qsShow option:not(:first-child)").map(&:text) - end - - def addic7ed_homepage - Net::HTTP.start("www.addic7ed.com") do |http| - request = Net::HTTP::Get.new("/") - request["User-Agent"] = USER_AGENTS.sample - http.request(request) - end + @addic7ed_shows ||= GetShowsList.call end end end diff --git a/spec/lib/addic7ed/services/get_shows_list_spec.rb b/spec/lib/addic7ed/services/get_shows_list_spec.rb new file mode 100644 index 0000000..5db4f26 --- /dev/null +++ b/spec/lib/addic7ed/services/get_shows_list_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +describe Addic7ed::GetShowsList do + before { described_class.class_variable_set(:@@shows, nil) } + + it "downloads Addic7ed homepage" do + subject.call + expect(a_request(:get, "http://www.addic7ed.com")).to have_been_made + end + + it "returns the list of shows names" do + expect(described_class.call).to include "Game of Thrones", "The Walking Dead", "Californication", "Breaking Bad", "Weeds" + end + + it "memoizes to minimize network requests" do + 2.times { subject.call } + expect(a_request(:get, "http://www.addic7ed.com")).to have_been_made.once + end +end diff --git a/spec/lib/addic7ed/services/url_encode_show_name_spec.rb b/spec/lib/addic7ed/services/url_encode_show_name_spec.rb new file mode 100644 index 0000000..323873b --- /dev/null +++ b/spec/lib/addic7ed/services/url_encode_show_name_spec.rb @@ -0,0 +1,41 @@ +require "spec_helper" + +describe Addic7ed::URLEncodeShowName do + it "changes all spaces to underscores" do + expect(described_class.call("The.Walking.Dead")).to eq "The_Walking_Dead" + end + + it "wraps country code with parenthesis" do + expect(described_class.call("Shameless.US")).to eq "Shameless_(US)" + expect(described_class.call("Vikings.UK")).to eq "Vikings_(UK)" + end + + it "is case-insensitive" do + expect(described_class.call("shameless.us")).to eq "Shameless_(US)" + end + + it "wraps production year with parenthesis" do + expect(described_class.call("Vikings.2013")).to eq "Vikings_(2013)" + end + + it "returns last show when production year is unspecified" do + expect(described_class.call("Empire")).to eq "Empire_(2015)" + expect(described_class.call("American.Crime")).to eq "American_Crime_(2015)" + end + + it "handles when both country code and production year are present" do + expect(described_class.call("Legacy.2013.UK")).to eq "Legacy_(2013)_(UK)" + end + + it "handles when show name contains a quote" do + expect(described_class.call("Greys.Anatomy")).to eq "Grey's_Anatomy" + end + + it "handles when a show name contains dots" do + expect(described_class.call("The.O.C.")).to eq "The_O.C." + end + + it "raises a ShowNotFound error when no matching show is found" do + expect{described_class.call("Not.an.existing.show")}.to raise_error Addic7ed::ShowNotFound + end +end diff --git a/spec/lib/addic7ed/show_list_spec.rb b/spec/lib/addic7ed/show_list_spec.rb deleted file mode 100644 index d3e5425..0000000 --- a/spec/lib/addic7ed/show_list_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -require "spec_helper" -require "./lib/addic7ed" - -describe Addic7ed::ShowList, ".url_segment_for" do - it "changes all spaces to underscores" do - expect(Addic7ed::ShowList.url_segment_for("The.Walking.Dead")).to eq "The_Walking_Dead" - end - - it "wraps country code with parenthesis" do - expect(Addic7ed::ShowList.url_segment_for("Shameless.US")).to eq "Shameless_(US)" - expect(Addic7ed::ShowList.url_segment_for("Vikings.UK")).to eq "Vikings_(UK)" - end - - it "is case-insensitive" do - expect(Addic7ed::ShowList.url_segment_for("shameless.us")).to eq "Shameless_(US)" - end - - it "wraps production year with parenthesis" do - expect(Addic7ed::ShowList.url_segment_for("Vikings.2013")).to eq "Vikings_(2013)" - end - - it "returns last show when production year is unspecified" do - expect(Addic7ed::ShowList.url_segment_for("Empire")).to eq "Empire_(2015)" - expect(Addic7ed::ShowList.url_segment_for("American.Crime")).to eq "American_Crime_(2015)" - end - - it "handles when both country code and production year are present" do - expect(Addic7ed::ShowList.url_segment_for("Legacy.2013.UK")).to eq "Legacy_(2013)_(UK)" - end - - it "handles when show name contains a quote" do - expect(Addic7ed::ShowList.url_segment_for("Greys.Anatomy")).to eq "Grey's_Anatomy" - end - - it "handles when a show name contains dots" do - expect(Addic7ed::ShowList.url_segment_for("The.O.C.")).to eq "The_O.C." - end - - it "raises a ShowNotFound error when no matching show is found" do - expect{Addic7ed::ShowList.url_segment_for("Not.an.existing.show")}.to raise_error Addic7ed::ShowNotFound - end -end From 8097d68d0b2d29d27997f318710352eb1b1220f2 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 11:55:06 +0400 Subject: [PATCH 12/89] Refactor URLEncodeShowName to be more readable --- lib/addic7ed/services/url_encode_show_name.rb | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/lib/addic7ed/services/url_encode_show_name.rb b/lib/addic7ed/services/url_encode_show_name.rb index 974f10b..0293166 100644 --- a/lib/addic7ed/services/url_encode_show_name.rb +++ b/lib/addic7ed/services/url_encode_show_name.rb @@ -8,43 +8,35 @@ def initialize(filename) @filename = filename end - # This method is unfortunately over complex because we have to compare the given show name to + # This service is unfortunately over complex because we have to compare the given show name to # the actual Addic7ed shows list in order to find out how Addic7ed URL-encodes this show name # (this is due to the inconsistency of their URL-encoding policy) def call - shows_matching = shows_matching_exactly - shows_matching = shows_matching_without_year if shows_matching.empty? - raise ShowNotFound if shows_matching.empty? - shows_matching.last.gsub(' ', '_') + matching_shows = matching_shows(ignore_year: false) + matching_shows = matching_shows(ignore_year: true) if matching_shows.empty? + raise ShowNotFound if matching_shows.empty? + matching_shows.last.gsub(' ', '_') end private - def shows_matching_exactly - @shows_matching_exactly ||= addic7ed_shows.select{ |addic7ed_show| is_matching? addic7ed_show } + def matching_shows(opts) + addic7ed_shows.select{ |show_name| is_matching?(show_name, opts) } end - def shows_matching_without_year - @shows_matching_without_year ||= addic7ed_shows.select{ |addic7ed_show| is_matching? addic7ed_show, :comparer_without_year } + def normalize(show_name, opts) + show_name + .downcase + .gsub("'", "") + .gsub(/[_\.]+/, ' ') + .gsub(/ (US|UK)( |$)/i, ' (\1)\2') + .gsub(/ (\d{4})( |$)/i, ' (\1)\2') + .strip + .tap { |show_name| show_name.gsub!(/ \(\d{4}\)( |$)/, '\1') if opts[:ignore_year] } end - def default_comparer(showname) - showname.downcase.gsub("'", "").gsub(".", " ").strip - end - - def comparer_without_year(showname) - default_comparer(showname).gsub(/ \(\d{4}\)( |$)/, '\1') - end - - def is_matching?(addic7ed_show, comparer = :default_comparer) - [humanized_name, addic7ed_show].map(&method(comparer)).reduce(:==) - end - - def humanized_name - @humanized_name ||= filename. - gsub(/[_\.]+/, ' '). - gsub(/ (US|UK)( |$)/i, ' (\1)\2'). - gsub(/ (\d{4})( |$)/i, ' (\1)\2') + def is_matching?(addic7ed_show, opts) + normalize(addic7ed_show, opts) == normalize(filename, opts) end def addic7ed_shows From 5c3bcb2546637faebad05be9dadf806b53b73d91 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 12:02:23 +0400 Subject: [PATCH 13/89] Require files more smartly --- lib/addic7ed.rb | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index dc4b921..618955b 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -1,14 +1,5 @@ -require 'addic7ed/version' -require 'addic7ed/common' -require 'addic7ed/service' -require 'addic7ed/services/get_shows_list' -require 'addic7ed/services/normalize_version' -require 'addic7ed/services/normalize_comment' -require 'addic7ed/services/url_encode_show_name' -require 'addic7ed/services/parse_subtitle' -require 'addic7ed/services/parse_page' -require 'addic7ed/services/download_subtitle' -require 'addic7ed/errors' -require 'addic7ed/models/video_file' -require 'addic7ed/models/episode' -require 'addic7ed/models/subtitle' +Dir[ + File.expand_path("lib/addic7ed/*.rb"), + File.expand_path("lib/addic7ed/services/**/*.rb"), + File.expand_path("lib/addic7ed/models/**/*.rb") +].each { |file| require file } From 72cf33dc4be346737fdebe45fa8586ccc8a6f910 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 15:05:53 +0400 Subject: [PATCH 14/89] Require the gem code in spec_helper rather than in each spec file --- spec/lib/addic7ed/common_spec.rb | 1 - spec/lib/addic7ed/models/episode_spec.rb | 1 - spec/lib/addic7ed/models/subtitle_spec.rb | 1 - spec/lib/addic7ed/models/video_file_spec.rb | 1 - spec/lib/addic7ed/services/download_subtitle_spec.rb | 1 - spec/lib/addic7ed/services/normalize_comment_spec.rb | 1 - spec/lib/addic7ed/services/normalize_version_spec.rb | 1 - spec/spec_helper.rb | 2 ++ 8 files changed, 2 insertions(+), 7 deletions(-) diff --git a/spec/lib/addic7ed/common_spec.rb b/spec/lib/addic7ed/common_spec.rb index 587fd2e..b398a12 100644 --- a/spec/lib/addic7ed/common_spec.rb +++ b/spec/lib/addic7ed/common_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require './lib/addic7ed' describe Addic7ed do it 'defines SHOWS_URL' do diff --git a/spec/lib/addic7ed/models/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb index 1d0ae8c..32e2957 100644 --- a/spec/lib/addic7ed/models/episode_spec.rb +++ b/spec/lib/addic7ed/models/episode_spec.rb @@ -1,5 +1,4 @@ require "spec_helper" -require "./lib/addic7ed" describe Addic7ed::Episode, "#localized_urls" do let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } diff --git a/spec/lib/addic7ed/models/subtitle_spec.rb b/spec/lib/addic7ed/models/subtitle_spec.rb index dcdccc3..da4b298 100644 --- a/spec/lib/addic7ed/models/subtitle_spec.rb +++ b/spec/lib/addic7ed/models/subtitle_spec.rb @@ -1,5 +1,4 @@ require "spec_helper" -require "./lib/addic7ed" describe Addic7ed::Subtitle, "#initialize" do let(:version) { "Version DIMENSiON" } diff --git a/spec/lib/addic7ed/models/video_file_spec.rb b/spec/lib/addic7ed/models/video_file_spec.rb index 4fec494..5136f71 100644 --- a/spec/lib/addic7ed/models/video_file_spec.rb +++ b/spec/lib/addic7ed/models/video_file_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require './lib/addic7ed' describe Addic7ed::VideoFile do let(:file) { Addic7ed::VideoFile.new(filename) } diff --git a/spec/lib/addic7ed/services/download_subtitle_spec.rb b/spec/lib/addic7ed/services/download_subtitle_spec.rb index d42a6ed..c1f3536 100644 --- a/spec/lib/addic7ed/services/download_subtitle_spec.rb +++ b/spec/lib/addic7ed/services/download_subtitle_spec.rb @@ -1,5 +1,4 @@ require "spec_helper" -require "./lib/addic7ed" describe Addic7ed::DownloadSubtitle do let(:url) { "http://www.addic7ed.com/original/68018/4" } diff --git a/spec/lib/addic7ed/services/normalize_comment_spec.rb b/spec/lib/addic7ed/services/normalize_comment_spec.rb index 32d7d60..55502a4 100644 --- a/spec/lib/addic7ed/services/normalize_comment_spec.rb +++ b/spec/lib/addic7ed/services/normalize_comment_spec.rb @@ -1,5 +1,4 @@ require "spec_helper" -require "./lib/addic7ed" describe Addic7ed::NormalizeComment do def normalized_comment(comment) diff --git a/spec/lib/addic7ed/services/normalize_version_spec.rb b/spec/lib/addic7ed/services/normalize_version_spec.rb index 92cae39..497f0a4 100644 --- a/spec/lib/addic7ed/services/normalize_version_spec.rb +++ b/spec/lib/addic7ed/services/normalize_version_spec.rb @@ -1,5 +1,4 @@ require "spec_helper" -require "./lib/addic7ed" describe Addic7ed::NormalizeVersion do def normalized_version(version) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 826fe01..d746635 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -24,3 +24,5 @@ stub_request(:get, "http://www.addic7ed.com").to_return(File.new("spec/responses/homepage.http")) end end + +require "./lib/addic7ed" From 387f0b9f75130f95f924825c4ffca81ed9c44c6e Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 16:40:59 +0400 Subject: [PATCH 15/89] Add a console executable because it's handy --- bin/console | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 bin/console diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..434d4c6 --- /dev/null +++ b/bin/console @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +Bundler.require(:default, ENV["RACK_ENV"] || :development) + +APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..")) + +$LOAD_PATH.unshift File.expand_path('lib', APP_ROOT) + +require "addic7ed" + +Pry.start From e3c5334a6578516577faad8a3f809f9821a276c4 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 19 Jun 2016 17:01:39 +0400 Subject: [PATCH 16/89] Allow network requests from the console --- bin/console | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/console b/bin/console index 434d4c6..ef67ca4 100755 --- a/bin/console +++ b/bin/console @@ -9,4 +9,6 @@ $LOAD_PATH.unshift File.expand_path('lib', APP_ROOT) require "addic7ed" +WebMock.allow_net_connect! + Pry.start From f6fe3ba99a8aac08d941b42d022184770ca6a53b Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 11:02:38 +0400 Subject: [PATCH 17/89] Move logic from Episode model to a new Search model to better separate concerns and responsabilities --- README.md | 3 +- lib/addic7ed/models/episode.rb | 40 +++----- lib/addic7ed/models/search.rb | 32 ++++++ spec/lib/addic7ed/models/episode_spec.rb | 124 +++++------------------ spec/lib/addic7ed/models/search_spec.rb | 83 +++++++++++++++ 5 files changed, 153 insertions(+), 129 deletions(-) create mode 100644 lib/addic7ed/models/search.rb create mode 100644 spec/lib/addic7ed/models/search_spec.rb diff --git a/README.md b/README.md index 823cdaf..a3c30bf 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,14 @@ Ruby command-line script to fetch subtitles on Addic7ed ### Refactoring TODO list * [x] move download logic to a service object -* [ ] add a new `SubtitleSearch` entry class (that holds lang and search options like hi) +* [x] add a new `Search` model * [ ] move compatibility logic to a `CheckCompatibility` service object * [ ] move best subtitle logic to a service object * [x] refactor how `Episode` holds `Subtitle`s * [x] rename `ShowList` and make it a service object * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") +* [ ] allow language to be specified both by string or by symbol * [ ] write code documentation * [ ] Update README * [ ] add specs for parsing diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index 8a1f0f6..57969cd 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -4,28 +4,21 @@ module Addic7ed class Episode - attr_reader :video_file, :untagged + attr_reader :showname, :season, :episode - def initialize(filename, untagged = false) - @video_file = Addic7ed::VideoFile.new(filename) - @untagged = untagged - @subtitles = languages_hash { |code, _| {code => nil} } - @best_subtitle = languages_hash { |code, _| {code => nil} } + def initialize(showname, season, episode) + @showname = showname + @season = season + @episode = episode + @subtitles = languages_hash { |code, _| {code => nil} } end - def subtitles(lang = "en") - @subtitles[lang] ||= Addic7ed::ParsePage.call(localized_urls[lang]) + def subtitles(lang) + @subtitles[lang] ||= Addic7ed::ParsePage.call(page_url(lang)) end - def best_subtitle(lang = "en", no_hi = false) - @best_subtitle[lang] ||= find_best_subtitle(lang, no_hi) - end - - def download_best_subtitle!(lang, no_hi = false) - subtitle_url = best_subtitle(lang, no_hi).url - subtitle_filename = video_file.basename.gsub(/\.\w{3}$/, untagged ? ".srt" : ".#{lang}.srt") - referer = localized_urls[lang] - DownloadSubtitle.call(subtitle_url, subtitle_filename, referer) + def page_url(lang) + localized_urls[lang] end protected @@ -34,19 +27,12 @@ def localized_urls @localized_urls ||= languages_hash { |code, lang| {code => localized_url(lang[:id])} } end - def find_best_subtitle(lang, no_hi = false) - subtitles(lang).each do |sub| - @best_subtitle[lang] = sub if sub.works_for?(video_file.group, no_hi) && sub.can_replace?(@best_subtitle[lang]) - end - raise NoSubtitleFound unless @best_subtitle[lang] - end - - def url_segment - @url_segment ||= URLEncodeShowName.call(video_file.showname) + def url_encoded_showname + @url_encoded_showname ||= URLEncodeShowName.call(showname) end def localized_url(lang) - "http://www.addic7ed.com/serie/#{url_segment}/#{video_file.season}/#{video_file.episode}/#{lang}" + "http://www.addic7ed.com/serie/#{url_encoded_showname}/#{season}/#{episode}/#{lang}" end def languages_hash(&block) diff --git a/lib/addic7ed/models/search.rb b/lib/addic7ed/models/search.rb new file mode 100644 index 0000000..f90b8de --- /dev/null +++ b/lib/addic7ed/models/search.rb @@ -0,0 +1,32 @@ +module Addic7ed + class Search + attr_reader :video_filename, :language, :options + + def initialize(video_filename, language, options = {}) + @video_filename = video_filename + @language = language + @options = default_options.merge(options) + end + + def video_file + @video_file ||= VideoFile.new(video_filename) + end + + def episode + @episode ||= Episode.new(video_file.showname, video_file.season, video_file.episode) + end + + def best_subtitle + episode.subtitles(language).each do |subtitle| + @best_subtitle = subtitle if subtitle.works_for?(video_file.group, options[:no_hi]) && subtitle.can_replace?(@best_subtitle) + end + @best_subtitle || raise(NoSubtitleFound) + end + + private + + def default_options + {no_hi: false} + end + end +end diff --git a/spec/lib/addic7ed/models/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb index 32e2957..4e9227a 100644 --- a/spec/lib/addic7ed/models/episode_spec.rb +++ b/spec/lib/addic7ed/models/episode_spec.rb @@ -1,24 +1,10 @@ require "spec_helper" -describe Addic7ed::Episode, "#localized_urls" do - let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } - let(:episode) { described_class.new(filename) } - - subject { episode.send(:localized_urls) } - - it "returns a hash of episode page localized URL" do - expect(subject["fr"]).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" - expect(subject["es"]).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4" - end - - it "raises LanguageNotSupported given an unsupported language code" do - expect{ subject["aa"] }.to raise_error Addic7ed::LanguageNotSupported - end -end - describe Addic7ed::Episode, "#subtitles" do - let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } - let(:episode) { described_class.new(filename) } + let(:showname) { "The.Walking.Dead" } + let(:season) { 3 } + let(:episode_nbr) { 2 } + let(:episode) { described_class.new(showname, season, episode_nbr) } before do %w{fr en it}.each do |lang| @@ -28,121 +14,57 @@ end end - it "should return an array of Addic7ed::Subtitle given valid episode and language" do + it "returns an array of Addic7ed::Subtitle given valid episode and language" do expect(episode.subtitles("fr").size).to eq 4 expect(episode.subtitles("en").size).to eq 3 expect(episode.subtitles("it").size).to eq 1 end - it "uses English as default language" do - expect(episode.subtitles).to eq episode.subtitles("en") - end - it "raises LanguageNotSupported given an unsupported language code" do expect{ episode.subtitles("aa") }.to raise_error Addic7ed::LanguageNotSupported end it "memoizes results to minimize network requests" do - 2.times { episode.subtitles } + 2.times { episode.subtitles("en") } expect(a_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1")).to have_been_made.times(1) end context "when episode does not exist" do - let(:filename) { "The.Walking.Dead.S03E42.720p.HDTV.x264-EVOLVE.mkv" } + let(:episode_nbr) { 42 } before { stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/42/8").to_return File.new("spec/responses/walking-dead-3-42-8.http") } - it "raises EpisodeNotFound given not existing episode" do - expect{ described_class.new(filename).subtitles("fr") }.to raise_error Addic7ed::EpisodeNotFound + it "raises EpisodeNotFound" do + expect{ described_class.new(showname, season, episode_nbr).subtitles("fr") }.to raise_error Addic7ed::EpisodeNotFound end end context "when episode exists but has no subtitles" do before { stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48").to_return File.new("spec/responses/walking-dead-3-2-48.http") } - it "raises NoSubtitleFound given valid episode which has no subtitle on Addic7ed" do + it "raises NoSubtitleFound" do expect{ episode.subtitles("az") }.to raise_error Addic7ed::NoSubtitleFound end end end -### -### THE FOLLOWING WILL BE MOVED TO A NEW SERVICE OBJECT AND REWRITTEN -### - -# describe Addic7ed::Episode, "#best_subtitle" do -# let(:lang) { "fr" } -# let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } -# let(:filename_no_hi) { "The.Walking.Dead.S03E02.720p.HDTV.x264-KILLERS.mkv" } -# let(:filename_compatible_group) { "The.Walking.Dead.S03E04.HDTV.XviD-ASAP.mkv" } -# let(:episode) { Addic7ed::Episode.new(filename) } - -# it "finds the subtitle with status completed and same group name" do -# stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8") -# .to_return File.new("spec/responses/walking-dead-3-2-8.http") -# expect(episode.best_subtitle("fr").url).to eq "http://www.addic7ed.com/original/68018/4" -# end - -# it "finds the subtitle with status completed, compatible group name and as many downloads as possible" do -# stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/4/8") -# .to_return File.new("spec/responses/walking-dead-3-4-8.http") -# compatible_episode = Addic7ed::Episode.new(filename_compatible_group) -# expect(compatible_episode.best_subtitle("fr").url).to eq "http://www.addic7ed.com/updated/8/68508/3" -# end +describe Addic7ed::Episode, "#page_url(lang)" do + let(:showname) { "The.Walking.Dead" } + let(:season) { 3 } + let(:episode_nbr) { 2 } + let(:episode) { described_class.new(showname, season, episode_nbr) } -# it "finds the subtitle with status completed, same group name and not hearing impaired" do -# stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1") -# .to_return File.new("spec/responses/walking-dead-3-2-1.http") -# episode = Addic7ed::Episode.new(filename_no_hi) -# expect(episode.best_subtitle("en", true).url).to eq "http://www.addic7ed.com/updated/1/68018/0" -# end - -# it "uses English as default language" do -# expect(episode.best_subtitle).to eq episode.best_subtitle("en") -# end - -# it "raises LanguageNotSupported given an unsupported language code" do -# expect{ episode.best_subtitle("aa") }.to raise_error Addic7ed::LanguageNotSupported -# end - -# it "raises NoSubtitleFound given valid episode which has no subtitle on Addic7ed" do -# stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48") -# .to_return File.new("spec/responses/walking-dead-3-2-48.http") -# expect{ episode.best_subtitle("az") }.to raise_error Addic7ed::NoSubtitleFound -# end -# end - -describe Addic7ed::Episode, "#download_best_subtitle!" do - let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } - let(:lang) { "fr" } - let(:episode) { described_class.new(filename) } - let(:best_subtitle) { double(:best_subtitle, url: "http://best.subtitle.url") } - - subject { episode.download_best_subtitle!(lang) } - - before { allow(episode).to receive(:best_subtitle).and_return(best_subtitle) } - - it "calls DownloadSubtitle on the best subtitle's URL" do - expect(Addic7ed::DownloadSubtitle).to receive(:call).with(best_subtitle.url, anything(), anything()) - subject - end - - it "calls DownloadSubtitle with episode's page as referer" do - expect(Addic7ed::DownloadSubtitle).to receive(:call).with(anything(), anything(), episode.send(:localized_urls)[lang]) - subject + it "returns an episode page URL for given language" do + expect(episode.page_url("fr")).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" + expect(episode.page_url("es")).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4" end - it "calls DownloadSubtitle with videofile's filename with language-prefixed .srt extension" do - expect(Addic7ed::DownloadSubtitle).to receive(:call).with(anything(), File.basename(filename, ".*") + ".#{lang}.srt", anything()) - subject + it "uses URLEncodeShowName to generate the localized URLs" do + expect(Addic7ed::URLEncodeShowName).to receive(:call).with(showname).and_return("The_Walking_Dead") + episode.page_url("fr") end - context "when untagged option is set" do - let(:episode) { described_class.new(filename, true) } - - it "calls DownloadSubtitle with videofile's filename with .srt extension" do - expect(Addic7ed::DownloadSubtitle).to receive(:call).with(anything(), File.basename(filename, ".*") + ".srt", anything()) - subject - end + it "raises LanguageNotSupported given an unsupported language code" do + expect{ episode.page_url("aa") }.to raise_error Addic7ed::LanguageNotSupported end end diff --git a/spec/lib/addic7ed/models/search_spec.rb b/spec/lib/addic7ed/models/search_spec.rb new file mode 100644 index 0000000..da3c9fa --- /dev/null +++ b/spec/lib/addic7ed/models/search_spec.rb @@ -0,0 +1,83 @@ +require "spec_helper" + +describe Addic7ed::Search do + let(:video_filename) { "Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv" } + let(:language) { "fr" } + let(:options) { {} } + + let(:subtitles_fr) { [] } + + let(:video_file) { double(:video_file, showname: "Game.of.Thrones", season: 6, episode: 9, group: "AVS") } + let(:episode) { double(:episode) } + + before do + allow(Addic7ed::VideoFile).to receive(:new).and_return(video_file) + allow(Addic7ed::Episode).to receive(:new).and_return(episode) + allow(episode).to receive(:subtitles).with(language).and_return(subtitles_fr) + end + + subject { described_class.new(video_filename, language, options) } + + describe "#video_file" do + it "returns the associated Addic7ed::VideoFile" do + subject.video_file + expect(Addic7ed::VideoFile).to have_received(:new).with(video_filename) + end + + it "is memoized" do + 2.times { subject.video_file } + expect(Addic7ed::VideoFile).to have_received(:new).exactly(1) + end + end + + describe "#episode" do + it "returns the associated Addic7ed::Episode object" do + subject.episode + expect(Addic7ed::Episode).to have_received(:new).with("Game.of.Thrones", 6, 9) + end + end + + #### + #### THE FOLLOWING WILL BE MOVED TO A NEW SERVICE OBJECT AND REWRITTEN + #### + # describe "#best_subtitle" do + # let(:video_filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } + # let(:video_filename_no_hi) { "The.Walking.Dead.S03E02.720p.HDTV.x264-KILLERS.mkv" } + # let(:video_filename_compatible_group) { "The.Walking.Dead.S03E04.HDTV.XviD-ASAP.mkv" } + # let(:episode) { Addic7ed::Episode.new(video_filename, language, options) } + # + # it "finds the subtitle with status completed and same group name" do + # stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8") + # .to_return File.new("spec/responses/walking-dead-3-2-8.http") + # expect(episode.best_subtitle("fr").url).to eq "http://www.addic7ed.com/original/68018/4" + # end + # + # it "finds the subtitle with status completed, compatible group name and as many downloads as possible" do + # stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/4/8") + # .to_return File.new("spec/responses/walking-dead-3-4-8.http") + # compatible_episode = Addic7ed::Episode.new(filename_compatible_group) + # expect(compatible_episode.best_subtitle("fr").url).to eq "http://www.addic7ed.com/updated/8/68508/3" + # end + # + # it "finds the subtitle with status completed, same group name and not hearing impaired" do + # stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1") + # .to_return File.new("spec/responses/walking-dead-3-2-1.http") + # episode = Addic7ed::Episode.new(filename_no_hi) + # expect(episode.best_subtitle("en", true).url).to eq "http://www.addic7ed.com/updated/1/68018/0" + # end + # + # it "uses English as default language" do + # expect(episode.best_subtitle).to eq episode.best_subtitle("en") + # end + # + # it "raises LanguageNotSupported given an unsupported language code" do + # expect{ episode.best_subtitle("aa") }.to raise_error Addic7ed::LanguageNotSupported + # end + # + # it "raises NoSubtitleFound given valid episode which has no subtitle on Addic7ed" do + # stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48") + # .to_return File.new("spec/responses/walking-dead-3-2-48.http") + # expect{ episode.best_subtitle("az") }.to raise_error Addic7ed::NoSubtitleFound + # end + # end +end From b299a3427756c4a48020a41b2d9e928e22f596af Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 14:50:10 +0400 Subject: [PATCH 18/89] Add Rubocop and comply to it :cop: --- .rubocop.yml | 11 ++ Gemfile | 14 +-- README.md | 3 +- Rakefile | 6 +- addic7ed.gemspec | 3 +- bin/addic7ed | 2 + bin/console | 2 +- lib/addic7ed/common.rb | 117 +++++++++--------- lib/addic7ed/models/episode.rb | 9 +- lib/addic7ed/models/search.rb | 9 +- lib/addic7ed/models/subtitle.rb | 50 ++++---- lib/addic7ed/models/video_file.rb | 60 +++++---- lib/addic7ed/services/download_subtitle.rb | 33 ++--- lib/addic7ed/services/get_shows_list.rb | 12 +- lib/addic7ed/services/normalize_version.rb | 12 +- lib/addic7ed/services/parse_page.rb | 14 +-- lib/addic7ed/services/parse_subtitle.rb | 88 ++++++------- lib/addic7ed/services/url_encode_show_name.rb | 16 +-- lib/addic7ed/version.rb | 2 +- spec/lib/addic7ed/common_spec.rb | 12 +- spec/lib/addic7ed/models/episode_spec.rb | 34 +++-- spec/lib/addic7ed/models/search_spec.rb | 8 +- spec/lib/addic7ed/models/subtitle_spec.rb | 101 +++++++++------ spec/lib/addic7ed/models/video_file_spec.rb | 78 +++++++----- .../services/download_subtitle_spec.rb | 27 ++-- .../addic7ed/services/get_shows_list_spec.rb | 9 +- .../services/url_encode_show_name_spec.rb | 2 +- spec/spec_helper.rb | 11 +- 28 files changed, 437 insertions(+), 308 deletions(-) create mode 100644 .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..f97164e --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,11 @@ +Style/DoubleNegation: + Enabled: false + +Style/StringLiterals: + EnforcedStyle: double_quotes + +Metrics/LineLength: + Max: 100 + +Style/AccessModifierIndentation: + EnforcedStyle: outdent diff --git a/Gemfile b/Gemfile index 12dae51..2740e55 100644 --- a/Gemfile +++ b/Gemfile @@ -1,15 +1,15 @@ -source 'https://rubygems.org' +source "https://rubygems.org" # The gem's dependencies will be specified in addic7ed.gemspec gemspec group :test do - gem 'coveralls', require: false + gem "coveralls", require: false end platforms :rbx do - gem 'json' - gem 'racc' - gem 'rubysl' - gem 'psych' - gem 'iconv' + gem "json" + gem "racc" + gem "rubysl" + gem "psych" + gem "iconv" end diff --git a/README.md b/README.md index a3c30bf..e9c558f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ Ruby command-line script to fetch subtitles on Addic7ed * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") * [ ] allow language to be specified both by string or by symbol * [ ] write code documentation -* [ ] Update README +* [ ] update README +* [x] add Rubocop * [ ] add specs for parsing * [ ] move CLI to a separate gem (and use Thor or similar) diff --git a/Rakefile b/Rakefile index 03c2e28..7629c26 100644 --- a/Rakefile +++ b/Rakefile @@ -1,8 +1,8 @@ -require 'rspec/core/rake_task' +require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) -task :default => :spec +task default: :spec -require 'bundler' +require "bundler" Bundler::GemHelper.install_tasks diff --git a/addic7ed.gemspec b/addic7ed.gemspec index b0eb01d..da59dd3 100644 --- a/addic7ed.gemspec +++ b/addic7ed.gemspec @@ -1,4 +1,4 @@ -$:.push File.expand_path("../lib", __FILE__) +$LOAD_PATH.push File.expand_path("../lib", __FILE__) require "addic7ed/version" @@ -16,6 +16,7 @@ Gem::Specification.new do |s| s.add_development_dependency("rake") s.add_development_dependency("webmock") s.add_development_dependency("pry") + s.add_development_dependency("rubocop") s.add_runtime_dependency("nokogiri", "~> 1.6.8") s.add_runtime_dependency("json", "~> 1.8.3") diff --git a/bin/addic7ed b/bin/addic7ed index d09159f..3e6dab4 100755 --- a/bin/addic7ed +++ b/bin/addic7ed @@ -1,5 +1,6 @@ #!/usr/bin/env ruby +# rubocop:disable all def require_dependencies require 'optparse' require 'nokogiri' @@ -135,3 +136,4 @@ options[:filenames].each do |filename| next end end +# rubocop:enable all diff --git a/bin/console b/bin/console index ef67ca4..c9b6903 100755 --- a/bin/console +++ b/bin/console @@ -5,7 +5,7 @@ Bundler.require(:default, ENV["RACK_ENV"] || :development) APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..")) -$LOAD_PATH.unshift File.expand_path('lib', APP_ROOT) +$LOAD_PATH.unshift File.expand_path("lib", APP_ROOT) require "addic7ed" diff --git a/lib/addic7ed/common.rb b/lib/addic7ed/common.rb index 7bddf71..81a334a 100644 --- a/lib/addic7ed/common.rb +++ b/lib/addic7ed/common.rb @@ -1,66 +1,66 @@ # encoding: UTF-8 -module Addic7ed - - SHOWS_URL = 'http://www.addic7ed.com/ajax_getShows.php' - EPISODES_URL = 'http://www.addic7ed.com/ajax_getEpisodes.php' - EPISODE_REDIRECT_URL = 'http://www.addic7ed.com/re_episode.php' +module Addic7ed # rubocop:disable Metrics/ModuleLength + SHOWS_URL = "http://www.addic7ed.com/ajax_getShows.php".freeze + EPISODES_URL = "http://www.addic7ed.com/ajax_getEpisodes.php".freeze + EPISODE_REDIRECT_URL = "http://www.addic7ed.com/re_episode.php".freeze COMPATIBILITY_720P = { - 'LOL' => 'DIMENSION', - 'SYS' => 'DIMENSION', - 'XII' => 'IMMERSE', - 'ASAP' => 'IMMERSE' - } + "LOL" => "DIMENSION", + "SYS" => "DIMENSION", + "XII" => "IMMERSE", + "ASAP" => "IMMERSE" + }.freeze LANGUAGES = { - 'ar' => {name: 'Arabic', id: 38}, - 'az' => {name: 'Azerbaijani', id: 48}, - 'bn' => {name: 'Bengali', id: 47}, - 'bs' => {name: 'Bosnian', id: 44}, - 'bg' => {name: 'Bulgarian', id: 35}, - 'ca' => {name: 'Català', id: 12}, - 'cn' => {name: 'Chinese (Simplified)', id: 41}, - 'zh' => {name: 'Chinese (Traditional)', id: 24}, - 'hr' => {name: 'Croatian', id: 31}, - 'cs' => {name: 'Czech', id: 14}, - 'da' => {name: 'Danish', id: 30}, - 'nl' => {name: 'Dutch', id: 17}, - 'en' => {name: 'English', id: 1}, - 'eu' => {name: 'Euskera', id: 13}, - 'fi' => {name: 'Finnish', id: 28}, - 'fr' => {name: 'French', id: 8}, - 'gl' => {name: 'Galego', id: 15}, - 'de' => {name: 'German', id: 11}, - 'el' => {name: 'Greek', id: 27}, - 'he' => {name: 'Hebrew', id: 23}, - 'hu' => {name: 'Hungarian', id: 20}, - 'id' => {name: 'Indonesian', id: 37}, - 'it' => {name: 'Italian', id: 7}, - 'ja' => {name: 'Japanese', id: 32}, - 'ko' => {name: 'Korean', id: 42}, - 'mk' => {name: 'Macedonian', id: 49}, - 'ms' => {name: 'Malay', id: 40}, - 'no' => {name: 'Norwegian', id: 29}, - 'fa' => {name: 'Persian', id: 43}, - 'pl' => {name: 'Polish', id: 21}, - 'pt' => {name: 'Portuguese', id: 9}, - 'pt-br' => {name: 'Portuguese (Brazilian)', id: 10}, - 'ro' => {name: 'Romanian', id: 26}, - 'ru' => {name: 'Russian', id: 19}, - 'sr' => {name: 'Serbian (Cyrillic)', id: 39}, - 'sr-la' => {name: 'Serbian (Latin)', id: 36}, - 'sk' => {name: 'Slovak', id: 25}, - 'sl' => {name: 'Slovenian', id: 22}, - 'es' => {name: 'Spanish', id: 4}, - 'es-la' => {name: 'Spanish (Latin America)', id: 6}, - 'es-es' => {name: 'Spanish (Spain)', id: 5}, - 'sv' => {name: 'Swedish', id: 18}, - 'th' => {name: 'Thai', id: 46}, - 'tr' => {name: 'Turkish', id: 16}, - 'vi' => {name: 'Vietnamese', id: 45} - } + "ar" => { name: "Arabic", id: 38 }, + "az" => { name: "Azerbaijani", id: 48 }, + "bn" => { name: "Bengali", id: 47 }, + "bs" => { name: "Bosnian", id: 44 }, + "bg" => { name: "Bulgarian", id: 35 }, + "ca" => { name: "Català", id: 12 }, + "cn" => { name: "Chinese (Simplified)", id: 41 }, + "zh" => { name: "Chinese (Traditional)", id: 24 }, + "hr" => { name: "Croatian", id: 31 }, + "cs" => { name: "Czech", id: 14 }, + "da" => { name: "Danish", id: 30 }, + "nl" => { name: "Dutch", id: 17 }, + "en" => { name: "English", id: 1 }, + "eu" => { name: "Euskera", id: 13 }, + "fi" => { name: "Finnish", id: 28 }, + "fr" => { name: "French", id: 8 }, + "gl" => { name: "Galego", id: 15 }, + "de" => { name: "German", id: 11 }, + "el" => { name: "Greek", id: 27 }, + "he" => { name: "Hebrew", id: 23 }, + "hu" => { name: "Hungarian", id: 20 }, + "id" => { name: "Indonesian", id: 37 }, + "it" => { name: "Italian", id: 7 }, + "ja" => { name: "Japanese", id: 32 }, + "ko" => { name: "Korean", id: 42 }, + "mk" => { name: "Macedonian", id: 49 }, + "ms" => { name: "Malay", id: 40 }, + "no" => { name: "Norwegian", id: 29 }, + "fa" => { name: "Persian", id: 43 }, + "pl" => { name: "Polish", id: 21 }, + "pt" => { name: "Portuguese", id: 9 }, + "pt-br" => { name: "Portuguese (Brazilian)", id: 10 }, + "ro" => { name: "Romanian", id: 26 }, + "ru" => { name: "Russian", id: 19 }, + "sr" => { name: "Serbian (Cyrillic)", id: 39 }, + "sr-la" => { name: "Serbian (Latin)", id: 36 }, + "sk" => { name: "Slovak", id: 25 }, + "sl" => { name: "Slovenian", id: 22 }, + "es" => { name: "Spanish", id: 4 }, + "es-la" => { name: "Spanish (Latin America)", id: 6 }, + "es-es" => { name: "Spanish (Spain)", id: 5 }, + "sv" => { name: "Swedish", id: 18 }, + "th" => { name: "Thai", id: 46 }, + "tr" => { name: "Turkish", id: 16 }, + "vi" => { name: "Viet namese", id: 45 } + }.freeze + # rubocop:disable Metrics/LineLength USER_AGENTS = [ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0a2) Gecko/20111101 Firefox/9.0a2", "Mozilla/5.0 (Windows NT 6.2; rv:9.0.1) Gecko/20100101 Firefox/9.0.1", @@ -1285,5 +1285,6 @@ module Addic7ed "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13" - ] -end \ No newline at end of file + ].freeze + # rubocop:enable Metrics/LineLength +end diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index 57969cd..3db1ada 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -1,16 +1,15 @@ -require 'net/http' -require 'open-uri' +require "net/http" +require "open-uri" module Addic7ed class Episode - attr_reader :showname, :season, :episode def initialize(showname, season, episode) @showname = showname @season = season @episode = episode - @subtitles = languages_hash { |code, _| {code => nil} } + @subtitles = languages_hash { |code, _| { code => nil } } end def subtitles(lang) @@ -24,7 +23,7 @@ def page_url(lang) protected def localized_urls - @localized_urls ||= languages_hash { |code, lang| {code => localized_url(lang[:id])} } + @localized_urls ||= languages_hash { |code, lang| { code => localized_url(lang[:id]) } } end def url_encoded_showname diff --git a/lib/addic7ed/models/search.rb b/lib/addic7ed/models/search.rb index f90b8de..1eb20b8 100644 --- a/lib/addic7ed/models/search.rb +++ b/lib/addic7ed/models/search.rb @@ -18,15 +18,20 @@ def episode def best_subtitle episode.subtitles(language).each do |subtitle| - @best_subtitle = subtitle if subtitle.works_for?(video_file.group, options[:no_hi]) && subtitle.can_replace?(@best_subtitle) + @best_subtitle = subtitle if better_than_best_subtitle?(subtitle) end @best_subtitle || raise(NoSubtitleFound) end private + def better_than_best_subtitle?(subtitle) + subtitle.works_for?(video_file.group, options[:no_hi]) && + subtitle.can_replace?(@best_subtitle) + end + def default_options - {no_hi: false} + { no_hi: false } end end end diff --git a/lib/addic7ed/models/subtitle.rb b/lib/addic7ed/models/subtitle.rb index 7ec853c..cb20a67 100644 --- a/lib/addic7ed/models/subtitle.rb +++ b/lib/addic7ed/models/subtitle.rb @@ -1,6 +1,5 @@ module Addic7ed class Subtitle - attr_reader :version, :language, :status, :via, :downloads, :comment attr_accessor :url @@ -16,34 +15,38 @@ def initialize(options = {}) end def to_s - "#{url}\t->\t#{version} (#{language}, #{status}) [#{downloads} downloads]#{" (via #{via})" if via}" + str = "#{url}\t->\t#{version} (#{language}, #{status}) [#{downloads} downloads]" + str += " (via #{via})" if via + str end - def works_for?(version = '', no_hi = false) + def works_for?(version = "", no_hi = false) hi_works = !@hi || !no_hi - is_completed? and is_compatible_with? version and hi_works + completed? && compatible_with?(version) && hi_works end def can_replace?(other_subtitle) - return false unless is_completed? + return false unless completed? return true if other_subtitle.nil? language == other_subtitle.language && - is_compatible_with?(other_subtitle.version) && - is_more_popular_than?(other_subtitle) + compatible_with?(other_subtitle.version) && + more_popular_than?(other_subtitle) end - def is_featured? + def featured? via == "http://addic7ed.com" end - def is_completed? - status == 'Completed' + def completed? + status == "Completed" end protected - def is_compatible_with?(other_version) - defined_as_compatible_with(other_version) || generally_compatible_with?(other_version) || commented_as_compatible_with?(other_version) + def compatible_with?(other_version) + defined_as_compatible_with(other_version) || + generally_compatible_with?(other_version) || + commented_as_compatible_with?(other_version) end def defined_as_compatible_with(other_version) @@ -55,18 +58,23 @@ def generally_compatible_with?(other_version) end def commented_as_compatible_with?(other_version) - return false if /(won't|doesn't|not) +work/i.match comment - return false if /resync +(from|of)/i.match comment - res = comment.include? other_version.downcase - res ||= comment.include? COMPATIBILITY_720P[other_version].downcase if COMPATIBILITY_720P[other_version] - res ||= comment.include? COMPATIBILITY_720P[version].downcase if COMPATIBILITY_720P[version] - !!res + return false if /(won't|doesn't|not) +work/i =~ comment + return false if /resync +(from|of)/i =~ comment + known_compatible_versions(other_version) + .push(other_version) + .map(&:downcase) + .map { |compatible_version| comment.include? compatible_version } + .reduce(:|) + end + + def known_compatible_versions(other_version) + [COMPATIBILITY_720P[other_version], COMPATIBILITY_720P[version]].compact end - def is_more_popular_than?(other_subtitle) + def more_popular_than?(other_subtitle) return true if other_subtitle.nil? - return false if other_subtitle.is_featured? - return downloads > other_subtitle.downloads + return false if other_subtitle.featured? + downloads > other_subtitle.downloads end end end diff --git a/lib/addic7ed/models/video_file.rb b/lib/addic7ed/models/video_file.rb index a14b6f0..f119297 100644 --- a/lib/addic7ed/models/video_file.rb +++ b/lib/addic7ed/models/video_file.rb @@ -1,41 +1,55 @@ module Addic7ed class VideoFile + TVSHOW_REGEX = /\A(?.*\w)[\[\. ]+S?(?\d{1,2})[-\. ]?[EX]?(?\d{2})([-\. ]?[EX]?\d{2})*[\]\. ]+(?.*)-(?\w*)\[?(?\w*)\]?(\.\w{3})?\z/i # rubocop:disable Style/LineLength - TVSHOW_REGEX = /\A(?.*\w)[\[\. ]+S?(?\d{1,2})[-\. ]?[EX]?(?\d{2})([-\. ]?[EX]?\d{2})*[\]\. ]+(?.*)-(?\w*)\[?(?\w*)\]?(\.\w{3})?\z/i - - attr_reader :filename, :showname, :season, :episode, :tags, :group, :distribution + attr_reader :filename, :regexp_matches def initialize(filename) @filename = filename - if match = TVSHOW_REGEX.match(basename) - @showname = match[:showname] - @season = match[:season].to_i - @episode = match[:episode].to_i - @tags = match[:tags].upcase.split(/[\. ]/) - @group = match[:group].upcase - @distribution = match[:distribution].upcase - else - raise InvalidFilename - end + @regexp_matches = TVSHOW_REGEX.match(basename) + raise InvalidFilename if regexp_matches.nil? + end + + def showname + @showname ||= regexp_matches[:showname] + end + + def season + @season ||= regexp_matches[:season].to_i + end + + def episode + @episode ||= regexp_matches[:episode].to_i + end + + def tags + @tags ||= regexp_matches[:tags].upcase.split(/[\. ]/) + end + + def group + @group ||= regexp_matches[:group].upcase + end + + def distribution + @distribution ||= regexp_matches[:distribution].upcase end def basename - @basename ||= ::File.basename(@filename) + @basename ||= File.basename(filename) end def to_s - @filename + filename end def inspect -"Guesses for #{@filename}: - show: #{@showname} - season: #{@season} - episode: #{@episode} - tags: #{@tags} - group: #{@group} - distribution: #{@distribution}" + "Guesses for #{filename}:\n" \ + " show: #{showname}\n" \ + " season: #{season}\n" \ + " episode: #{episode}\n" \ + " tags: #{tags}\n" \ + " group: #{group}\n" \ + " distribution: #{distribution}" end - end end diff --git a/lib/addic7ed/services/download_subtitle.rb b/lib/addic7ed/services/download_subtitle.rb index 3916695..f9db309 100644 --- a/lib/addic7ed/services/download_subtitle.rb +++ b/lib/addic7ed/services/download_subtitle.rb @@ -4,24 +4,20 @@ class DownloadSubtitle HTTP_REDIRECT_LIMIT = 8 - attr_reader :url, :filename, :referer, :http_redirect_count + attr_reader :url, :filename, :referer, :redirect_count - def initialize(url, filename, referer, http_redirect_count = 0) + def initialize(url, filename, referer, redirect_count = 0) @url = url @filename = filename @referer = referer - @http_redirect_count = http_redirect_count + @redirect_count = redirect_count end def call - raise DownloadError.new("Too many HTTP redirections") if http_redirect_count >= HTTP_REDIRECT_LIMIT - raise DailyLimitExceeded.new("Daily limit exceeded") if /^\/downloadexceeded.php/.match url - if response.kind_of? Net::HTTPRedirection - new_url = URI.escape(response["location"]) # Addic7ed is serving redirection URL not-encoded, but Ruby does not support it (see http://bugs.ruby-lang.org/issues/7396) - DownloadSubtitle.call(new_url, filename, url, http_redirect_count + 1) - else - write(response.body) - end + raise DownloadError, "Too many HTTP redirections" if redirect_count >= HTTP_REDIRECT_LIMIT + raise DailyLimitExceeded, "Daily limit exceeded" if %r{^/downloadexceeded.php} =~ url + return follow_redirection(response["location"]) if response.is_a? Net::HTTPRedirection + write(response.body) end private @@ -33,12 +29,19 @@ def uri def response @response ||= Net::HTTP.start(uri.hostname, uri.port) do |http| request = Net::HTTP::Get.new(uri.request_uri) - request["Referer"] = referer # Addic7ed needs the Referer to be correct - request["User-Agent"] = USER_AGENTS.sample # User-agent is just here to fake a real browser request + request["Referer"] = referer # Addic7ed requires the Referer to be correct + request["User-Agent"] = USER_AGENTS.sample http.request(request) end rescue - raise DownloadError.new("A network error occured") + raise DownloadError, "A network error occured" + end + + def follow_redirection(location_header) + # Addic7ed is serving redirection URL not-encoded, + # but Ruby does not support it (see http://bugs.ruby-lang.org/issues/7396) + new_url = URI.escape(location_header) + DownloadSubtitle.call(new_url, filename, url, redirect_count + 1) end def write(content) @@ -46,7 +49,7 @@ def write(content) f << content end rescue - raise DownloadError.new("Cannot write to disk") + raise DownloadError, "Cannot write to disk" end end end diff --git a/lib/addic7ed/services/get_shows_list.rb b/lib/addic7ed/services/get_shows_list.rb index ff0dafc..adb44f5 100644 --- a/lib/addic7ed/services/get_shows_list.rb +++ b/lib/addic7ed/services/get_shows_list.rb @@ -1,13 +1,21 @@ module Addic7ed class GetShowsList - include Service + include Singleton + + def self.call + instance.call + end def call - @@shows ||= Nokogiri::HTML(addic7ed_homepage.body).css("select#qsShow option:not(:first-child)").map(&:text) + @shows ||= homepage_body.css("select#qsShow option:not(:first-child)").map(&:text) end private + def homepage_body + @homepage_body ||= Nokogiri::HTML(addic7ed_homepage.body) + end + def addic7ed_homepage Net::HTTP.start("www.addic7ed.com") do |http| request = Net::HTTP::Get.new("/") diff --git a/lib/addic7ed/services/normalize_version.rb b/lib/addic7ed/services/normalize_version.rb index 5258c3c..6fc1bbc 100644 --- a/lib/addic7ed/services/normalize_version.rb +++ b/lib/addic7ed/services/normalize_version.rb @@ -9,12 +9,12 @@ def initialize(version) end def call - version. - gsub(/[[:space:]]/, ""). - upcase. - gsub(/,[\d\. ]+MBS$/, ''). - gsub(/(^VERSION *|720P|1080P|HDTV|PROPER|RERIP|INTERNAL|X\.?264)/, ''). - gsub(/[- \.]/, '') + version + .gsub(/[[:space:]]/, "") + .upcase + .gsub(/,[\d\. ]+MBS$/, "") + .gsub(/(^VERSION *|720P|1080P|HDTV|PROPER|RERIP|INTERNAL|X\.?264)/, "") + .gsub(/[- \.]/, "") end end end diff --git a/lib/addic7ed/services/parse_page.rb b/lib/addic7ed/services/parse_page.rb index ef3e5cd..688c689 100644 --- a/lib/addic7ed/services/parse_page.rb +++ b/lib/addic7ed/services/parse_page.rb @@ -1,6 +1,6 @@ -require 'nokogiri' -require 'net/http' -require 'open-uri' +require "nokogiri" +require "net/http" +require "open-uri" module Addic7ed class ParsePage @@ -13,7 +13,7 @@ def initialize(url) end def call - check_subtitles_presence! + raise NoSubtitleFound unless subtitles_found? subtitles_nodes.map { |subtitle_node| Addic7ed::ParseSubtitle.call(subtitle_node) } end @@ -25,7 +25,7 @@ def page_dom end def subtitles_nodes - @subtitles_nodes ||= page_dom.css('#container95m table.tabel95 table.tabel95') + @subtitles_nodes ||= page_dom.css("#container95m table.tabel95 table.tabel95") end def server_response @@ -36,8 +36,8 @@ def server_response end end - def check_subtitles_presence! - raise NoSubtitleFound unless page_dom.css('select#filterlang ~ font[color="yellow"]').empty? && subtitles_nodes.size > 0 + def subtitles_found? + page_dom.css("select#filterlang ~ font[color='yellow']").empty? && !subtitles_nodes.empty? end end end diff --git a/lib/addic7ed/services/parse_subtitle.rb b/lib/addic7ed/services/parse_subtitle.rb index 87fdc34..3d3ec6c 100644 --- a/lib/addic7ed/services/parse_subtitle.rb +++ b/lib/addic7ed/services/parse_subtitle.rb @@ -1,4 +1,4 @@ -require 'nokogiri' +require "nokogiri" module Addic7ed class ParseSubtitle @@ -6,70 +6,74 @@ class ParseSubtitle attr_reader :subtitle_node + FIELDS = %i(version language status url source hi downloads comment).freeze + def initialize(subtitle_node) @subtitle_node = subtitle_node end def call - Addic7ed::Subtitle.new( - version: extract_version(subtitle_node), - language: extract_language(subtitle_node), - status: extract_status(subtitle_node), - url: extract_url(subtitle_node), - source: extract_source(subtitle_node), - hi: extract_hi(subtitle_node), - downloads: extract_downloads(subtitle_node), - comment: extract_comment(subtitle_node) - ) + Addic7ed::Subtitle.new(extract_fields) end private - def extract_version(subtitle_node) - version_node = subtitle_node.css('.NewsTitle').first - raise Addic7ed::ParsingError unless version_node - version_node.content + def extract_fields + FIELDS.map do |field| + { field => send(:"extract_#{field}") } + end.reduce(:merge) + end + + def extract_field(selector, options = { required: true }) + node = subtitle_node.css(selector).first + raise Addic7ed::ParsingError if options[:required] && node.nil? + yield node + end + + def extract_version + extract_field(".NewsTitle", &:content) end - def extract_language(subtitle_node) - language_node = subtitle_node.css('.language').first - raise Addic7ed::ParsingError unless language_node - language_node.content.gsub(/\A\W*/, '').gsub(/[^\w\)]*\z/, '') + def extract_language + extract_field(".language") do |node| + node.content.gsub(/\A\W*/, ").gsub(/[^\w\)]*\z/, ") + end end - def extract_status(subtitle_node) - status_node = subtitle_node.css('tr:nth-child(3) td:nth-child(4) b').first - raise Addic7ed::ParsingError unless status_node - status_node.content.strip + def extract_status + extract_field("tr:nth-child(3) td:nth-child(4) b") do |node| + node.content.strip + end end - def extract_url(subtitle_node) - url_node = subtitle_node.css('a.buttonDownload').last - raise Addic7ed::ParsingError unless url_node - 'http://www.addic7ed.com' + url_node['href'] + def extract_url + extract_field("a.buttonDownload:last-of-type") do |node| + "http://www.addic7ed.com#{node['href']}" + end end - def extract_source(subtitle_node) - source_node = subtitle_node.css('tr:nth-child(3) td:first-child a').first - source_node['href'] if source_node + def extract_source + extract_field("tr:nth-child(3) td:first-child a", required: false) do |node| + node["href"] unless node.nil? + end end - def extract_hi(subtitle_node) - hi_node = subtitle_node.css('tr:nth-child(4) td.newsDate').children[1] - raise Addic7ed::ParsingError unless hi_node - !hi_node.attribute("title").nil? + def extract_hi + extract_field("tr:nth-child(4) td.newsDate img:last-of-type") do |node| + node.attribute("title") == "Hearing Impaired" + end end - def extract_downloads(subtitle_node) - downloads_node = subtitle_node.css('tr:nth-child(4) td.newsDate').first - raise Addic7ed::ParsingError unless downloads_node - /(?\d*) Downloads/.match(downloads_node.content)[:downloads] + def extract_downloads + extract_field("tr:nth-child(4) td.newsDate") do |node| + /(?\d*) Downloads/.match(node.content)[:downloads] + end end - def extract_comment(subtitle_node) - comment_node = subtitle_node.css('tr:nth-child(2) td.newsDate').first - raise Addic7ed::ParsingError unless comment_node - comment_node.content.gsub(/]+\>/i, "") + def extract_comment + extract_field("tr:nth-child(2) td.newsDate") do |node| + node.content.gsub(/]+\>/i, "") + end end end end diff --git a/lib/addic7ed/services/url_encode_show_name.rb b/lib/addic7ed/services/url_encode_show_name.rb index 0293166..7a95404 100644 --- a/lib/addic7ed/services/url_encode_show_name.rb +++ b/lib/addic7ed/services/url_encode_show_name.rb @@ -15,27 +15,27 @@ def call matching_shows = matching_shows(ignore_year: false) matching_shows = matching_shows(ignore_year: true) if matching_shows.empty? raise ShowNotFound if matching_shows.empty? - matching_shows.last.gsub(' ', '_') + matching_shows.last.tr(" ", "_") end private def matching_shows(opts) - addic7ed_shows.select{ |show_name| is_matching?(show_name, opts) } + addic7ed_shows.select { |show_name| matching?(show_name, opts) } end def normalize(show_name, opts) show_name .downcase - .gsub("'", "") - .gsub(/[_\.]+/, ' ') - .gsub(/ (US|UK)( |$)/i, ' (\1)\2') - .gsub(/ (\d{4})( |$)/i, ' (\1)\2') + .delete("'") + .gsub(/[_\.]+/, " ") + .gsub(/ (US|UK)( |$)/i, " (\\1)\\2") + .gsub(/ (\d{4})( |$)/i, " (\\1)\\2") .strip - .tap { |show_name| show_name.gsub!(/ \(\d{4}\)( |$)/, '\1') if opts[:ignore_year] } + .tap { |showname| showname.gsub!(/ \(\d{4}\)( |$)/, '\1') if opts[:ignore_year] } end - def is_matching?(addic7ed_show, opts) + def matching?(addic7ed_show, opts) normalize(addic7ed_show, opts) == normalize(filename, opts) end diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index e554f4a..6aa5dc3 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,3 +1,3 @@ module Addic7ed - VERSION = "2.1.0" + VERSION = "2.1.0".freeze end diff --git a/spec/lib/addic7ed/common_spec.rb b/spec/lib/addic7ed/common_spec.rb index b398a12..d4e6faf 100644 --- a/spec/lib/addic7ed/common_spec.rb +++ b/spec/lib/addic7ed/common_spec.rb @@ -1,20 +1,20 @@ -require 'spec_helper' +require "spec_helper" describe Addic7ed do - it 'defines SHOWS_URL' do + it "defines SHOWS_URL" do expect(Addic7ed::SHOWS_URL).to_not be_nil end - it 'defines EPISODES_URL' do + it "defines EPISODES_URL" do expect(Addic7ed::EPISODES_URL).to_not be_nil end - it 'defines EPISODE_REDIRECT_URL' do + it "defines EPISODE_REDIRECT_URL" do expect(Addic7ed::EPISODE_REDIRECT_URL).to_not be_nil end - it 'defines LANGUAGES' do + it "defines LANGUAGES" do expect(Addic7ed::LANGUAGES).to_not be_nil - expect(Addic7ed::LANGUAGES).to include 'fr' => {name: 'French', id: 8} + expect(Addic7ed::LANGUAGES).to include "fr" => { name: "French", id: 8 } end end diff --git a/spec/lib/addic7ed/models/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb index 4e9227a..b87307b 100644 --- a/spec/lib/addic7ed/models/episode_spec.rb +++ b/spec/lib/addic7ed/models/episode_spec.rb @@ -7,7 +7,7 @@ let(:episode) { described_class.new(showname, season, episode_nbr) } before do - %w{fr en it}.each do |lang| + %w(fr en it).each do |lang| lang_id = Addic7ed::LANGUAGES[lang][:id] stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/#{lang_id}") .to_return File.new("spec/responses/walking-dead-3-2-#{lang_id}.http") @@ -21,29 +21,43 @@ end it "raises LanguageNotSupported given an unsupported language code" do - expect{ episode.subtitles("aa") }.to raise_error Addic7ed::LanguageNotSupported + expect { episode.subtitles("aa") }.to raise_error Addic7ed::LanguageNotSupported end it "memoizes results to minimize network requests" do 2.times { episode.subtitles("en") } - expect(a_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1")).to have_been_made.times(1) + expect( + a_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1") + ).to have_been_made.times(1) end context "when episode does not exist" do let(:episode_nbr) { 42 } - before { stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/42/8").to_return File.new("spec/responses/walking-dead-3-42-8.http") } + before do + stub_request( + :get, + "http://www.addic7ed.com/serie/The_Walking_Dead/3/42/8" + ).to_return File.new("spec/responses/walking-dead-3-42-8.http") + end it "raises EpisodeNotFound" do - expect{ described_class.new(showname, season, episode_nbr).subtitles("fr") }.to raise_error Addic7ed::EpisodeNotFound + expect do + described_class.new(showname, season, episode_nbr).subtitles("fr") + end.to raise_error Addic7ed::EpisodeNotFound end end context "when episode exists but has no subtitles" do - before { stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48").to_return File.new("spec/responses/walking-dead-3-2-48.http") } + before do + stub_request( + :get, + "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48" + ).to_return File.new("spec/responses/walking-dead-3-2-48.http") + end it "raises NoSubtitleFound" do - expect{ episode.subtitles("az") }.to raise_error Addic7ed::NoSubtitleFound + expect { episode.subtitles("az") }.to raise_error Addic7ed::NoSubtitleFound end end end @@ -60,11 +74,13 @@ end it "uses URLEncodeShowName to generate the localized URLs" do - expect(Addic7ed::URLEncodeShowName).to receive(:call).with(showname).and_return("The_Walking_Dead") + expect( + Addic7ed::URLEncodeShowName + ).to receive(:call).with(showname).and_return("The_Walking_Dead") episode.page_url("fr") end it "raises LanguageNotSupported given an unsupported language code" do - expect{ episode.page_url("aa") }.to raise_error Addic7ed::LanguageNotSupported + expect { episode.page_url("aa") }.to raise_error Addic7ed::LanguageNotSupported end end diff --git a/spec/lib/addic7ed/models/search_spec.rb b/spec/lib/addic7ed/models/search_spec.rb index da3c9fa..7870279 100644 --- a/spec/lib/addic7ed/models/search_spec.rb +++ b/spec/lib/addic7ed/models/search_spec.rb @@ -4,11 +4,11 @@ let(:video_filename) { "Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv" } let(:language) { "fr" } let(:options) { {} } - let(:subtitles_fr) { [] } - - let(:video_file) { double(:video_file, showname: "Game.of.Thrones", season: 6, episode: 9, group: "AVS") } let(:episode) { double(:episode) } + let(:video_file) do + double(:video_file, showname: "Game.of.Thrones", season: 6, episode: 9, group: "AVS") + end before do allow(Addic7ed::VideoFile).to receive(:new).and_return(video_file) @@ -37,6 +37,7 @@ end end +# rubocop:disable all #### #### THE FOLLOWING WILL BE MOVED TO A NEW SERVICE OBJECT AND REWRITTEN #### @@ -80,4 +81,5 @@ # expect{ episode.best_subtitle("az") }.to raise_error Addic7ed::NoSubtitleFound # end # end +# rubocop:enable:all end diff --git a/spec/lib/addic7ed/models/subtitle_spec.rb b/spec/lib/addic7ed/models/subtitle_spec.rb index da4b298..43018d3 100644 --- a/spec/lib/addic7ed/models/subtitle_spec.rb +++ b/spec/lib/addic7ed/models/subtitle_spec.rb @@ -18,10 +18,22 @@ end describe Addic7ed::Subtitle, "#to_s" do - let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION", language: "fr", status: "Completed", url: "http://some.fancy.url", via: "http://addic7ed.com", downloads: "42") } + let(:subtitle) do + Addic7ed::Subtitle.new( + version: "DIMENSION", + language: "fr", + status: "Completed", + url: "http://some.fancy.url", + via: "http://addic7ed.com", + downloads: "42" + ) + end + let(:expected) do + "http://some.fancy.url\t->\tDIMENSION (fr, Completed) [42 downloads] (via http://addic7ed.com)" + end it "prints a human readable version" do - expect(subtitle.to_s).to eq "http://some.fancy.url\t->\tDIMENSION (fr, Completed) [42 downloads] (via http://addic7ed.com)" + expect(subtitle.to_s).to eq(expected) end end @@ -29,116 +41,131 @@ let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION") } context "when it is incomplete" do - before { allow(subtitle).to receive(:is_completed?).and_return(false) } + before { allow(subtitle).to receive(:completed?).and_return(false) } it "returns false" do - expect(subtitle.works_for? "DIMENSION").to be false + expect(subtitle.works_for?("DIMENSION")).to be false end end context "when it is completed" do - before { allow(subtitle).to receive(:is_completed?).and_return(true) } + before { allow(subtitle).to receive(:completed?).and_return(true) } it "returns true given the exact same version" do - expect(subtitle.works_for? "DIMENSION").to be true + expect(subtitle.works_for?("DIMENSION")).to be true end it "returns true given a compatible version" do - expect(subtitle.works_for? "LOL").to be true + expect(subtitle.works_for?("LOL")).to be true end it "returns false given an incompatible version" do - expect(subtitle.works_for? "EVOLVE").to be false + expect(subtitle.works_for?("EVOLVE")).to be false end context "when is has a compatibility comment" do let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Works with IMMERSE") } it "returns true given the same version as comment" do - expect(subtitle.works_for? "IMMERSE").to be true + expect(subtitle.works_for?("IMMERSE")).to be true end it "returns true given a compatible version as comment" do - expect(subtitle.works_for? "ASAP").to be true + expect(subtitle.works_for?("ASAP")).to be true end it "returns false given an incompatible version as comment" do - expect(subtitle.works_for? "KILLERS").to be false + expect(subtitle.works_for?("KILLERS")).to be false end end context "when is has an incompatibility comment" do - let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Doesn't work with IMMERSE") } + let(:subtitle) do + Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Doesn't work with IMMERSE") + end it "returns false" do - expect(subtitle.works_for? "IMMERSE").to be false + expect(subtitle.works_for?("IMMERSE")).to be false end end context "when is has an ambiguous comment" do - let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Resync from IMMERSE") } + let(:subtitle) do + Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Resync from IMMERSE") + end it "returns false" do - expect(subtitle.works_for? "IMMERSE").to be false + expect(subtitle.works_for?("IMMERSE")).to be false end end context "when it has multiple versions" do - let(:subtitle) { Addic7ed::Subtitle.new(version: "FOV,TLA") } + let(:subtitle) do + Addic7ed::Subtitle.new(version: "FOV,TLA") + end it "returns true if it works for one of them" do - expect(subtitle.works_for? "TLA").to be true - expect(subtitle.works_for? "FOV").to be true + expect(subtitle.works_for?("TLA")).to be true + expect(subtitle.works_for?("FOV")).to be true end it "returns false when none of them work" do - expect(subtitle.works_for? "LOL").to be false + expect(subtitle.works_for?("LOL")).to be false end end end end describe Addic7ed::Subtitle, "#can_replace?" do - let(:defaults) { {version: "DIMENSION", language: "fr", status: "Completed", downloads: "10"} } let(:subtitle) { Addic7ed::Subtitle.new(defaults) } let(:other_subtitle) { Addic7ed::Subtitle.new(defaults) } + let(:defaults) do + { + version: "DIMENSION", + language: "fr", + status: "Completed", + downloads: "10" + } + end context "when it is incomplete" do - before { allow(subtitle).to receive(:is_completed?).and_return(false) } + before { allow(subtitle).to receive(:completed?).and_return(false) } it "returns false" do - expect(subtitle.can_replace? other_subtitle).to be false + expect(subtitle.can_replace?(other_subtitle)).to be false end end context "when it is completed" do - before { allow(subtitle).to receive(:is_completed?).and_return(true) } + before { allow(subtitle).to receive(:completed?).and_return(true) } it "returns true given no other_subtitle" do - expect(subtitle.can_replace? nil).to be true + expect(subtitle.can_replace?(nil)).to be true end context "when other_subtitle has a different language" do before { allow(other_subtitle).to receive(:language).and_return("en") } it "returns false" do - expect(subtitle.can_replace? other_subtitle).to be false + expect(subtitle.can_replace?(other_subtitle)).to be false end end context "when other_subtitle has an incompatible version" do - before { allow(subtitle).to receive(:is_compatible_with?).with(other_subtitle.version).and_return(false) } + before do + allow(subtitle).to receive(:compatible_with?).with(other_subtitle.version).and_return(false) + end it "returns false" do - expect(subtitle.can_replace? other_subtitle).to be false + expect(subtitle.can_replace?(other_subtitle)).to be false end end context "when other_subtitle is featured by Addic7ed" do - before { allow(other_subtitle).to receive(:is_featured?).and_return(true) } + before { allow(other_subtitle).to receive(:featured?).and_return(true) } it "returns false" do - expect(subtitle.can_replace? other_subtitle).to be false + expect(subtitle.can_replace?(other_subtitle)).to be false end end @@ -146,7 +173,7 @@ before { allow(other_subtitle).to receive(:downloads).and_return(subtitle.downloads + 1) } it "returns false" do - expect(subtitle.can_replace? other_subtitle).to be false + expect(subtitle.can_replace?(other_subtitle)).to be false end end @@ -154,28 +181,28 @@ before { allow(other_subtitle).to receive(:downloads).and_return(subtitle.downloads - 1) } it "returns true" do - expect(subtitle.can_replace? other_subtitle).to be true + expect(subtitle.can_replace?(other_subtitle)).to be true end end end end -describe Addic7ed::Subtitle, "#is_featured?" do +describe Addic7ed::Subtitle, "#featured?" do it "returns true when 'via' is 'http://addic7ed.com'" do - expect(Addic7ed::Subtitle.new(via: 'http://addic7ed.com').is_featured?).to be true + expect(Addic7ed::Subtitle.new(via: "http://addic7ed.com").featured?).to be true end it "returns false otherwise" do - expect(Addic7ed::Subtitle.new(via: 'anything else').is_featured?).to be false + expect(Addic7ed::Subtitle.new(via: "anything else").featured?).to be false end end -describe Addic7ed::Subtitle, "#is_completed?" do +describe Addic7ed::Subtitle, "#completed?" do it "returns true when 'status' is 'Completed'" do - expect(Addic7ed::Subtitle.new(status: 'Completed').is_completed?).to be true + expect(Addic7ed::Subtitle.new(status: "Completed").completed?).to be true end it "returns false otherwise" do - expect(Addic7ed::Subtitle.new(status: '80%').is_completed?).to be false + expect(Addic7ed::Subtitle.new(status: "80%").completed?).to be false end end diff --git a/spec/lib/addic7ed/models/video_file_spec.rb b/spec/lib/addic7ed/models/video_file_spec.rb index 5136f71..85f193f 100644 --- a/spec/lib/addic7ed/models/video_file_spec.rb +++ b/spec/lib/addic7ed/models/video_file_spec.rb @@ -1,4 +1,4 @@ -require 'spec_helper' +require "spec_helper" describe Addic7ed::VideoFile do let(:file) { Addic7ed::VideoFile.new(filename) } @@ -7,12 +7,12 @@ let(:filename) { filename } it "it detects successfully" do - expect(file.showname ).to eq (expected_show_name || 'Showname') - expect(file.season ).to eq 2 - expect(file.episode ).to eq 1 - expect(file.tags ).to eq ['720P', 'HDTV', 'X264'] - expect(file.group ).to eq 'GROUP' - expect(file.distribution).to satisfy { |d| ['', 'DISTRIBUTION'].include?(d) } + expect(file.showname).to eq(expected_show_name || "Showname") + expect(file.season).to eq 2 + expect(file.episode).to eq 1 + expect(file.tags).to eq %w(720P HDTV X264) + expect(file.group).to eq "GROUP" + expect(file.distribution).to satisfy { |d| ["", "DISTRIBUTION"].include?(d) } end end @@ -20,7 +20,7 @@ let(:filename) { filename } it "raises an error" do - expect{file}.to raise_error Addic7ed::InvalidFilename + expect { file }.to raise_error Addic7ed::InvalidFilename end end @@ -33,52 +33,56 @@ it_behaves_like "a media file", "Showname.02x01.720p.HDTV.x264-GROUP.mkv" end - context 'with 3-digits notation' do + context "with 3-digits notation" do it_behaves_like "a media file", "Showname.201.720p.HDTV.x264-GROUP.mkv" end - context 'with brackets notation' do + context "with brackets notation" do it_behaves_like "a media file", "Showname.[S02E01].720p.HDTV.x264-GROUP.mkv" end - context 'with brackets and x notation' do + context "with brackets and x notation" do it_behaves_like "a media file", "Showname.[2x01].720p.HDTV.x264-GROUP.mkv" end - context 'with brackets and 3-digits notation' do + context "with brackets and 3-digits notation" do it_behaves_like "a media file", "Showname.[201].720p.HDTV.x264-GROUP.mkv" end - context 'with brackets and x notation and space separator' do + context "with brackets and x notation and space separator" do it_behaves_like "a media file", "Showname [2x01] 720p.HDTV.x264-GROUP.mkv" end - context 'with brackets and 3-digits notation and space separator' do + context "with brackets and 3-digits notation and space separator" do it_behaves_like "a media file", "Showname [201] 720p.HDTV.x264-GROUP.mkv" end - context 'with lowercase filename' do + context "with lowercase filename" do it_behaves_like "a media file", "showname.s02e01.720p.HDTV.x264-group.mkv", "showname" end - context 'with multiple words in show name' do + context "with multiple words in show name" do it_behaves_like "a media file", "Show.Name.S02E01.720p.HDTV.x264-GROUP.mkv", "Show.Name" end - context 'with multiple words in show name separated by spaces' do + context "with multiple words in show name separated by spaces" do it_behaves_like "a media file", "Show Name.S02E01.720p.HDTV.x264-GROUP.mkv", "Show Name" end - context 'with only numbers in show name' do + context "with only numbers in show name" do it_behaves_like "a media file", "42.S02E01.720p.HDTV.x264-GROUP.mkv", "42" end context "with production year" do - it_behaves_like "a media file", "Showname.2014.S02E01.720p.HDTV.x264-GROUP.mkv", "Showname.2014" + it_behaves_like "a media file", + "Showname.2014.S02E01.720p.HDTV.x264-GROUP.mkv", + "Showname.2014" end context "with an optional distribution group name" do - it_behaves_like "a media file", "Showname.2014.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv", "Showname.2014" + it_behaves_like "a media file", + "Showname.2014.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv", + "Showname.2014" end context "with a full path" do @@ -128,31 +132,41 @@ context "with no distribution" do it_behaves_like "a media file", "Showname.S02E01.720p.HDTV.x264-GROUP.mkv" end - end - describe '#basename' do - it 'returns only file name given a full path' do - expect(Addic7ed::VideoFile.new("/full/path/to/Showname.S02E01.720p.HDTV.x264-GROUP.mkv").basename).to eq "Showname.S02E01.720p.HDTV.x264-GROUP.mkv" + describe "#basename" do + subject { Addic7ed::VideoFile.new("/full/path/to/Showname.S02E01.720p.HDTV.x264-GROUP.mkv") } + + it "returns only file name given a full path" do + expect(subject.basename).to eq "Showname.S02E01.720p.HDTV.x264-GROUP.mkv" end end - describe '#to_s' do - it 'returns file name as a string' do - expect(Addic7ed::VideoFile.new("/full/path/to/Showname.S02E01.720p.HDTV.x264-GROUP.mkv").to_s).to eq "/full/path/to/Showname.S02E01.720p.HDTV.x264-GROUP.mkv" + describe "#to_s" do + subject { Addic7ed::VideoFile.new("/full/path/to/Showname.S02E01.720p.HDTV.x264-GROUP.mkv") } + + it "returns file name as a string" do + expect(subject.to_s).to eq "/full/path/to/Showname.S02E01.720p.HDTV.x264-GROUP.mkv" end end - describe '#inspect' do - it 'prints a human-readable detailed version' do - expect(Addic7ed::VideoFile.new("Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv").inspect).to eq( -'Guesses for Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv: + describe "#inspect" do + let(:expected) do + <<-EOS +Guesses for Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv: show: Showname season: 2 episode: 1 tags: ["720P", "HDTV", "X264"] group: GROUP - distribution: DISTRIBUTION') + distribution: DISTRIBUTION +EOS + end + + subject { Addic7ed::VideoFile.new("Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv") } + + it "prints a human-readable detailed version" do + expect(subject.inspect).to eq expected.strip end end end diff --git a/spec/lib/addic7ed/services/download_subtitle_spec.rb b/spec/lib/addic7ed/services/download_subtitle_spec.rb index c1f3536..faa70f0 100644 --- a/spec/lib/addic7ed/services/download_subtitle_spec.rb +++ b/spec/lib/addic7ed/services/download_subtitle_spec.rb @@ -8,7 +8,10 @@ subject { described_class.new(url, filename, referer, 0) } describe "#call" do - let!(:http_stub) { stub_request(:get, url).to_return File.new("spec/responses/walking-dead-3-2-8_best_subtitle.http") } + let!(:http_stub) do + stub_request(:get, url) + .to_return File.new("spec/responses/walking-dead-3-2-8_best_subtitle.http") + end before { allow_any_instance_of(described_class).to receive(:write) } @@ -23,9 +26,14 @@ end context "when the HTTP request returns a HTTP redirection" do - let!(:http_stub) { stub_request(:get, url).to_return File.new("spec/responses/basic_redirection.http") } let!(:redirect_url) { "http://www.addic7ed.com/original/68018/4.redirected" } - let!(:redirect_stub) { stub_request(:get, redirect_url).to_return File.new("spec/responses/walking-dead-3-2-8_best_subtitle.http") } + let!(:http_stub) do + stub_request(:get, url).to_return File.new("spec/responses/basic_redirection.http") + end + let!(:redirect_stub) do + stub_request(:get, redirect_url) + .to_return File.new("spec/responses/walking-dead-3-2-8_best_subtitle.http") + end it "follows the redirection by calling itself with redirection URL" do subject.call @@ -34,11 +42,14 @@ end context "when it gets stuck in a redirection loop" do - let!(:redirect_stub) { stub_request(:get, redirect_url).to_return File.new("spec/responses/basic_redirection.http") } + let!(:redirect_stub) do + stub_request(:get, redirect_url) + .to_return File.new("spec/responses/basic_redirection.http") + end it "follows up to HTTP_REDIRECT_LIMIT redirections then raises a Addic7ed::DownloadError" do - expect(described_class).to receive(:new).exactly(described_class.const_get(:HTTP_REDIRECT_LIMIT) + 1).times.and_call_original - expect{ subject.call }.to raise_error Addic7ed::DownloadError + expect(described_class).to receive(:new).exactly(9).times.and_call_original + expect { subject.call }.to raise_error Addic7ed::DownloadError end end end @@ -47,7 +58,7 @@ let!(:http_stub) { stub_request(:get, url).to_timeout } it "raises Addic7ed::DownloadError" do - expect{ subject.call }.to raise_error Addic7ed::DownloadError + expect { subject.call }.to raise_error Addic7ed::DownloadError end end end @@ -62,7 +73,7 @@ before { allow(Kernel).to receive(:open).and_raise(IOError) } it "raises a Addic7ed::DownloadError error" do - expect{ subject.send(:write, "some content") }.to raise_error Addic7ed::DownloadError + expect { subject.send(:write, "some content") }.to raise_error Addic7ed::DownloadError end end end diff --git a/spec/lib/addic7ed/services/get_shows_list_spec.rb b/spec/lib/addic7ed/services/get_shows_list_spec.rb index 5db4f26..14fa0dd 100644 --- a/spec/lib/addic7ed/services/get_shows_list_spec.rb +++ b/spec/lib/addic7ed/services/get_shows_list_spec.rb @@ -1,19 +1,20 @@ require "spec_helper" describe Addic7ed::GetShowsList do - before { described_class.class_variable_set(:@@shows, nil) } + before { Singleton.__init__(described_class) } it "downloads Addic7ed homepage" do - subject.call + described_class.call expect(a_request(:get, "http://www.addic7ed.com")).to have_been_made end it "returns the list of shows names" do - expect(described_class.call).to include "Game of Thrones", "The Walking Dead", "Californication", "Breaking Bad", "Weeds" + some_shows = ["Game of Thrones", "The Walking Dead", "Californication", "Breaking Bad", "Weeds"] + expect(described_class.call).to include(*some_shows) end it "memoizes to minimize network requests" do - 2.times { subject.call } + 2.times { described_class.call } expect(a_request(:get, "http://www.addic7ed.com")).to have_been_made.once end end diff --git a/spec/lib/addic7ed/services/url_encode_show_name_spec.rb b/spec/lib/addic7ed/services/url_encode_show_name_spec.rb index 323873b..148a31e 100644 --- a/spec/lib/addic7ed/services/url_encode_show_name_spec.rb +++ b/spec/lib/addic7ed/services/url_encode_show_name_spec.rb @@ -36,6 +36,6 @@ end it "raises a ShowNotFound error when no matching show is found" do - expect{described_class.call("Not.an.existing.show")}.to raise_error Addic7ed::ShowNotFound + expect { described_class.call("Not.an.existing.show") }.to raise_error Addic7ed::ShowNotFound end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d746635..40e7c67 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,10 +1,10 @@ -unless RUBY_ENGINE == 'rbx' - require 'coveralls' +unless RUBY_ENGINE == "rbx" + require "coveralls" Coveralls.wear! end -require 'webmock/rspec' -require 'pry' +require "webmock/rspec" +require "pry" WebMock.disable_net_connect!(allow_localhost: true) @@ -21,7 +21,8 @@ config.raise_errors_for_deprecations! config.before(:each) do - stub_request(:get, "http://www.addic7ed.com").to_return(File.new("spec/responses/homepage.http")) + stub_request(:get, "http://www.addic7ed.com") + .to_return(File.new("spec/responses/homepage.http")) end end From c84d715748ddcafe73dc6a785344a9ffa981ee2e Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 15:02:24 +0400 Subject: [PATCH 19/89] Fix an error with JRuby --- lib/addic7ed/services/parse_subtitle.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/services/parse_subtitle.rb b/lib/addic7ed/services/parse_subtitle.rb index 3d3ec6c..3741781 100644 --- a/lib/addic7ed/services/parse_subtitle.rb +++ b/lib/addic7ed/services/parse_subtitle.rb @@ -6,7 +6,7 @@ class ParseSubtitle attr_reader :subtitle_node - FIELDS = %i(version language status url source hi downloads comment).freeze + FIELDS = [:version, :language, :status, :url, :source, :hi, :downloads, :comment].freeze def initialize(subtitle_node) @subtitle_node = subtitle_node From 4ab73fcf787ca4f4874410c1dc24086347dd371a Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 15:13:08 +0400 Subject: [PATCH 20/89] Enable Rubocop for CodeClimate --- .codeclimate.yml | 3 +++ .rubocop.yml | 8 ++++++++ lib/addic7ed/models/video_file.rb | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..789aa8b --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,3 @@ +engines: + rubocop: + enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index f97164e..884c562 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,8 @@ +AllCops: + DisplayCopNames: true + DisplayStyleGuide: true + ExtraDetails: true + Style/DoubleNegation: Enabled: false @@ -9,3 +14,6 @@ Metrics/LineLength: Style/AccessModifierIndentation: EnforcedStyle: outdent + +Style/Documentation: + Enabled: false diff --git a/lib/addic7ed/models/video_file.rb b/lib/addic7ed/models/video_file.rb index f119297..b87eeab 100644 --- a/lib/addic7ed/models/video_file.rb +++ b/lib/addic7ed/models/video_file.rb @@ -1,6 +1,6 @@ module Addic7ed class VideoFile - TVSHOW_REGEX = /\A(?.*\w)[\[\. ]+S?(?\d{1,2})[-\. ]?[EX]?(?\d{2})([-\. ]?[EX]?\d{2})*[\]\. ]+(?.*)-(?\w*)\[?(?\w*)\]?(\.\w{3})?\z/i # rubocop:disable Style/LineLength + TVSHOW_REGEX = /\A(?.*\w)[\[\. ]+S?(?\d{1,2})[-\. ]?[EX]?(?\d{2})([-\. ]?[EX]?\d{2})*[\]\. ]+(?.*)-(?\w*)\[?(?\w*)\]?(\.\w{3})?\z/i # rubocop:disable Metrics/LineLength attr_reader :filename, :regexp_matches From 843bab9eb1f2ee49825e3a68e2f7911af4be777b Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 15:26:57 +0400 Subject: [PATCH 21/89] Use config generated by CodeClimate --- .codeclimate.yml | 13 + .rubocop.yml | 1151 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1160 insertions(+), 4 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 789aa8b..b86b6ea 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,3 +1,16 @@ +--- engines: + duplication: + enabled: true + config: + languages: + - ruby + fixme: + enabled: true rubocop: enabled: true +ratings: + paths: + - "**.rb" +exclude_paths: +- spec/ diff --git a/.rubocop.yml b/.rubocop.yml index 884c562..357b564 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,19 +1,1162 @@ AllCops: + DisabledByDefault: true DisplayCopNames: true DisplayStyleGuide: true ExtraDetails: true -Style/DoubleNegation: - Enabled: false +#################### Lint ################################ -Style/StringLiterals: - EnforcedStyle: double_quotes +Lint/AmbiguousOperator: + Description: >- + Checks for ambiguous operators in the first argument of a + method invocation without parentheses. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' + Enabled: true + +Lint/AmbiguousRegexpLiteral: + Description: >- + Checks for ambiguous regexp literals in the first argument of + a method invocation without parenthesis. + Enabled: true + +Lint/AssignmentInCondition: + Description: "Don't use assignment in conditions." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' + Enabled: true + +Lint/BlockAlignment: + Description: 'Align block ends correctly.' + Enabled: true + +Lint/CircularArgumentReference: + Description: "Don't refer to the keyword argument in the default value." + Enabled: true + +Lint/ConditionPosition: + Description: >- + Checks for condition placed in a confusing position relative to + the keyword. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' + Enabled: true + +Lint/Debugger: + Description: 'Check for debugger calls.' + Enabled: true + +Lint/DefEndAlignment: + Description: 'Align ends corresponding to defs correctly.' + Enabled: true + +Lint/DeprecatedClassMethods: + Description: 'Check for deprecated class method calls.' + Enabled: true + +Lint/DuplicateMethods: + Description: 'Check for duplicate methods calls.' + Enabled: true + +Lint/EachWithObjectArgument: + Description: 'Check for immutable argument given to each_with_object.' + Enabled: true + +Lint/ElseLayout: + Description: 'Check for odd code arrangement in an else block.' + Enabled: true + +Lint/EmptyEnsure: + Description: 'Checks for empty ensure block.' + Enabled: true + +Lint/EmptyInterpolation: + Description: 'Checks for empty string interpolation.' + Enabled: true + +Lint/EndAlignment: + Description: 'Align ends correctly.' + Enabled: true + +Lint/EndInMethod: + Description: 'END blocks should not be placed inside method definitions.' + Enabled: true + +Lint/EnsureReturn: + Description: 'Do not use return in an ensure block.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure' + Enabled: true + +Lint/Eval: + Description: 'The use of eval represents a serious security risk.' + Enabled: true + +Lint/FormatParameterMismatch: + Description: 'The number of parameters to format/sprint must match the fields.' + Enabled: true + +Lint/HandleExceptions: + Description: "Don't suppress exception." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' + Enabled: true + +Lint/InvalidCharacterLiteral: + Description: >- + Checks for invalid character literals with a non-escaped + whitespace character. + Enabled: true + +Lint/LiteralInCondition: + Description: 'Checks of literals used in conditions.' + Enabled: true + +Lint/LiteralInInterpolation: + Description: 'Checks for literals used in interpolation.' + Enabled: true + +Lint/Loop: + Description: >- + Use Kernel#loop with break rather than begin/end/until or + begin/end/while for post-loop tests. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' + Enabled: true + +Lint/NestedMethodDefinition: + Description: 'Do not use nested method definitions.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' + Enabled: true + +Lint/NonLocalExitFromIterator: + Description: 'Do not use return in iterator to cause non-local exit.' + Enabled: true + +Lint/ParenthesesAsGroupedExpression: + Description: >- + Checks for method calls with a space before the opening + parenthesis. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' + Enabled: true + +Lint/RequireParentheses: + Description: >- + Use parentheses in the method call to avoid confusion + about precedence. + Enabled: true + +Lint/RescueException: + Description: 'Avoid rescuing the Exception class.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' + Enabled: true + +Lint/ShadowingOuterLocalVariable: + Description: >- + Do not use the same name as outer local variable + for block arguments or block local variables. + Enabled: true + +Lint/StringConversionInInterpolation: + Description: 'Checks for Object#to_s usage in string interpolation.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s' + Enabled: true + +Lint/UnderscorePrefixedVariableName: + Description: 'Do not use prefix `_` for a variable that is used.' + Enabled: true + +Lint/UnneededDisable: + Description: >- + Checks for rubocop:disable comments that can be removed. + Note: this cop is not disabled when disabling all cops. + It must be explicitly disabled. + Enabled: true + +Lint/UnusedBlockArgument: + Description: 'Checks for unused block arguments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' + Enabled: true + +Lint/UnusedMethodArgument: + Description: 'Checks for unused method arguments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' + Enabled: true + +Lint/UnreachableCode: + Description: 'Unreachable code.' + Enabled: true + +Lint/UselessAccessModifier: + Description: 'Checks for useless access modifiers.' + Enabled: true + +Lint/UselessAssignment: + Description: 'Checks for useless assignment to a local variable.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' + Enabled: true + +Lint/UselessComparison: + Description: 'Checks for comparison of something with itself.' + Enabled: true + +Lint/UselessElseWithoutRescue: + Description: 'Checks for useless `else` in `begin..end` without `rescue`.' + Enabled: true + +Lint/UselessSetterCall: + Description: 'Checks for useless setter call to a local variable.' + Enabled: true + +Lint/Void: + Description: 'Possible use of operator/literal/variable in void context.' + Enabled: true + +###################### Metrics #################################### + +Metrics/AbcSize: + Description: >- + A calculated magnitude based on number of assignments, + branches, and conditions. + Reference: 'http://c2.com/cgi/wiki?AbcMetric' + Enabled: true + Max: 20 + +Metrics/BlockNesting: + Description: 'Avoid excessive block nesting' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' + Enabled: true + Max: 4 + +Metrics/ClassLength: + Description: 'Avoid classes longer than 250 lines of code.' + Enabled: true + Max: 250 + +Metrics/CyclomaticComplexity: + Description: >- + A complexity metric that is strongly correlated to the number + of test cases needed to validate a method. + Enabled: true Metrics/LineLength: + Description: 'Limit lines to 80 characters.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' Max: 100 + Enabled: true + +Metrics/MethodLength: + Description: 'Avoid methods longer than 30 lines of code.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' + Enabled: true + Max: 30 + +Metrics/ModuleLength: + Description: 'Avoid modules longer than 250 lines of code.' + Enabled: true + Max: 250 + +Metrics/ParameterLists: + Description: 'Avoid parameter lists longer than three or four parameters.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' + Enabled: true + +Metrics/PerceivedComplexity: + Description: >- + A complexity metric geared towards measuring complexity for a + human reader. + Enabled: true + +##################### Performance ############################# + +Performance/Count: + Description: >- + Use `count` instead of `select...size`, `reject...size`, + `select...count`, `reject...count`, `select...length`, + and `reject...length`. + Enabled: true + +Performance/Detect: + Description: >- + Use `detect` instead of `select.first`, `find_all.first`, + `select.last`, and `find_all.last`. + Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' + Enabled: true + +Performance/FlatMap: + Description: >- + Use `Enumerable#flat_map` + instead of `Enumerable#map...Array#flatten(1)` + or `Enumberable#collect..Array#flatten(1)` + Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code' + Enabled: true + EnabledForFlattenWithoutParams: false + # If enabled, this cop will warn about usages of + # `flatten` being called without any parameters. + # This can be dangerous since `flat_map` will only flatten 1 level, and + # `flatten` without any parameters can flatten multiple levels. + +Performance/ReverseEach: + Description: 'Use `reverse_each` instead of `reverse.each`.' + Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' + Enabled: true + +Performance/Sample: + Description: >- + Use `sample` instead of `shuffle.first`, + `shuffle.last`, and `shuffle[Fixnum]`. + Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' + Enabled: true + +Performance/Size: + Description: >- + Use `size` instead of `count` for counting + the number of elements in `Array` and `Hash`. + Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code' + Enabled: true + +Performance/StringReplacement: + Description: >- + Use `tr` instead of `gsub` when you are replacing the same + number of characters. Use `delete` instead of `gsub` when + you are deleting characters. + Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code' + Enabled: true + +##################### Rails ################################## + +Rails/ActionFilter: + Description: 'Enforces consistent use of action filter methods.' + Enabled: true + +Rails/Date: + Description: >- + Checks the correct usage of date aware methods, + such as Date.today, Date.current etc. + Enabled: true + +Rails/Delegate: + Description: 'Prefer delegate method for delegations.' + Enabled: true + +Rails/FindBy: + Description: 'Prefer find_by over where.first.' + Enabled: true + +Rails/FindEach: + Description: 'Prefer all.find_each over all.find.' + Enabled: true + +Rails/HasAndBelongsToMany: + Description: 'Prefer has_many :through to has_and_belongs_to_many.' + Enabled: true + +Rails/Output: + Description: 'Checks for calls to puts, print, etc.' + Enabled: true + +Rails/ReadWriteAttribute: + Description: >- + Checks for read_attribute(:attr) and + write_attribute(:attr, val). + Enabled: true + +Rails/ScopeArgs: + Description: 'Checks the arguments of ActiveRecord scopes.' + Enabled: true + +Rails/TimeZone: + Description: 'Checks the correct usage of time zone aware methods.' + StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' + Reference: 'http://danilenko.org/2012/7/6/rails_timezones' + Enabled: true + +Rails/Validation: + Description: 'Use validates :attribute, hash of validations.' + Enabled: true + +################## Style ################################# Style/AccessModifierIndentation: + Description: Check indentation of private/protected visibility modifiers. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected' EnforcedStyle: outdent + Enabled: true + +Style/AccessorMethodName: + Description: Check the naming of accessor methods for get_/set_. + Enabled: true + +Style/Alias: + Description: 'Use alias_method instead of alias.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' + Enabled: true + +Style/AlignArray: + Description: >- + Align the elements of an array literal if they span more than + one line. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays' + Enabled: true + +Style/AlignHash: + Description: >- + Align the elements of a hash literal if they span more than + one line. + Enabled: true + +Style/AlignParameters: + Description: >- + Align the parameters of a method call if they span more + than one line. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' + Enabled: true + +Style/AndOr: + Description: 'Use &&/|| instead of and/or.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or' + Enabled: true + +Style/ArrayJoin: + Description: 'Use Array#join instead of Array#*.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' + Enabled: true + +Style/AsciiComments: + Description: 'Use only ascii symbols in comments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' + Enabled: true + +Style/AsciiIdentifiers: + Description: 'Use only ascii symbols in identifiers.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' + Enabled: true + +Style/Attr: + Description: 'Checks for uses of Module#attr.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' + Enabled: true + +Style/BeginBlock: + Description: 'Avoid the use of BEGIN blocks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' + Enabled: true + +Style/BarePercentLiterals: + Description: 'Checks if usage of %() or %Q() matches configuration.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand' + Enabled: true + +Style/BlockComments: + Description: 'Do not use block comments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' + Enabled: true + +Style/BlockEndNewline: + Description: 'Put end statement of multiline block on its own line.' + Enabled: true + +Style/BlockDelimiters: + Description: >- + Avoid using {...} for multi-line blocks (multiline chaining is + always ugly). + Prefer {...} over do...end for single-line blocks. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' + Enabled: true + +Style/BracesAroundHashParameters: + Description: 'Enforce braces style around hash parameters.' + Enabled: true + +Style/CaseEquality: + Description: 'Avoid explicit use of the case equality operator(===).' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' + Enabled: true + +Style/CaseIndentation: + Description: 'Indentation of when in a case/when/[else/]end.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case' + Enabled: true + +Style/CharacterLiteral: + Description: 'Checks for uses of character literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' + Enabled: true + +Style/ClassAndModuleCamelCase: + Description: 'Use CamelCase for classes and modules.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' + Enabled: true + +Style/ClassAndModuleChildren: + Description: 'Checks style of children classes and modules.' + Enabled: true + +Style/ClassCheck: + Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' + Enabled: true + +Style/ClassMethods: + Description: 'Use self when defining module/class methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-class-methods' + Enabled: true + +Style/ClassVars: + Description: 'Avoid the use of class variables.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' + Enabled: true + +Style/ClosingParenthesisIndentation: + Description: 'Checks the indentation of hanging closing parentheses.' + Enabled: true + +Style/ColonMethodCall: + Description: 'Do not use :: for method call.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' + Enabled: true + +Style/CommandLiteral: + Description: 'Use `` or %x around command literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x' + Enabled: true + +Style/CommentAnnotation: + Description: 'Checks formatting of annotation comments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' + Enabled: true + +Style/CommentIndentation: + Description: 'Indentation of comments.' + Enabled: true + +Style/ConstantName: + Description: 'Constants should use SCREAMING_SNAKE_CASE.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' + Enabled: true + +Style/DefWithParentheses: + Description: 'Use def with parentheses when there are arguments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' + Enabled: true + +Style/DeprecatedHashMethods: + Description: 'Checks for use of deprecated Hash methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key' + Enabled: true Style/Documentation: + Description: 'Document classes and non-namespace modules.' + Enabled: false + +Style/DotPosition: + Description: 'Checks the position of the dot in multi-line method calls.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' + Enabled: true + +Style/DoubleNegation: + Description: 'Checks for uses of double negation (!!).' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' Enabled: false + +Style/EachWithObject: + Description: 'Prefer `each_with_object` over `inject` or `reduce`.' + Enabled: true + +Style/ElseAlignment: + Description: 'Align elses and elsifs correctly.' + Enabled: true + +Style/EmptyElse: + Description: 'Avoid empty else-clauses.' + Enabled: true + +Style/EmptyLineBetweenDefs: + Description: 'Use empty lines between defs.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' + Enabled: true + +Style/EmptyLines: + Description: "Don't use several empty lines in a row." + Enabled: true + +Style/EmptyLinesAroundAccessModifier: + Description: "Keep blank lines around access modifiers." + Enabled: true + +Style/EmptyLinesAroundBlockBody: + Description: "Keeps track of empty lines around block bodies." + Enabled: true + +Style/EmptyLinesAroundClassBody: + Description: "Keeps track of empty lines around class bodies." + Enabled: true + +Style/EmptyLinesAroundModuleBody: + Description: "Keeps track of empty lines around module bodies." + Enabled: true + +Style/EmptyLinesAroundMethodBody: + Description: "Keeps track of empty lines around method bodies." + Enabled: true + +Style/EmptyLiteral: + Description: 'Prefer literals to Array.new/Hash.new/String.new.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' + Enabled: true + +Style/EndBlock: + Description: 'Avoid the use of END blocks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' + Enabled: true + +Style/EndOfLine: + Description: 'Use Unix-style line endings.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' + Enabled: true + +Style/EvenOdd: + Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' + Enabled: true + +Style/ExtraSpacing: + Description: 'Do not use unnecessary spacing.' + Enabled: true + +Style/FileName: + Description: 'Use snake_case for source file names.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' + Enabled: true + +Style/InitialIndentation: + Description: >- + Checks the indentation of the first non-blank non-comment line in a file. + Enabled: true + +Style/FirstParameterIndentation: + Description: 'Checks the indentation of the first parameter in a method call.' + Enabled: true + +Style/FlipFlop: + Description: 'Checks for flip flops' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' + Enabled: true + +Style/For: + Description: 'Checks use of for or each in multiline loops.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops' + Enabled: true + +Style/FormatString: + Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' + Enabled: true + +Style/GlobalVars: + Description: 'Do not introduce global variables.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' + Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' + Enabled: true + +Style/GuardClause: + Description: 'Check for conditionals that can be replaced with guard clauses' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' + Enabled: true + +Style/HashSyntax: + Description: >- + Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax + { :a => 1, :b => 2 }. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals' + Enabled: true + +Style/IfUnlessModifier: + Description: >- + Favor modifier if/unless usage when you have a + single-line body. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' + Enabled: true + +Style/IfWithSemicolon: + Description: 'Do not use if x; .... Use the ternary operator instead.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' + Enabled: true + +Style/IndentationConsistency: + Description: 'Keep indentation straight.' + Enabled: true + +Style/IndentationWidth: + Description: 'Use 2 spaces for indentation.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' + Enabled: true + +Style/IndentArray: + Description: >- + Checks the indentation of the first element in an array + literal. + Enabled: true + +Style/IndentHash: + Description: 'Checks the indentation of the first key in a hash literal.' + Enabled: true + +Style/InfiniteLoop: + Description: 'Use Kernel#loop for infinite loops.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop' + Enabled: true + +Style/Lambda: + Description: 'Use the new lambda literal syntax for single-line blocks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' + Enabled: true + +Style/LambdaCall: + Description: 'Use lambda.call(...) instead of lambda.(...).' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' + Enabled: true + +Style/LeadingCommentSpace: + Description: 'Comments should start with a space.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space' + Enabled: true + +Style/LineEndConcatenation: + Description: >- + Use \ instead of + or << to concatenate two string literals at + line end. + Enabled: true + +Style/MethodCallParentheses: + Description: 'Do not use parentheses for method calls with no arguments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens' + Enabled: true + +Style/MethodDefParentheses: + Description: >- + Checks if the method definitions have or don't have + parentheses. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' + Enabled: true + +Style/MethodName: + Description: 'Use the configured style when naming methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' + Enabled: true + +Style/ModuleFunction: + Description: 'Checks for usage of `extend self` in modules.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' + Enabled: true + +Style/MultilineBlockChain: + Description: 'Avoid multi-line chains of blocks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' + Enabled: true + +Style/MultilineBlockLayout: + Description: 'Ensures newlines after multiline block do statements.' + Enabled: true + +Style/MultilineIfThen: + Description: 'Do not use then for multi-line if/unless.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then' + Enabled: true + +Style/MultilineOperationIndentation: + Description: >- + Checks indentation of binary operations that span more than + one line. + Enabled: true + +Style/MultilineTernaryOperator: + Description: >- + Avoid multi-line ?: (the ternary operator); + use if/unless instead. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary' + Enabled: true + +Style/NegatedIf: + Description: >- + Favor unless over if for negative conditions + (or control flow or). + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' + Enabled: true + +Style/NegatedWhile: + Description: 'Favor until over while for negative conditions.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' + Enabled: true + +Style/NestedTernaryOperator: + Description: 'Use one expression per branch in a ternary operator.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary' + Enabled: true + +Style/Next: + Description: 'Use `next` to skip iteration instead of a condition at the end.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' + Enabled: true + +Style/NilComparison: + Description: 'Prefer x.nil? to x == nil.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' + Enabled: true + +Style/NonNilCheck: + Description: 'Checks for redundant nil checks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks' + Enabled: true + +Style/Not: + Description: 'Use ! instead of not.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' + Enabled: true + +Style/NumericLiterals: + Description: >- + Add underscores to large numeric literals to improve their + readability. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' + Enabled: true + +Style/OneLineConditional: + Description: >- + Favor the ternary operator(?:) over + if/then/else/end constructs. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' + Enabled: true + +Style/OpMethod: + Description: 'When defining binary operators, name the argument other.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' + Enabled: true + +Style/OptionalArguments: + Description: >- + Checks for optional arguments that do not appear at the end + of the argument list + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#optional-arguments' + Enabled: true + +Style/ParallelAssignment: + Description: >- + Check for simple usages of parallel assignment. + It will only warn when the number of variables + matches on both sides of the assignment. + This also provides performance benefits + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment' + Enabled: true + +Style/ParenthesesAroundCondition: + Description: >- + Don't use parentheses around the condition of an + if/unless/while. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if' + Enabled: true + +Style/PercentLiteralDelimiters: + Description: 'Use `%`-literal delimiters consistently' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' + Enabled: true + +Style/PercentQLiterals: + Description: 'Checks if uses of %Q/%q match the configured preference.' + Enabled: true + +Style/PerlBackrefs: + Description: 'Avoid Perl-style regex back references.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' + Enabled: true + +Style/PredicateName: + Description: 'Check the names of predicate methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' + Enabled: true + +Style/Proc: + Description: 'Use proc instead of Proc.new.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' + Enabled: true + +Style/RaiseArgs: + Description: 'Checks the arguments passed to raise/fail.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' + Enabled: true + +Style/RedundantBegin: + Description: "Don't use begin blocks when they are not needed." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit' + Enabled: true + +Style/RedundantException: + Description: "Checks for an obsolete RuntimeException argument in raise/fail." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror' + Enabled: true + +Style/RedundantReturn: + Description: "Don't use return where it's not required." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' + Enabled: true + +Style/RedundantSelf: + Description: "Don't use self where it's not needed." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required' + Enabled: true + +Style/RegexpLiteral: + Description: 'Use / or %r around regular expressions.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' + Enabled: true + +Style/RescueEnsureAlignment: + Description: 'Align rescues and ensures correctly.' + Enabled: true + +Style/RescueModifier: + Description: 'Avoid using rescue in its modifier form.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers' + Enabled: true + +Style/SelfAssignment: + Description: >- + Checks for places where self-assignment shorthand should have + been used. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' + Enabled: true + +Style/Semicolon: + Description: "Don't use semicolons to terminate expressions." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' + Enabled: true + +Style/SignalException: + Description: 'Checks for proper usage of fail and raise.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' + Enabled: true + +Style/SingleLineBlockParams: + Description: 'Enforces the names of some block params.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' + Enabled: true + +Style/SingleLineMethods: + Description: 'Avoid single-line methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' + Enabled: true + +Style/SpaceBeforeFirstArg: + Description: >- + Checks that exactly one space is used between a method name + and the first argument for method calls without parentheses. + Enabled: true + +Style/SpaceAfterColon: + Description: 'Use spaces after colons.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: true + +Style/SpaceAfterComma: + Description: 'Use spaces after commas.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: true + +Style/SpaceAroundKeyword: + Description: 'Use spaces around keywords.' + Enabled: true + +Style/SpaceAfterMethodName: + Description: >- + Do not put a space between a method name and the opening + parenthesis in a method definition. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' + Enabled: true + +Style/SpaceAfterNot: + Description: Tracks redundant space after the ! operator. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang' + Enabled: true + +Style/SpaceAfterSemicolon: + Description: 'Use spaces after semicolons.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: true + +Style/SpaceBeforeBlockBraces: + Description: >- + Checks that the left block brace has or doesn't have space + before it. + Enabled: true + +Style/SpaceBeforeComma: + Description: 'No spaces before commas.' + Enabled: true + +Style/SpaceBeforeComment: + Description: >- + Checks for missing space between code and a comment on the + same line. + Enabled: true + +Style/SpaceBeforeSemicolon: + Description: 'No spaces before semicolons.' + Enabled: true + +Style/SpaceInsideBlockBraces: + Description: >- + Checks that block braces have or don't have surrounding space. + For blocks taking parameters, checks that the left brace has + or doesn't have trailing space. + Enabled: true + +Style/SpaceAroundBlockParameters: + Description: 'Checks the spacing inside and after block parameters pipes.' + Enabled: true + +Style/SpaceAroundEqualsInParameterDefault: + Description: >- + Checks that the equals signs in parameter default assignments + have or don't have surrounding space depending on + configuration. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals' + Enabled: true + +Style/SpaceAroundOperators: + Description: 'Use a single space around operators.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: true + +Style/SpaceInsideBrackets: + Description: 'No spaces after [ or before ].' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' + Enabled: true + +Style/SpaceInsideHashLiteralBraces: + Description: "Use spaces inside hash literal braces - or don't." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: true + +Style/SpaceInsideParens: + Description: 'No spaces after ( or before ).' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' + Enabled: true + +Style/SpaceInsideRangeLiteral: + Description: 'No spaces inside range literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals' + Enabled: true + +Style/SpaceInsideStringInterpolation: + Description: 'Checks for padding/surrounding spaces inside string interpolation.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#string-interpolation' + Enabled: true + +Style/SpecialGlobalVars: + Description: 'Avoid Perl-style global variables.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' + Enabled: true + +Style/StringLiterals: + Description: 'Checks if uses of quotes match the configured preference.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' + EnforcedStyle: double_quotes + Enabled: true + +Style/StringLiteralsInInterpolation: + Description: >- + Checks if uses of quotes inside expressions in interpolated + strings match the configured preference. + Enabled: true + +Style/StructInheritance: + Description: 'Checks for inheritance from Struct.new.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new' + Enabled: true + +Style/SymbolLiteral: + Description: 'Use plain symbols instead of string symbols when possible.' + Enabled: true + +Style/SymbolProc: + Description: 'Use symbols as procs instead of blocks when possible.' + Enabled: true + +Style/Tab: + Description: 'No hard tabs.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' + Enabled: true + +Style/TrailingBlankLines: + Description: 'Checks trailing blank lines and final newline.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' + Enabled: true + +Style/TrailingCommaInArguments: + Description: 'Checks for trailing comma in parameter lists.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma' + Enabled: true + +Style/TrailingCommaInLiteral: + Description: 'Checks for trailing comma in literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' + Enabled: true + +Style/TrailingWhitespace: + Description: 'Avoid trailing whitespace.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace' + Enabled: true + +Style/TrivialAccessors: + Description: 'Prefer attr_* methods to trivial readers/writers.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' + Enabled: true + +Style/UnlessElse: + Description: >- + Do not use unless with else. Rewrite these with the positive + case first. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless' + Enabled: true + +Style/UnneededCapitalW: + Description: 'Checks for %W when interpolation is not needed.' + Enabled: true + +Style/UnneededPercentQ: + Description: 'Checks for %q/%Q when single quotes or double quotes would do.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q' + Enabled: true + +Style/TrailingUnderscoreVariable: + Description: >- + Checks for the usage of unneeded trailing underscores at the + end of parallel variable assignment. + Enabled: true + +Style/VariableInterpolation: + Description: >- + Don't interpolate global, instance and class variables + directly in strings. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' + Enabled: true + +Style/VariableName: + Description: 'Use the configured style when naming variables.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' + Enabled: true + +Style/WhenThen: + Description: 'Use when x then ... for one-line cases.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' + Enabled: true + +Style/WhileUntilDo: + Description: 'Checks for redundant do after while or until.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do' + Enabled: true + +Style/WhileUntilModifier: + Description: >- + Favor modifier while/until usage when you have a + single-line body. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' + Enabled: true + +Style/WordArray: + Description: 'Use %w or %W for arrays of words.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' + Enabled: true From e40b9f516220f95c0f02836def65bad3d01cb98d Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 15:52:38 +0400 Subject: [PATCH 22/89] Enable Brakeman engine for Codeship and disable Fixme engine --- .codeclimate.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index b86b6ea..53e74d3 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -6,9 +6,11 @@ engines: languages: - ruby fixme: - enabled: true + enabled: false rubocop: enabled: true + brakeman: + enabled: true ratings: paths: - "**.rb" From 2deebfe24099a7b7ea673220cca582f55bc3780e Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 15:57:24 +0400 Subject: [PATCH 23/89] Don't use single-line Rubocop disables --- lib/addic7ed/common.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/addic7ed/common.rb b/lib/addic7ed/common.rb index 81a334a..998114b 100644 --- a/lib/addic7ed/common.rb +++ b/lib/addic7ed/common.rb @@ -1,6 +1,7 @@ # encoding: UTF-8 -module Addic7ed # rubocop:disable Metrics/ModuleLength +# rubocop:disable Metrics/ModuleLength +module Addic7ed SHOWS_URL = "http://www.addic7ed.com/ajax_getShows.php".freeze EPISODES_URL = "http://www.addic7ed.com/ajax_getEpisodes.php".freeze EPISODE_REDIRECT_URL = "http://www.addic7ed.com/re_episode.php".freeze @@ -1288,3 +1289,4 @@ module Addic7ed # rubocop:disable Metrics/ModuleLength ].freeze # rubocop:enable Metrics/LineLength end +# rubocop:enable Metrics/ModuleLength From 17c2bab045d42e9004995ac4c5a61c9e5d370b53 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 16:57:45 +0400 Subject: [PATCH 24/89] Move user agents, languages and URLs to a config file --- README.md | 3 +- lib/addic7ed/common.rb | 1288 +------------------- lib/addic7ed/config.json | 1417 ++++++++++++++++++++++ lib/addic7ed/services/get_shows_list.rb | 2 + spec/lib/addic7ed/common_spec.rb | 10 +- spec/lib/addic7ed/models/episode_spec.rb | 24 +- 6 files changed, 1444 insertions(+), 1300 deletions(-) create mode 100644 lib/addic7ed/config.json diff --git a/README.md b/README.md index e9c558f..86f8d38 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,11 @@ Ruby command-line script to fetch subtitles on Addic7ed * [x] rename `ShowList` and make it a service object * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") -* [ ] allow language to be specified both by string or by symbol +* [x] use symbols rather than strings for languages * [ ] write code documentation * [ ] update README * [x] add Rubocop +* [x] move user agents and languages to a config file * [ ] add specs for parsing * [ ] move CLI to a separate gem (and use Thor or similar) diff --git a/lib/addic7ed/common.rb b/lib/addic7ed/common.rb index 998114b..66c4ec4 100644 --- a/lib/addic7ed/common.rb +++ b/lib/addic7ed/common.rb @@ -1,10 +1,11 @@ -# encoding: UTF-8 +require "json" -# rubocop:disable Metrics/ModuleLength module Addic7ed - SHOWS_URL = "http://www.addic7ed.com/ajax_getShows.php".freeze - EPISODES_URL = "http://www.addic7ed.com/ajax_getEpisodes.php".freeze - EPISODE_REDIRECT_URL = "http://www.addic7ed.com/re_episode.php".freeze + CONFIG = JSON.load(File.open("lib/addic7ed/config.json"), nil, symbolize_names: true).freeze + LANGUAGES = CONFIG[:languages].freeze + USER_AGENTS = CONFIG[:user_agents].freeze + SHOWS_URL = CONFIG[:urls][:shows].freeze + EPISODES_URL = CONFIG[:urls][:episodes].freeze COMPATIBILITY_720P = { "LOL" => "DIMENSION", @@ -12,1281 +13,4 @@ module Addic7ed "XII" => "IMMERSE", "ASAP" => "IMMERSE" }.freeze - - LANGUAGES = { - "ar" => { name: "Arabic", id: 38 }, - "az" => { name: "Azerbaijani", id: 48 }, - "bn" => { name: "Bengali", id: 47 }, - "bs" => { name: "Bosnian", id: 44 }, - "bg" => { name: "Bulgarian", id: 35 }, - "ca" => { name: "Català", id: 12 }, - "cn" => { name: "Chinese (Simplified)", id: 41 }, - "zh" => { name: "Chinese (Traditional)", id: 24 }, - "hr" => { name: "Croatian", id: 31 }, - "cs" => { name: "Czech", id: 14 }, - "da" => { name: "Danish", id: 30 }, - "nl" => { name: "Dutch", id: 17 }, - "en" => { name: "English", id: 1 }, - "eu" => { name: "Euskera", id: 13 }, - "fi" => { name: "Finnish", id: 28 }, - "fr" => { name: "French", id: 8 }, - "gl" => { name: "Galego", id: 15 }, - "de" => { name: "German", id: 11 }, - "el" => { name: "Greek", id: 27 }, - "he" => { name: "Hebrew", id: 23 }, - "hu" => { name: "Hungarian", id: 20 }, - "id" => { name: "Indonesian", id: 37 }, - "it" => { name: "Italian", id: 7 }, - "ja" => { name: "Japanese", id: 32 }, - "ko" => { name: "Korean", id: 42 }, - "mk" => { name: "Macedonian", id: 49 }, - "ms" => { name: "Malay", id: 40 }, - "no" => { name: "Norwegian", id: 29 }, - "fa" => { name: "Persian", id: 43 }, - "pl" => { name: "Polish", id: 21 }, - "pt" => { name: "Portuguese", id: 9 }, - "pt-br" => { name: "Portuguese (Brazilian)", id: 10 }, - "ro" => { name: "Romanian", id: 26 }, - "ru" => { name: "Russian", id: 19 }, - "sr" => { name: "Serbian (Cyrillic)", id: 39 }, - "sr-la" => { name: "Serbian (Latin)", id: 36 }, - "sk" => { name: "Slovak", id: 25 }, - "sl" => { name: "Slovenian", id: 22 }, - "es" => { name: "Spanish", id: 4 }, - "es-la" => { name: "Spanish (Latin America)", id: 6 }, - "es-es" => { name: "Spanish (Spain)", id: 5 }, - "sv" => { name: "Swedish", id: 18 }, - "th" => { name: "Thai", id: 46 }, - "tr" => { name: "Turkish", id: 16 }, - "vi" => { name: "Viet namese", id: 45 } - }.freeze - - # rubocop:disable Metrics/LineLength - USER_AGENTS = [ - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0a2) Gecko/20111101 Firefox/9.0a2", - "Mozilla/5.0 (Windows NT 6.2; rv:9.0.1) Gecko/20100101 Firefox/9.0.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110613 Firefox/6.0a2", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110612 Firefox/6.0a2", - "Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0", - "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20110814 Firefox/6.0", - "Mozilla/5.0 (Windows NT 5.1; rv:6.0) Gecko/20100101 Firefox/6.0 FirePHP/0.6", - "Mozilla/5.0 (Windows NT 5.0; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0", - "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:5.0a2) Gecko/20110524 Firefox/5.0a2", - "Mozilla/5.0 (Windows NT 6.1; U; ru; rv:5.0.1.6) Gecko/20110501 Firefox/5.0.1 Firefox/5.0.1", - "Mozilla/3.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/5.0.1", - "Mozilla/5.0 (X11; U; Linux i586; de; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (X11; U; Linux amd64; rv:5.0) Gecko/20100101 Firefox/5.0 (Debian)", - "Mozilla/5.0 (X11; U; Linux amd64; en-US; rv:5.0) Gecko/20110619 Firefox/5.0", - "Mozilla/5.0 (X11; Linux) Gecko Firefox/5.0", - "Mozilla/5.0 (X11; Linux x86_64; rv:5.0) Gecko/20100101 Firefox/5.0 FirePHP/0.5", - "Mozilla/5.0 (X11; Linux x86_64; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0", - "Mozilla/5.0 (X11; Linux x86_64) Gecko Firefox/5.0", - "Mozilla/5.0 (X11; Linux ppc; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (X11; Linux AMD64) Gecko Firefox/5.0", - "Mozilla/5.0 (X11; FreeBSD amd64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20110619 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 6.1.1; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.2; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.1; U; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.0; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (Windows NT 5.0; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (U; Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20110324 Firefox/4.2a1pre", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20100101 Firefox/4.2a1pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110324 Firefox/4.2a1pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110323 Firefox/4.2a1pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110208 Firefox/4.2a1pre", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.0b9pre) Gecko/20110111 Firefox/4.0b9pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b9pre) Gecko/20101228 Firefox/4.0b9pre", - "Mozilla/5.0 (Windows NT 5.1; rv:2.0b9pre) Gecko/20110105 Firefox/4.0b9pre", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b8pre) Gecko/20101114 Firefox/4.0b8pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101213 Firefox/4.0b8pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101128 Firefox/4.0b8pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101114 Firefox/4.0b8pre", - "Mozilla/5.0 (Windows NT 5.1; rv:2.0b8pre) Gecko/20101127 Firefox/4.0b8pre", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8", - "Mozilla/4.0 (compatible; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8)", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b7pre) Gecko/20100921 Firefox/4.0b7pre", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b7) Gecko/20101111 Firefox/4.0b7", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b7) Gecko/20100101 Firefox/4.0b7", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre Firefox/4.0b6pre", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.0b4) Gecko/20100818 Firefox/4.0b4", - "Mozilla/5.0 (X11; Linux i686; rv:2.0b3pre) Gecko/20100731 Firefox/4.0b3pre", - "Mozilla/5.0 (Windows NT 5.2; rv:2.0b13pre) Gecko/20110304 Firefox/4.0b13pre", - "Mozilla/5.0 (Windows NT 5.1; rv:2.0b13pre) Gecko/20110223 Firefox/4.0b13pre", - "Mozilla/5.0 (X11; Linux i686; rv:2.0b12pre) Gecko/20110204 Firefox/4.0b12pre", - "Mozilla/5.0 (X11; Linux i686; rv:2.0b12pre) Gecko/20100101 Firefox/4.0b12pre", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b11pre) Gecko/20110128 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110131 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110129 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110128 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b11pre) Gecko/20110126 Firefox/4.0b11pre", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b11pre) Gecko/20110126 Firefox/4.0b11pre", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b10pre) Gecko/20110118 Firefox/4.0b10pre", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b10pre) Gecko/20110113 Firefox/4.0b10pre", - "Mozilla/5.0 (X11; Linux i686; rv:2.0b10) Gecko/20100101 Firefox/4.0b10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:2.0b10) Gecko/20110126 Firefox/4.0b10", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0b10) Gecko/20110126 Firefox/4.0b10", - "Mozilla/5.0 (X11; Linux x86_64; rv:2.0.1) Gecko/20110506 Firefox/4.0.1", - "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20110518 Firefox/4.0.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:2.0.1) Gecko/20110606 Firefox/4.0.1", - "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:2.0) Gecko/20110307 Firefox/4.0", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:2.0) Gecko/20110404 Fedora/16-dev Firefox/4.0", - "Mozilla/5.0 (X11; Arch Linux i686; rv:2.0) Gecko/20110321 Firefox/4.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0) Gecko/20110319 Firefox/4.0", - "Mozilla/5.0 (Windows NT 6.1; rv:1.9) Gecko/20100101 Firefox/4.0", - "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/20121223 Ubuntu/9.25 (jaunty) Firefox/3.8", - "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", - "Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401 Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", - "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.3a5pre) Gecko/20100526 Firefox/3.7a5pre", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2) Gecko/20091218 Firefox 3.6b5", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2a1pre) Gecko/20090428 Firefox/3.6a1pre", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2a1pre) Gecko/20090405 Firefox/3.6a1pre", - "Mozilla/5.0 (X11; U; Linux i686; ru-RU; rv:1.9.2a1pre) Gecko/20090405 Ubuntu/9.04 (jaunty) Firefox/3.6a1pre", - "Mozilla/5.0 (Windows; Windows NT 5.1; es-ES; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre", - "Mozilla/5.0 (Windows; Windows NT 5.1; en-US; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.9) Gecko/20100915 Gentoo Firefox/3.6.9", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.9) Gecko/20100827 Red Hat/3.6.9-2.el6 Firefox/3.6.9", - "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.9.2.9) Gecko/20100913 Firefox/3.6.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9 ( .NET CLR 3.5.30729; .NET CLR 4.0.20506)", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6;en-US; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9", - "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.2.8) Gecko/20101230 Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100804 Gentoo Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100723 SUSE/3.6.8-0.1.1 Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.2.8) Gecko/20100722 Ubuntu/10.04 (lucid) Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; fi-FI; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.8) Gecko/20100727 Firefox/3.6.8", - "Mozilla/5.0 (X11; U; Linux i686; de-DE; rv:1.9.2.8) Gecko/20100725 Gentoo Firefox/3.6.8", - "Mozilla/5.0 (X11; U; FreeBSD i386; de-CH; rv:1.9.2.8) Gecko/20100729 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.8) Gecko/20100722 AskTbADAP/3.9.1.14019 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.8) Gecko/20100722 Firefox 3.6.8 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.8) Gecko/20100722 Firefox 3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.3) Gecko/20121221 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-TW; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100809 Fedora/3.6.7-1.fc14 Firefox/3.6.7", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100723 Fedora/3.6.7-1.fc13 Firefox/3.6.7", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100726 CentOS/3.6-3.el5.centos Firefox/3.6.7", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.7 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-PT; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.0", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-PT; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; de-AT; rv:1.9.1.8) Gecko/20100625 Firefox/3.6.6", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.4) Gecko/20100614 Ubuntu/10.04 (lucid) Firefox/3.6.4", - "Mozilla/5.0 (X11; U; Linux i686; fa; rv:1.8.1.4) Gecko/20100527 Firefox/3.6.4", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.4) Gecko/20100625 Gentoo Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-CA; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.0 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100503 Firefox/3.6.4 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; nb-NO; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3pre) Gecko/20100405 Firefox/3.6.3plugin1 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; he; rv:1.9.1b4pre) Gecko/20100405 Firefox/3.6.3plugin1", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.3) Gecko/20100403 Fedora/3.6.3-4.fc13 Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100403 Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.3) Gecko/20100401 SUSE/3.6.3-1.1 Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100404 Ubuntu/10.04 (lucid) Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3", - "Mozilla/5.0 (X11; U; Linux AMD64; en-US; rv:1.9.2.3) Gecko/20100403 Ubuntu/10.10 (maverick) Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; pl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.0 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ca; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.25) Gecko/20111212 Firefox/3.6.25 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-US; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.21) Gecko/20110830 Ubuntu/10.10 (maverick) Firefox/3.6.21", - "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.20) Gecko/20110805 Ubuntu/10.04 (lucid) Firefox/3.6.20", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.20) Gecko/20110804 Red Hat/3.6-2.el5 Firefox/3.6.20", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; hu; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.20) Gecko/20110803 AskTbFWV5/3.13.0.17701 Firefox/3.6.20 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; cs; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", - "Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 GTB7.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.2) Gecko/20100316 AskTbSPC2/3.9.1.14019 Firefox/3.6.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 GTB6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.0.04506.648)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.0.04506.30)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.7; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10pre) Gecko/20100902 Ubuntu/9.10 (karmic) Firefox/3.6.1pre", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.19", - "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-GB; rv:1.9.2.19) Gecko/20110707 Firefox/3.6.19", - "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", - "Mozilla/5.0 (X11; U; Linux i686; pl; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.18) Gecko/20110615 Ubuntu/10.10 (maverick) Firefox/3.6.18", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ar; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; pt-BR; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-GB; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", - "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:5.0) Gecko/20100101 Firefox/3.6.17 Firefox/3.6.17", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; sl; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (X11; U; Linux x86_64; ja-JP; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.10 (maverick) Firefox/3.6.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16) Gecko/20110323 Ubuntu/9.10 (karmic) Firefox/3.6.16 FirePHP/0.5", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en; rv:1.9.1.13) Gecko/20100914 Firefox/3.6.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.16) Gecko/20110319 AskTbUTR/3.11.3.15590 Firefox/3.6.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16pre) Gecko/20110304 Ubuntu/10.10 (maverick) Firefox/3.6.15pre", - "Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.2.15) Gecko/20110303 Ubuntu/8.04 (hardy) Firefox/3.6.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.15) Gecko/20110303 Ubuntu/10.04 (lucid) Firefox/3.6.15 FirePHP/0.5", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.15) Gecko/20110330 CentOS/3.6-1.el5.centos Firefox/3.6.15", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C) FirePHP/0.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.15) Gecko/20110303 AskTbBT4/3.11.3.15590 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.14pre) Gecko/20110105 Firefox/3.6.14pre", - "Mozilla/5.0 (X11; U; Linux armv7l; en-US; rv:1.9.2.14) Gecko/20110224 Firefox/3.6.14 MB860/Version.0.43.3.MB860.AmericaMovil.en.MX", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-AU; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14 GTB7.1 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.13) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; nb-NO; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.13) Gecko/20110103 Fedora/3.6.13-1.fc14 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101223 Gentoo Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101219 Gentoo Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Red Hat/3.6-3.el4 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-NZ; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.13) Gecko/20101206 Ubuntu/9.10 (karmic) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.13) Gecko/20101206 Red Hat/3.6-2.el5 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux x86_64; da-DK; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux MIPS32 1074Kf CPS QuadCore; en-US; rv:1.9.2.13) Gecko/20110103 Fedora/3.6.13-1.fc14 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; pt-BR; rv:1.9.2.13) Gecko/20101209 Fedora/3.6.13-1.fc13 Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.2.13) Gecko/20101206 Ubuntu/9.10 (karmic) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101209 CentOS/3.6-2.el5.centos Firefox/3.6.13", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", - "Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.9.2.12) Gecko/20101030 Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; es-MX; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.04 (lucid) Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.2.12) Gecko/20101027 Fedora/3.6.12-1.fc13 Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.2.12) Gecko/20101026 SUSE/3.6.12-0.7.1 Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101102 Gentoo Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101102 Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux ppc; fr; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101114 Gentoo Firefox/3.6.12", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12 GTB7.1", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.12) Gecko/20101027 Fedora/3.6.12-1.fc13 Firefox/3.6.12", - "Mozilla/5.0 (X11; FreeBSD x86_64; rv:2.0) Gecko/20100101 Firefox/3.6.12", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; sv-SE; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 (.NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; .NET CLR 3.5.21022)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; de; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 GTB5", - "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.2.11) Gecko/20101028 CentOS/3.6-2.el5.centos Firefox/3.6.11", - "Mozilla/5.0 (X11; U; Linux armv7l; en-GB; rv:1.9.2.3pre) Gecko/20100723 Firefox/3.6.11", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; ru; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux x86_64; pt-BR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10 GTB7.1", - "Mozilla/5.0 (X11; U; Linux x86_64; el-GR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10 GTB7.1", - "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10) Gecko/20100915 Ubuntu/9.04 (jaunty) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.11) Gecko/20101013 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; en-CA; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100915 Ubuntu/9.10 (karmic) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100914 SUSE/3.6.10-0.3.1 Firefox/3.6.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ro; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; nl; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.1) Gecko/20100122 firefox/3.6.1", - "Mozilla/5.0(Windows; U; Windows NT 7.0; rv:1.9.2) Gecko/20100101 Firefox/3.6", - "Mozilla/5.0(Windows; U; Windows NT 5.2; rv:1.9.2) Gecko/20100101 Firefox/3.6", - "Mozilla/5.0 (X11; U; x86_64 Linux; en_GB, en_US; rv:1.9.2) Gecko/20100115 Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100222 Ubuntu/10.04 (lucid) Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100130 Gentoo Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2pre) Gecko/20100312 Ubuntu/9.04 (jaunty) Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100128 Gentoo Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100115 Ubuntu/10.04 (lucid) Firefox/3.6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6 FirePHP/0.4", - "Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/3.6", - "Mozilla/5.0 (X11; FreeBSD i686) Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU; rv:1.9.2) Gecko/20100105 MRA 5.6 (build 03278) Firefox/3.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; lt; rv:1.9.2) Gecko/20100115 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.3a3pre) Gecko/20100306 Firefox3.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100806 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.3) Gecko/20100401 Firefox/3.6;MEGAUPLOAD 1.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ar; rv:1.9.2) Gecko/20100115 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.2) Gecko/20100115 Firefox/3.6", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b5pre) Gecko/20090517 Firefox/3.5b4pre (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b4pre) Gecko/20090409 Firefox/3.5b4pre", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b4pre) Gecko/20090401 Firefox/3.5b4pre", - "Mozilla/5.0 (X11; U; Linux i686; nl-NL; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 GTB5 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; fr; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 GTB5", - "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.1.9) Gecko/20100402 Ubuntu/9.10 (karmic) Firefox/3.5.9 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.1.9) Gecko/20100330 Fedora/3.5.9-2.fc12 Firefox/3.5.9", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1.1 Firefox/3.5.9 GTB7.0", - "Mozilla/5.0 (X11; U; Linux x86_64; es-CL; rv:1.9.1.9) Gecko/20100402 Ubuntu/9.10 (karmic) Firefox/3.5.9", - "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1.1 Firefox/3.5.9", - "Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.1.9) Gecko/20100401 Ubuntu/9.10 (karmic) Firefox/3.5.9", - "Mozilla/5.0 (X11; U; Linux i686; hu-HU; rv:1.9.1.9) Gecko/20100330 Fedora/3.5.9-1.fc12 Firefox/3.5.9", - "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1 Firefox/3.5.9", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100401 Ubuntu/9.10 (karmic) Firefox/3.5.9 GTB7.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100315 Ubuntu/9.10 (karmic) Firefox/3.5.9", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.4) Gecko/20091028 Ubuntu/9.10 (karmic) Firefox/3.5.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; tr; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; et; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; es-ES; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB5 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.13) Gecko/20101203 Firefox/3.5.9 (de)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB7.0 (.NET CLR 3.0.30618)", - "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8", - "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc11 Firefox/3.5.8", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.8) Gecko/20100318 Gentoo Firefox/3.5.8", - "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8", - "Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8", - "Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8", - "Mozilla/5.0 (X11; U; FreeBSD i386; ja-JP; rv:1.9.1.8) Gecko/20100305 Firefox/3.5.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; sl; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 (.NET CLR 3.5.30729) FirePHP/0.4", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 GTB6", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 GTB7.0 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100305 Gentoo Firefox/3.5.7", - "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.1.7) Gecko/20100106 Ubuntu/9.10 (karmic) Firefox/3.5.7", - "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.7) Gecko/20091222 SUSE/3.5.7-1.1.1 Firefox/3.5.7", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; fr; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.0.04506.648)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; fa; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.7) Gecko/20091221 MRA 5.5 (build 02842) Firefox/3.5.7 (.NET CLR 3.5.30729)", - " Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.6) Gecko/20100117 Gentoo Firefox/3.5.6", - "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.6) Gecko/20091216 Fedora/3.5.6-1.fc11 Firefox/3.5.6 GTB6", - "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.6) Gecko/20091201 SUSE/3.5.6-1.1.1 Firefox/3.5.6 GTB6", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.6) Gecko/20100118 Gentoo Firefox/3.5.6", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6 GTB6", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6 GTB7.0", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091201 SUSE/3.5.6-1.1.1 Firefox/3.5.6", - "Mozilla/5.0 (X11; U; Linux i686; cs-CZ; rv:1.9.1.6) Gecko/20100107 Fedora/3.5.6-1.fc12 Firefox/3.5.6", - "Mozilla/5.0 (X11; U; Linux i686; ca; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; id; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.4 (build 02647) Firefox/3.5.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.5 (build 02842) Firefox/3.5.6 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.5 (build 02842) Firefox/3.5.6", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 GTB6 (.NET CLR 3.5.30729) FBSMTWB", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729) FBSMTWB", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.5) Gecko/20091109 Ubuntu/9.10 (karmic) Firefox/3.5.5", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.8pre) Gecko/20091227 Ubuntu/9.10 (karmic) Firefox/3.5.5", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091114 Gentoo Firefox/3.5.5", - "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; uk; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 MRA 5.5 (build 02842) Firefox/3.5.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.1.5) Gecko/20091102 MRA 5.5 (build 02842) Firefox/3.5.5", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.1.5) Gecko/Firefox/3.5.5", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.5) Gecko/20091102 MRA 5.5 (build 02842) Firefox/3.5.5 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.5) Gecko/20091102 MRA 5.5 (build 02842) Firefox/3.5.5", - "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.8.1) Gecko/20091102 Firefox/3.5.5", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; pl; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 FBSMTWB", - "Mozilla/5.0 (X11; U; Linux x86_64; ja; rv:1.9.1.4) Gecko/20091016 SUSE/3.5.4-1.1.2 Firefox/3.5.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4 (.NET CLR 3.5.30729) FBSMTWB", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.4) Gecko/20091007 Firefox/3.5.4", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4 ( .NET CLR 3.5.30729; .NET4.0E)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.4) Gecko/20091007 Firefox/3.5.4", - "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.5) Gecko/20091109 Ubuntu/9.10 (karmic) Firefox/3.5.3pre", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090914 Slackware/13.0_stable Firefox/3.5.3", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3", - "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.1.3) Gecko/20091020 Ubuntu/9.10 (karmic) Firefox/3.5.3", - "Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.3) Gecko/20090919 Firefox/3.5.3", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.3) Gecko/20090912 Gentoo Firefox/3.5.3 FirePHP/0.3", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB5", - "Mozilla/5.0 (X11; U; FreeBSD i386; ru-RU; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.5.3;MEGAUPLOAD 1.0 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; ko; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; fi; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 2.0.50727; .NET CLR 3.0.30618; .NET CLR 3.5.21022; .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; bg; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; pl; rv:1.9.1.2) Gecko/20090911 Slackware Firefox/3.5.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.2) Gecko/20090803 Slackware Firefox/3.5.2", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.2) Gecko/20090803 Firefox/3.5.2 Slackware", - "Mozilla/5.0 (X11; U; Linux i686; ru-RU; rv:1.9.1.2) Gecko/20090804 Firefox/3.5.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.2) Gecko/20090729 Slackware/13.0 Firefox/3.5.2", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", - "Mozilla/5.0 (X11; U; Linux i686 (x86_64); fr; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 GTB7.1 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; es-MX; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; uk; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; es-ES; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 FirePHP/0.4", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.1.16) Gecko/20101130 AskTbMYC/3.9.1.14019 Firefox/3.5.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; it; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 GTB7.1 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.16) Gecko/20101130 MRA 5.4 (build 02647) Firefox/3.5.16 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 GTB7.1", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.16) Gecko/20101130 AskTbPLTV5/3.8.0.12304 Firefox/3.5.16 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 GTB7.1 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 GTB7.1", - "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.1.15) Gecko/20101027 Fedora/3.5.15-1.fc12 Firefox/3.5.15", - "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.1.15) Gecko/20101027 Fedora/3.5.15-1.fc12 Firefox/3.5.15", - "Mozilla/5.0 (Windows; U; Windows NT 5.0; ru; rv:1.9.1.13) Gecko/20100914 Firefox/3.5.13", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.12) Gecko/2009070611 Firefox/3.5.12", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.1.12) Gecko/20100824 MRA 5.7 (build 03755) Firefox/3.5.12", - "Mozilla/5.0 (X11; U; Linux; en-US; rv:1.9.1.11) Gecko/20100720 Firefox/3.5.11", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.1.11) Gecko/20100701 Firefox/3.5.11 ( .NET CLR 3.5.30729; .NET4.0C)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.1.11) Gecko/20100701 Firefox/3.5.11 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.1.11) Gecko/20100701 Firefox/3.5.11", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.10) Gecko/20100504 Firefox/3.5.11 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.1.10) Gecko/20100506 SUSE/3.5.10-0.1.1 Firefox/3.5.10", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1.10) Gecko/20100504 Firefox/3.5.10 GTB7.0 ( .NET CLR 3.5.30729)", - "Mozilla/5.0 (X11; U; Linux x86_64; rv:1.9.1.1) Gecko/20090716 Linux Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100524 Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.1) Gecko/20090716 Linux Mint/7 (Gloria) Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.1) Gecko/20090716 Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.1) Gecko/20090714 SUSE/3.5.1-1.1 Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux x86; rv:1.9.1.1) Gecko/20090716 Linux Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux i686; nl-NL; rv:1.9.0.19) Gecko/20090720 Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.2pre) Gecko/20090729 Ubuntu/9.04 (jaunty) Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1 GTB5", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.1) Gecko/20090722 Gentoo Firefox/3.5.1", - "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.1) Gecko/20090714 SUSE/3.5.1-1.1 Firefox/3.5.1", - "Mozilla/5.0 (X11; U; DragonFly i386; de; rv:1.9.1) Gecko/20090720 Firefox/3.5.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; tr; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1 (.NET CLR 3.5.30729)", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; sv-SE; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1 (.NET CLR 3.5.30729)", - "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00", - "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", - "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52", - "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.9.168 Version/11.51", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51", - "Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50", - "Opera/9.80 (X11; Linux i686; U; hu) Presto/2.9.168 Version/11.50", - "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11", - "Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.8.131 Version/11.11", - "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/5.0 Opera 11.11", - "Opera/9.80 (X11; Linux x86_64; U; bg) Presto/2.8.131 Version/11.10", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.8.99 Version/11.10", - "Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10", - "Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1", - "Opera/9.80 (X11; Linux x86_64; U; Ubuntu/10.10 (maverick); pl) Presto/2.7.62 Version/11.01", - "Opera/9.80 (X11; Linux i686; U; ja) Presto/2.7.62 Version/11.01", - "Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; sv) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 5.1; U;) Presto/2.7.62 Version/11.01", - "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.7.62 Version/11.01", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101213 Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", - "Mozilla/5.0 (Windows NT 6.1; U; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01", - "Mozilla/5.0 (Windows NT 6.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; de) Opera 11.01", - "Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00", - "Opera/9.80 (X11; Linux i686; U; it) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.37 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; ko) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1; U; en-GB) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.1 x64; U; en) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.7.39 Version/11.00", - "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.7.39 Version/11.00", - "Opera/9.80 (Windows NT 5.1; U; MRA 5.5 (build 02842); ru) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 5.1; U; it) Presto/2.7.62 Version/11.00", - "Mozilla/5.0 (Windows NT 6.0; U; ja; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", - "Mozilla/5.0 (Windows NT 5.1; U; pl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", - "Mozilla/5.0 (Windows NT 5.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; fr) Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; ja) Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; en) Opera 11.00", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; pl) Opera 11.00", - "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.6.31 Version/10.70", - "Mozilla/5.0 (Windows NT 5.2; U; ru; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.70", - "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.70", - "Opera/9.80 (Windows NT 5.2; U; zh-cn) Presto/2.6.30 Version/10.63", - "Opera/9.80 (Windows NT 5.2; U; en) Presto/2.6.30 Version/10.63", - "Opera/9.80 (Windows NT 5.1; U; MRA 5.6 (build 03278); ru) Presto/2.6.30 Version/10.63", - "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.6.30 Version/10.62", - "Mozilla/5.0 (X11; Linux x86_64; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.62", - "Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; de) Opera 10.62", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; en) Opera 10.62", - "Opera/9.80 (X11; Linux i686; U; pl) Presto/2.6.30 Version/10.61", - "Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows NT 6.0; U; it) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Windows 98; U; de) Presto/2.6.30 Version/10.61", - "Opera/9.80 (Macintosh; Intel Mac OS X; U; nl) Presto/2.6.30 Version/10.61", - "Opera/9.80 (X11; Linux i686; U; en) Presto/2.5.27 Version/10.60", - "Opera/9.80 (Windows NT 6.0; U; nl) Presto/2.6.30 Version/10.60", - "Opera/10.60 (Windows NT 5.1; U; zh-cn) Presto/2.6.30 Version/10.60", - "Opera/10.60 (Windows NT 5.1; U; en-US) Presto/2.6.30 Version/10.60", - "Opera/9.80 (X11; Linux i686; U; it) Presto/2.5.24 Version/10.54", - "Opera/9.80 (X11; Linux i686; U; en-GB) Presto/2.5.24 Version/10.53", - "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", - "Mozilla/5.0 (Windows NT 5.1; U; Firefox/5.0; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", - "Mozilla/5.0 (Windows NT 5.1; U; Firefox/4.5; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", - "Mozilla/5.0 (Windows NT 5.1; U; Firefox/3.5; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; ko) Opera 10.53", - "Opera/9.80 (Windows NT 6.1; U; fr) Presto/2.5.24 Version/10.52", - "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.5.22 Version/10.51", - "Opera/9.80 (Windows NT 6.0; U; cs) Presto/2.5.22 Version/10.51", - "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51", - "Opera/9.80 (Linux i686; U; en) Presto/2.5.22 Version/10.51", - "Mozilla/5.0 (Windows NT 6.1; U; en-GB; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.51", - "Mozilla/5.0 (Linux i686; U; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.51", - "Mozilla/4.0 (compatible; MSIE 8.0; Linux i686; en) Opera 10.51", - "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 6.1; U; sk) Presto/2.6.22 Version/10.50", - "Opera/9.80 (Windows NT 6.1; U; ja) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 6.0; U; zh-cn) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 5.1; U; sk) Presto/2.5.22 Version/10.50", - "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.5.22 Version/10.50", - "Opera/10.50 (Windows NT 6.1; U; en-GB) Presto/2.2.2", - "Opera/9.80 (S60; SymbOS; Opera Tablet/9174; U; en) Presto/2.7.81 Version/10.5", - "Opera/9.80 (X11; U; Linux i686; en-US; rv:1.9.2.3) Presto/2.2.15 Version/10.10", - "Opera/9.80 (X11; Linux x86_64; U; it) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 6.1; U; de) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 6.0; U; Gecko/20100115; pl) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 5.1; U; de) Presto/2.2.15 Version/10.10", - "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.2.15 Version/10.10", - "Mozilla/5.0 (Windows NT 6.0; U; tr; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 10.10", - "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux i686; de) Opera 10.10", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0; tr) Opera 10.10", - "Opera/9.80 (X11; Linux x86_64; U; en-GB) Presto/2.2.15 Version/10.01", - "Opera/9.80 (X11; Linux x86_64; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux x86_64; U; de) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; pt-BR) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; pl) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; nb) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; en-GB) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; Debian; pl) Presto/2.2.15 Version/10.00", - "Opera/9.80 (X11; Linux i686; U; de) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; de) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 6.0; U; de) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 5.2; U; en) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.2.15 Version/10.00", - "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.2.15 Version/10.00", - "Opera/9.99 (X11; U; sk)", - "Opera/9.99 (Windows NT 5.1; U; pl) Presto/9.9.9", - "Opera/9.80 (J2ME/MIDP; Opera Mini/5.0 (Windows; U; Windows NT 5.1; en) AppleWebKit/886; U; en) Presto/2.4.15", - "Opera/9.70 (Linux ppc64 ; U; en) Presto/2.2.1", - "Opera/9.70 (Linux i686 ; U; zh-cn) Presto/2.2.0", - "Opera/9.70 (Linux i686 ; U; en-us) Presto/2.2.0", - "Opera/9.70 (Linux i686 ; U; en) Presto/2.2.1", - "Opera/9.70 (Linux i686 ; U; en) Presto/2.2.0", - "Opera/9.70 (Linux i686 ; U; ; en) Presto/2.2.1", - "Opera/9.70 (Linux i686 ; U; ; en) Presto/2.2.1", - "Mozilla/5.0 (Linux i686 ; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.70", - "Mozilla/4.0 (compatible; MSIE 6.0; Linux i686 ; en) Opera 9.70", - "Opera/9.64(Windows NT 5.1; U; en) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; pl) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; hr) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; en-GB) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; en) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; de) Presto/2.1.1", - "Opera/9.64 (X11; Linux x86_64; U; cs) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; tr) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; sv) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; pl) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; nb) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; Linux Mint; nb) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; Linux Mint; it) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; en) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; de) Presto/2.1.1", - "Opera/9.64 (X11; Linux i686; U; da) Presto/2.1.1", - "Opera/9.64 (Windows NT 6.1; U; MRA 5.5 (build 02842); ru) Presto/2.1.1", - "Opera/9.64 (Windows NT 6.1; U; de) Presto/2.1.1", - "Opera/9.64 (Windows NT 6.0; U; zh-cn) Presto/2.1.1", - "Opera/9.64 (Windows NT 6.0; U; pl) Presto/2.1.1", - "Opera/9.63 (X11; Linux x86_64; U; ru) Presto/2.1.1", - "Opera/9.63 (X11; Linux x86_64; U; cs) Presto/2.1.1", - "Opera/9.63 (X11; Linux i686; U; ru) Presto/2.1.1", - "Opera/9.63 (X11; Linux i686; U; ru)", - "Opera/9.63 (X11; Linux i686; U; nb) Presto/2.1.1", - "Opera/9.63 (X11; Linux i686; U; en)", - "Opera/9.63 (X11; Linux i686; U; de) Presto/2.1.1", - "Opera/9.63 (X11; Linux i686)", - "Opera/9.63 (X11; FreeBSD 7.1-RELEASE i386; U; en) Presto/2.1.1", - "Opera/9.63 (Windows NT 6.1; U; hu) Presto/2.1.1", - "Opera/9.63 (Windows NT 6.1; U; en) Presto/2.1.1", - "Opera/9.63 (Windows NT 6.1; U; de) Presto/2.1.1", - "Opera/9.63 (Windows NT 6.0; U; pl) Presto/2.1.1", - "Opera/9.63 (Windows NT 6.0; U; nb) Presto/2.1.1", - "Opera/9.63 (Windows NT 6.0; U; fr) Presto/2.1.1", - "Opera/9.63 (Windows NT 6.0; U; en) Presto/2.1.1", - "Opera/9.63 (Windows NT 6.0; U; cs) Presto/2.1.1", - "Opera/9.63 (Windows NT 5.2; U; en) Presto/2.1.1", - "Opera/9.63 (Windows NT 5.2; U; de) Presto/2.1.1", - "Opera/9.63 (Windows NT 5.1; U; pt-BR) Presto/2.1.1", - "Opera/9.62 (X11; Linux x86_64; U; ru) Presto/2.1.1", - "Opera/9.62 (X11; Linux x86_64; U; en_GB, en_US) Presto/2.1.1", - "Opera/9.62 (X11; Linux i686; U; pt-BR) Presto/2.1.1", - "Opera/9.62 (X11; Linux i686; U; Linux Mint; en) Presto/2.1.1", - "Opera/9.62 (X11; Linux i686; U; it) Presto/2.1.1", - "Opera/9.62 (X11; Linux i686; U; fi) Presto/2.1.1", - "Opera/9.62 (X11; Linux i686; U; en) Presto/2.1.1", - "Opera/9.62 (Windows NT 6.1; U; en) Presto/2.1.1", - "Opera/9.62 (Windows NT 6.1; U; de) Presto/2.1.1", - "Opera/9.62 (Windows NT 6.0; U; pl) Presto/2.1.1", - "Opera/9.62 (Windows NT 6.0; U; nb) Presto/2.1.1", - "Opera/9.62 (Windows NT 6.0; U; en-GB) Presto/2.1.1", - "Opera/9.62 (Windows NT 6.0; U; en) Presto/2.1.1", - "Opera/9.62 (Windows NT 6.0; U; de) Presto/2.1.1", - "Opera/9.62 (Windows NT 5.2; U; en) Presto/2.1.1", - "Opera/9.62 (Windows NT 5.1; U; zh-tw) Presto/2.1.1", - "Opera/9.62 (Windows NT 5.1; U; zh-cn) Presto/2.1.1", - "Opera/9.62 (Windows NT 5.1; U; tr) Presto/2.1.1", - "Opera/9.62 (Windows NT 5.1; U; ru) Presto/2.1.1", - "Opera/9.62 (Windows NT 5.1; U; pt-BR) Presto/2.1.1", - "Opera/9.61 (X11; Linux x86_64; U; fr) Presto/2.1.1", - "Opera/9.61 (X11; Linux i686; U; ru) Presto/2.1.1", - "Opera/9.61 (X11; Linux i686; U; pl) Presto/2.1.1", - "Opera/9.61 (X11; Linux i686; U; en) Presto/2.1.1", - "Opera/9.61 (X11; Linux i686; U; de) Presto/2.1.1", - "Opera/9.61 (Windows NT 6.0; U; ru) Presto/2.1.1", - "Opera/9.61 (Windows NT 6.0; U; pt-BR) Presto/2.1.1", - "Opera/9.61 (Windows NT 6.0; U; http://lucideer.com; en-GB) Presto/2.1.1", - "Opera/9.61 (Windows NT 6.0; U; en) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.2; U; en) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.1; U; zh-tw) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.1; U; zh-cn) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.1; U; ru) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.1; U; fr) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.1; U; en-GB) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.1; U; en) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.1; U; de) Presto/2.1.1", - "Opera/9.61 (Windows NT 5.1; U; cs) Presto/2.1.1", - "Opera/9.61 (Macintosh; Intel Mac OS X; U; de) Presto/2.1.1", - "Mozilla/5.0 (Windows NT 5.1; U; en-GB; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.61", - "Opera/9.60 (X11; Linux x86_64; U)", - "Opera/9.60 (X11; Linux i686; U; ru) Presto/2.1.1", - "Opera/9.60 (X11; Linux i686; U; en-GB) Presto/2.1.1", - "Opera/9.60 (Windows NT 6.0; U; uk) Presto/2.1.1", - "Opera/9.60 (Windows NT 6.0; U; ru) Presto/2.1.1", - "Opera/9.60 (Windows NT 6.0; U; pl) Presto/2.1.1", - "Opera/9.60 (Windows NT 6.0; U; de) Presto/2.1.1", - "Opera/9.60 (Windows NT 6.0; U; bg) Presto/2.1.1", - "Opera/9.60 (Windows NT 5.1; U; tr) Presto/2.1.1", - "Opera/9.60 (Windows NT 5.1; U; sv) Presto/2.1.1", - "Opera/9.60 (Windows NT 5.1; U; es-ES) Presto/2.1.1", - "Opera/9.60 (Windows NT 5.1; U; en-GB) Presto/2.1.1", - "Opera/9.60 (Windows NT 5.0; U; en) Presto/2.1.1", - "Mozilla/5.0 (X11; Linux x86_64; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.60", - "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux x86_64; en) Opera 9.60", - "Opera/9.52 (X11; Linux x86_64; U; ru)", - "Opera/9.52 (X11; Linux x86_64; U; en)", - "Opera/9.52 (X11; Linux x86_64; U)", - "Opera/9.52 (X11; Linux ppc; U; de)", - "Opera/9.52 (X11; Linux i686; U; fr)", - "Opera/9.52 (X11; Linux i686; U; en)", - "Opera/9.52 (X11; Linux i686; U; cs)", - "Opera/9.52 (Windows NT 6.0; U; Opera/9.52 (X11; Linux x86_64; U); en)", - "Opera/9.52 (Windows NT 6.0; U; fr)", - "Opera/9.52 (Windows NT 6.0; U; en)", - "Opera/9.52 (Windows NT 6.0; U; de)", - "Opera/9.52 (Windows NT 5.2; U; ru)", - "Opera/9.52 (Windows NT 5.0; U; en)", - "Opera/9.52 (Macintosh; PPC Mac OS X; U; ja)", - "Opera/9.52 (Macintosh; PPC Mac OS X; U; fr)", - "Opera/9.52 (Macintosh; Intel Mac OS X; U; pt-BR)", - "Opera/9.52 (Macintosh; Intel Mac OS X; U; pt)", - "Mozilla/5.0 (Windows NT 5.1; U; de; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.52", - "Mozilla/5.0 (Windows NT 5.1; U; ; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.52", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; ru) Opera 9.52", - "Opera/9.51 (X11; Linux i686; U; Linux Mint; en)", - "Opera/9.51 (X11; Linux i686; U; fr)", - "Opera/9.51 (X11; Linux i686; U; de)", - "Opera/9.51 (Windows NT 6.0; U; sv)", - "Opera/9.51 (Windows NT 6.0; U; es)", - "Opera/9.51 (Windows NT 6.0; U; en)", - "Opera/9.51 (Windows NT 5.2; U; en)", - "Opera/9.51 (Windows NT 5.1; U; nn)", - "Opera/9.51 (Windows NT 5.1; U; fr)", - "Opera/9.51 (Windows NT 5.1; U; es-LA)", - "Opera/9.51 (Windows NT 5.1; U; es-AR)", - "Opera/9.51 (Windows NT 5.1; U; en-GB)", - "Opera/9.51 (Windows NT 5.1; U; en)", - "Opera/9.51 (Windows NT 5.1; U; da)", - "Opera/9.51 (Macintosh; Intel Mac OS X; U; en)", - "Mozilla/5.0 (X11; Linux i686; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", - "Mozilla/5.0 (Windows NT 6.0; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", - "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", - "Mozilla/5.0 (Windows NT 5.1; U; en-GB; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", - "Mozilla/5.0 (Windows NT 5.1; U; de; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", - "Opera/9.50 (X11; Linux x86_64; U; pl)", - "Opera/9.50 (X11; Linux x86_64; U; nb)", - "Opera/9.50 (X11; Linux ppc; U; en)", - "Opera/9.50 (X11; Linux i686; U; es-ES)", - "Opera/9.50 (Windows NT 5.2; U; it)", - "Opera/9.50 (Windows NT 5.1; U; ru)", - "Opera/9.50 (Windows NT 5.1; U; nn)", - "Opera/9.50 (Windows NT 5.1; U; nl)", - "Opera/9.50 (Windows NT 5.1; U; it)", - "Opera/9.50 (Windows NT 5.1; U; es-ES)", - "Opera/9.50 (Macintosh; Intel Mac OS X; U; en)", - "Opera/9.50 (Macintosh; Intel Mac OS X; U; de)", - "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50", - "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux x86_64; en) Opera 9.50", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0; en) Opera 9.50", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; en) Opera 9.50", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; de) Opera 9.50", - "Opera/9.5 (Windows NT 6.0; U; en)", - "Opera/9.5 (Windows NT 5.1; U; fr)", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9b3) Gecko/2008020514 Opera 9.5", - "Opera 9.4 (Windows NT 6.1; U; en)", - "Opera 9.4 (Windows NT 5.3; U; en)", - "Opera/9.30 (Nintendo Wii; U; ; 2071; Wii Shop Channel/1.0; en)", - "Opera/9.30 (Nintendo Wii; U; ; 2047-7;pt-br)", - "Opera/9.30 (Nintendo Wii; U; ; 2047-7;es)", - "Opera/9.30 (Nintendo Wii; U; ; 2047-7;en)", - "Opera/9.30 (Nintendo Wii; U; ; 2047-7; fr)", - "Opera/9.30 (Nintendo Wii; U; ; 2047-7; de)", - "Opera/9.27 (X11; Linux i686; U; fr)", - "Opera/9.27 (X11; Linux i686; U; en)", - "Opera/9.27 (Windows NT 5.2; U; en)", - "Opera/9.27 (Windows NT 5.1; U; ja)", - "Opera/9.27 (Macintosh; Intel Mac OS X; U; sv)", - "Mozilla/5.0 (Windows NT 5.2; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.27", - "Mozilla/5.0 (Windows NT 5.1; U; es-la; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.27", - "Mozilla/5.0 (Macintosh; Intel Mac OS X; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.27", - "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux i686; en) Opera 9.27", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; en) Opera 9.27", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; es-la) Opera 9.27", - "Opera/9.26 (Windows; U; pl)", - "Opera/9.26 (Windows NT 5.1; U; zh-cn)", - "Opera/9.26 (Windows NT 5.1; U; pl)", - "Opera/9.26 (Windows NT 5.1; U; nl)", - "Opera/9.26 (Windows NT 5.1; U; MEGAUPLOAD 2.0; en)", - "Opera/9.26 (Windows NT 5.1; U; de)", - "Opera/9.26 (Macintosh; PPC Mac OS X; U; en)", - "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.26", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0; en) Opera 9.26", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.26", - "Opera/9.25 (X11; Linux i686; U; fr-ca)", - "Opera/9.25 (X11; Linux i686; U; fr)", - "Opera/9.25 (X11; Linux i686; U; en)", - "Opera/9.25 (Windows NT 6.0; U; SV1; MEGAUPLOAD 2.0; ru)", - "Opera/9.25 (Windows NT 6.0; U; sv)", - "Opera/9.25 (Windows NT 6.0; U; ru)", - "Opera/9.25 (Windows NT 6.0; U; MEGAUPLOAD 1.0; ru)", - "Opera/9.25 (Windows NT 6.0; U; en-US)", - "Opera/9.25 (Windows NT 5.2; U; en)", - "Opera/9.25 (Windows NT 5.1; U; zh-cn)", - "Opera/9.25 (Windows NT 5.1; U; ru)", - "Opera/9.25 (Windows NT 5.1; U; MEGAUPLOAD 1.0; pt-br)", - "Opera/9.25 (Windows NT 5.1; U; lt)", - "Opera/9.25 (Windows NT 5.1; U; de)", - "Opera/9.25 (Windows NT 5.0; U; en)", - "Opera/9.25 (Windows NT 5.0; U; cs)", - "Opera/9.25 (Windows NT 4.0; U; en)", - "Opera/9.25 (OpenSolaris; U; en)", - "Opera/9.25 (Macintosh; PPC Mac OS X; U; en)", - "Opera/9.25 (Macintosh; Intel Mac OS X; U; en)", - "Opera/9.24 (X11; SunOS i86pc; U; en)", - "Opera/9.24 (X11; Linux i686; U; de)", - "Opera/9.24 (Windows NT 5.1; U; tr)", - "Opera/9.24 (Windows NT 5.1; U; ru)", - "Opera/9.24 (Windows NT 5.0; U; ru)", - "Opera/9.24 (Macintosh; PPC Mac OS X; U; en)", - "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.24", - "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.24", - "Mozilla/4.0 (compatible; MSIE 6.0; Mac_PowerPC; en) Opera 9.24", - "Opera/9.23 (X11; Linux x86_64; U; en)", - "Opera/9.23 (X11; Linux i686; U; es-es)", - "Opera/9.23 (X11; Linux i686; U; en)", - "Opera/9.23 (Windows NT 6.0; U; de)", - "Opera/9.23 (Windows NT 5.1; U; zh-cn)", - "Opera/9.23 (Windows NT 5.1; U; SV1; MEGAUPLOAD 1.0; ru)", - "Opera/9.23 (Windows NT 5.1; U; pt)", - "Opera/9.23 (Windows NT 5.1; U; ja)", - "Opera/9.23 (Windows NT 5.1; U; it)", - "Opera/9.23 (Windows NT 5.1; U; fi)", - "Opera/9.23 (Windows NT 5.1; U; en)", - "Opera/9.23 (Windows NT 5.1; U; de)", - "Opera/9.23 (Windows NT 5.1; U; da)", - "Opera/9.23 (Windows NT 5.0; U; en)", - "Opera/9.23 (Windows NT 5.0; U; de)", - "Opera/9.23 (Nintendo Wii; U; ; 1038-58; Wii Internet Channel/1.0; en)", - "Opera/9.23 (Macintosh; Intel Mac OS X; U; ja)", - "Opera/9.23 (Mac OS X; ru)", - "Opera/9.23 (Mac OS X; fr)", - "Mozilla/5.0 (X11; Linux i686; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.23", - "Opera/9.22 (X11; OpenBSD i386; U; en)", - "Opera/9.22 (X11; Linux i686; U; en)", - "Opera/9.22 (X11; Linux i686; U; de)", - "Opera/9.22 (Windows NT 6.0; U; ru)", - "Opera/9.22 (Windows NT 6.0; U; en)", - "Opera/9.22 (Windows NT 5.1; U; SV1; MEGAUPLOAD 2.0; ru)", - "Opera/9.22 (Windows NT 5.1; U; SV1; MEGAUPLOAD 1.0; ru)", - "Opera/9.22 (Windows NT 5.1; U; pl)", - "Opera/9.22 (Windows NT 5.1; U; fr)", - "Opera/9.22 (Windows NT 5.1; U; en)", - "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.22", - "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux i686; en) Opera 9.22", - "Opera/9.21 (X11; Linux x86_64; U; en)", - "Opera/9.21 (X11; Linux i686; U; es-es)", - "Opera/9.21 (X11; Linux i686; U; en)", - "Opera/9.21 (X11; Linux i686; U; de)", - "Opera/9.21 (Windows NT 6.0; U; nb)", - "Opera/9.21 (Windows NT 6.0; U; en)", - "Opera/9.21 (Windows NT 5.2; U; en)", - "Opera/9.21 (Windows NT 5.1; U; SV1; MEGAUPLOAD 1.0; ru)", - "Opera/9.21 (Windows NT 5.1; U; ru)", - "Opera/9.21 (Windows NT 5.1; U; pt-br)", - "Opera/9.21 (Windows NT 5.1; U; pl)", - "Opera/9.21 (Windows NT 5.1; U; nl)", - "Opera/9.21 (Windows NT 5.1; U; MEGAUPLOAD 1.0; en)", - "Opera/9.21 (Windows NT 5.1; U; fr)", - "Opera/9.21 (Windows NT 5.1; U; en)", - "Opera/9.21 (Windows NT 5.1; U; de)", - "Opera/9.21 (Windows NT 5.0; U; de)", - "Opera/9.21 (Windows 98; U; en)", - "Opera/9.21 (Macintosh; PPC Mac OS X; U; en)", - "Opera/9.21 (Macintosh; Intel Mac OS X; U; en)", - "Opera/9.20(Windows NT 5.1; U; en)", - "Opera/9.20 (X11; Linux x86_64; U; en)", - "Opera/9.20 (X11; Linux ppc; U; en)", - "Opera/9.20 (X11; Linux i686; U; tr)", - "Opera/9.20 (X11; Linux i686; U; ru)", - "Opera/9.20 (X11; Linux i686; U; pl)", - "Opera/9.20 (X11; Linux i686; U; es-es)", - "Opera/9.20 (X11; Linux i686; U; en)", - "Opera/9.20 (X11; Linux i586; U; en)", - "Opera/9.20 (Windows NT 6.0; U; es-es)", - "Opera/9.20 (Windows NT 6.0; U; en)", - "Opera/9.20 (Windows NT 6.0; U; de)", - "Opera/9.20 (Windows NT 5.2; U; en)", - "Opera/9.20 (Windows NT 5.1; U; zh-tw)", - "Opera/9.20 (Windows NT 5.1; U; nb)", - "Opera/9.20 (Windows NT 5.1; U; MEGAUPLOAD=1.0; es-es)", - "Opera/9.20 (Windows NT 5.1; U; it)", - "Opera/9.20 (Windows NT 5.1; U; es-es)", - "Opera/9.20 (Windows NT 5.1; U; es-AR)", - "Opera/9.20 (Windows NT 5.1; U; en)", - "Opera/9.12 (X11; Linux i686; U; en) (Ubuntu)", - "Opera/9.12 (Windows NT 5.0; U; ru)", - "Opera/9.12 (Windows NT 5.0; U)", - "Opera/9.10 (X11; Linux; U; en)", - "Opera/9.10 (X11; Linux x86_64; U; en)", - "Opera/9.10 (X11; Linux i686; U; pl)", - "Opera/9.10 (X11; Linux i686; U; kubuntu;pl)", - "Opera/9.10 (X11; Linux i686; U; en)", - "Opera/9.10 (X11; Linux i386; U; en)", - "Opera/9.10 (Windows NT 6.0; U; it-IT)", - "Opera/9.10 (Windows NT 6.0; U; en)", - "Opera/9.10 (Windows NT 5.2; U; en)", - "Opera/9.10 (Windows NT 5.2; U; de)", - "Opera/9.10 (Windows NT 5.1; U; zh-tw)", - "Opera/9.10 (Windows NT 5.1; U; sv)", - "Opera/9.10 (Windows NT 5.1; U; pt)", - "Opera/9.10 (Windows NT 5.1; U; pl)", - "Opera/9.10 (Windows NT 5.1; U; nl)", - "Opera/9.10 (Windows NT 5.1; U; MEGAUPLOAD 1.0; pl)", - "Opera/9.10 (Windows NT 5.1; U; it)", - "Opera/9.10 (Windows NT 5.1; U; hu)", - "Opera/9.10 (Windows NT 5.1; U; fi)", - "Opera/9.10 (Windows NT 5.1; U; es-es)", - "Opera/9.02 (X11; Linux i686; U; pl)", - "Opera/9.02 (X11; Linux i686; U; hu)", - "Opera/9.02 (X11; Linux i686; U; en)", - "Opera/9.02 (X11; Linux i686; U; de)", - "Opera/9.02 (Windows NT 5.2; U; en)", - "Opera/9.02 (Windows NT 5.2; U; de)", - "Opera/9.02 (Windows NT 5.1; U; zh-cn)", - "Opera/9.02 (Windows NT 5.1; U; ru)", - "Opera/9.02 (Windows NT 5.1; U; pt-br)", - "Opera/9.02 (Windows NT 5.1; U; pl)", - "Opera/9.02 (Windows NT 5.1; U; nb)", - "Opera/9.02 (Windows NT 5.1; U; ja)", - "Opera/9.02 (Windows NT 5.1; U; fi)", - "Opera/9.02 (Windows NT 5.1; U; en)", - "Opera/9.02 (Windows NT 5.1; U; de)", - "Opera/9.02 (Windows NT 5.0; U; sv)", - "Opera/9.02 (Windows NT 5.0; U; pl)", - "Opera/9.02 (Windows NT 5.0; U; en)", - "Opera/9.01 (X11; OpenBSD i386; U; en)", - "Opera/9.01 (X11; Linux i686; U; en)", - "Opera/9.01 (X11; FreeBSD 6 i386; U;pl)", - "Opera/9.01 (X11; FreeBSD 6 i386; U; en)", - "Opera/9.01 (Windows NT 5.2; U; ru)", - "Opera/9.01 (Windows NT 5.2; U; en)", - "Opera/9.01 (Windows NT 5.1; U; ru)", - "Opera/9.01 (Windows NT 5.1; U; pl)", - "Opera/9.01 (Windows NT 5.1; U; ja)", - "Opera/9.01 (Windows NT 5.1; U; es-es)", - "Opera/9.01 (Windows NT 5.1; U; en)", - "Opera/9.01 (Windows NT 5.1; U; de)", - "Opera/9.01 (Windows NT 5.1; U; da)", - "Opera/9.01 (Windows NT 5.1; U; cs)", - "Opera/9.01 (Windows NT 5.1; U; bg)", - "Opera/9.01 (Windows NT 5.0; U; en)", - "Opera/9.01 (Windows NT 5.0; U; de)", - "Opera/9.01 (Macintosh; PPC Mac OS X; U; it)", - "Opera/9.01 (Macintosh; PPC Mac OS X; U; en)", - "Opera/9.00 (X11; Linux i686; U; pl)", - "Opera/9.00 (X11; Linux i686; U; en)", - "Opera/9.00 (X11; Linux i686; U; de)", - "Opera/9.00 (Windows NT 5.2; U; ru)", - "Opera/9.00 (Windows NT 5.2; U; pl)", - "Opera/9.00 (Windows NT 5.2; U; en)", - "Opera/9.00 (Windows NT 5.1; U; ru)", - "Opera/9.00 (Windows NT 5.1; U; pl)", - "Opera/9.00 (Windows NT 5.1; U; nl)", - "Opera/9.00 (Windows NT 5.1; U; ja)", - "Opera/9.00 (Windows NT 5.1; U; it)", - "Opera/9.00 (Windows NT 5.1; U; fr)", - "Opera/9.00 (Windows NT 5.1; U; fi)", - "Opera/9.00 (Windows NT 5.1; U; es-es)", - "Opera/9.00 (Windows NT 5.1; U; en)", - "Opera/9.00 (Windows NT 5.1; U; de)", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.12 Safari/535.11", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/17.0.940.0 Safari/535.8", - "Mozilla/5.0 (X11; CrOS i686 1193.158.0) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7xs5D9rRDFpg2g", - "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.6 (KHTML, like Gecko) Chrome/16.0.897.0 Safari/535.6", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.54 Safari/535.2", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.10 Chromium/15.0.874.120 Chrome/15.0.874.120 Safari/535.2", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.120 Safari/535.2", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.872.0 Safari/535.2", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.04 Chromium/15.0.871.0 Chrome/15.0.871.0 Safari/535.2", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.864.0 Safari/535.2", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.860.0 Safari/535.2", - "Chrome/15.0.860.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/15.0.860.0", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.186 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.825.0 Chrome/14.0.825.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.824.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.10913 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.814.0 Chrome/14.0.814.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.814.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.813.0 Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.812.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.811.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.809.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.10 Chromium/14.0.808.0 Chrome/14.0.808.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.808.0 Chrome/14.0.808.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.804.0 Chrome/14.0.804.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.803.0 Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.801.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.801.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1", - "Mozilla/5.0 (X11; CrOS i686 13.587.48) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.43 Safari/535.1", - "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41", - "Mozilla/5.0 ArchLinux (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/13.0.782.41 Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.32 Safari/535.1", - "Mozilla/5.0 (X11; Linux amd64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", - "Mozilla/5.0 (X11; CrOS i686 0.13.587) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.14 Safari/535.1", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.107 Safari/535.1", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.766.0 Safari/534.36", - "Mozilla/5.0 (X11; Linux amd64) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.766.0 Safari/534.36", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.35 (KHTML, like Gecko) Ubuntu/10.10 Chromium/13.0.764.0 Chrome/13.0.764.0 Safari/534.35", - "Mozilla/5.0 (X11; CrOS i686 0.13.507) AppleWebKit/534.35 (KHTML, like Gecko) Chrome/13.0.763.0 Safari/534.35", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.33 (KHTML, like Gecko) Ubuntu/9.10 Chromium/13.0.752.0 Chrome/13.0.752.0 Safari/534.33", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.31 (KHTML, like Gecko) Chrome/13.0.748.0 Safari/534.31", - "Mozilla/5.0 (Windows NT 6.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.750.0 Safari/534.30", - "Mozilla/5.0 (X11; CrOS i686 12.433.109) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.93 Safari/534.30", - "Mozilla/5.0 (X11; CrOS i686 12.0.742.91) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.93 Safari/534.30", - "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/12.0.742.91", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.91 Chromium/12.0.742.91 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30", - "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.60 Safari/534.30", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.53 Safari/534.30", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.113 Safari/534.30", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/11.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/11.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Windows NT 7.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Windows 8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", - "Mozilla/5.0 (X11; CrOS i686 12.433.216) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.105 Safari/534.30", - "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.724.100 Safari/534.30", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.706.0 Safari/534.25", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.704.0 Safari/534.25", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.703.0 Chrome/12.0.703.0 Safari/534.24", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.702.0 Chrome/12.0.702.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.700.3 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.699.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.699.0 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.698.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.697.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", - "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/11.0.696.50", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.43 Safari/534.24", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.14 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.12 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.12 Safari/534.24", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.04 Chromium/11.0.696.0 Chrome/11.0.696.0 Safari/534.24", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.0 Safari/534.24", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.694.0 Safari/534.24", - "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.23 (KHTML, like Gecko) Chrome/11.0.686.3 Safari/534.23", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.682.0 Safari/534.21", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_0; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", - "Mozilla/5.0 (Windows NT) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.669.0 Safari/534.20", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.19 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.19", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.18", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.660.0 Safari/534.18", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.654.0 Safari/534.17", - "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.652.0 Safari/534.17", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.82 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux armv7l; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", - "Mozilla/5.0 (X11; U; FreeBSD x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", - "Mozilla/5.0 (X11; U; FreeBSD i386; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.133 Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.133 Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.127 Chrome/10.0.648.127 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.0 Chrome/10.0.648.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.0 Chrome/10.0.648.0 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.642.0 Chrome/10.0.642.0 Safari/534.16", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.639.0 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.638.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.634.0 Safari/534.16", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.634.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 SUSE/10.0.626.0 (KHTML, like Gecko) Chrome/10.0.626.0 Safari/534.16", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.613.0 Safari/534.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.613.0 Chrome/10.0.613.0 Safari/534.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.04 Chromium/10.0.612.3 Chrome/10.0.612.3 Safari/534.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.612.1 Safari/534.15", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.611.0 Chrome/10.0.611.0 Safari/534.15", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.602.0 Safari/534.14", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML,like Gecko) Chrome/9.1.0.0 Safari/540.0", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML, like Gecko) Ubuntu/10.10 Chrome/9.1.0.0 Safari/540.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Ubuntu/10.10 Chromium/9.0.600.0 Chrome/9.0.600.0 Safari/534.14", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.600.0 Safari/534.14", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.599.0 Safari/534.13", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.84 Safari/534.13", - "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.44 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.19 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", - "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13" - ].freeze - # rubocop:enable Metrics/LineLength end -# rubocop:enable Metrics/ModuleLength diff --git a/lib/addic7ed/config.json b/lib/addic7ed/config.json new file mode 100644 index 0000000..fb1a683 --- /dev/null +++ b/lib/addic7ed/config.json @@ -0,0 +1,1417 @@ +{ + "urls": { + "shows": "http://www.addic7ed.com/ajax_getShows.php", + "episodes": "http://www.addic7ed.com/ajax_getEpisodes.php" + }, + "languages": { + "fr": { + "id": 8, + "name": "French" + }, + "ar": { + "id": 38, + "name": "Arabic" + }, + "az": { + "id": 48, + "name": "Azerbaijani" + }, + "bn": { + "id": 47, + "name": "Bengali" + }, + "bs": { + "id": 44, + "name": "Bosnian" + }, + "bg": { + "id": 35, + "name": "Bulgarian" + }, + "ca": { + "id": 12, + "name": "Català" + }, + "cn": { + "id": 41, + "name": "Chinese (Simplified)" + }, + "zh": { + "id": 24, + "name": "Chinese (Traditional)" + }, + "hr": { + "id": 31, + "name": "Croatian" + }, + "cs": { + "id": 14, + "name": "Czech" + }, + "da": { + "id": 30, + "name": "Danish" + }, + "nl": { + "id": 17, + "name": "Dutch" + }, + "en": { + "id": 1, + "name": "English" + }, + "eu": { + "id": 13, + "name": "Euskera" + }, + "fi": { + "id": 28, + "name": "Finnish" + }, + "fr": { + "id": 8, + "name": "French" + }, + "gl": { + "id": 15, + "name": "Galego" + }, + "de": { + "id": 11, + "name": "German" + }, + "el": { + "id": 27, + "name": "Greek" + }, + "he": { + "id": 23, + "name": "Hebrew" + }, + "hu": { + "id": 20, + "name": "Hungarian" + }, + "id": { + "id": 37, + "name": "Indonesian" + }, + "it": { + "id": 7, + "name": "Italian" + }, + "ja": { + "id": 32, + "name": "Japanese" + }, + "ko": { + "id": 42, + "name": "Korean" + }, + "mk": { + "id": 49, + "name": "Macedonian" + }, + "ms": { + "id": 40, + "name": "Malay" + }, + "no": { + "id": 29, + "name": "Norwegian" + }, + "fa": { + "id": 43, + "name": "Persian" + }, + "pl": { + "id": 21, + "name": "Polish" + }, + "pt": { + "id": 9, + "name": "Portuguese" + }, + "pt-br": { + "id": 10, + "name": "Portuguese (Brazilian)" + }, + "ro": { + "id": 26, + "name": "Romanian" + }, + "ru": { + "id": 19, + "name": "Russian" + }, + "sr": { + "id": 39, + "name": "Serbian (Cyrillic)" + }, + "sr-la": { + "id": 36, + "name": "Serbian (Latin)" + }, + "sk": { + "id": 25, + "name": "Slovak" + }, + "sl": { + "id": 22, + "name": "Slovenian" + }, + "es": { + "id": 4, + "name": "Spanish" + }, + "es-la": { + "id": 6, + "name": "Spanish (Latin America)" + }, + "es-es": { + "id": 5, + "name": "Spanish (Spain)" + }, + "sv": { + "id": 18, + "name": "Swedish" + }, + "th": { + "id": 46, + "name": "Thai" + }, + "tr": { + "id": 16, + "name": "Turkish" + }, + "vi": { + "id": 45, + "name": "Viet namese" + } + }, + "user_agents": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0a2) Gecko/20111101 Firefox/9.0a2", + "Mozilla/5.0 (Windows NT 6.2; rv:9.0.1) Gecko/20100101 Firefox/9.0.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110613 Firefox/6.0a2", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110612 Firefox/6.0a2", + "Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0", + "Mozilla/5.0 (Windows NT 6.1; rv:6.0) Gecko/20110814 Firefox/6.0", + "Mozilla/5.0 (Windows NT 5.1; rv:6.0) Gecko/20100101 Firefox/6.0 FirePHP/0.6", + "Mozilla/5.0 (Windows NT 5.0; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0", + "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:5.0a2) Gecko/20110524 Firefox/5.0a2", + "Mozilla/5.0 (Windows NT 6.1; U; ru; rv:5.0.1.6) Gecko/20110501 Firefox/5.0.1 Firefox/5.0.1", + "Mozilla/3.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/5.0.1", + "Mozilla/5.0 (X11; U; Linux i586; de; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (X11; U; Linux amd64; rv:5.0) Gecko/20100101 Firefox/5.0 (Debian)", + "Mozilla/5.0 (X11; U; Linux amd64; en-US; rv:5.0) Gecko/20110619 Firefox/5.0", + "Mozilla/5.0 (X11; Linux) Gecko Firefox/5.0", + "Mozilla/5.0 (X11; Linux x86_64; rv:5.0) Gecko/20100101 Firefox/5.0 FirePHP/0.5", + "Mozilla/5.0 (X11; Linux x86_64; rv:5.0) Gecko/20100101 Firefox/5.0 Firefox/5.0", + "Mozilla/5.0 (X11; Linux x86_64) Gecko Firefox/5.0", + "Mozilla/5.0 (X11; Linux ppc; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (X11; Linux AMD64) Gecko Firefox/5.0", + "Mozilla/5.0 (X11; FreeBSD amd64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20110619 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 6.1.1; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.2; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.1; U; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.0; WOW64; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (Windows NT 5.0; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (U; Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20110324 Firefox/4.2a1pre", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.2a1pre) Gecko/20100101 Firefox/4.2a1pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110324 Firefox/4.2a1pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110323 Firefox/4.2a1pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.2a1pre) Gecko/20110208 Firefox/4.2a1pre", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.0b9pre) Gecko/20110111 Firefox/4.0b9pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b9pre) Gecko/20101228 Firefox/4.0b9pre", + "Mozilla/5.0 (Windows NT 5.1; rv:2.0b9pre) Gecko/20110105 Firefox/4.0b9pre", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b8pre) Gecko/20101114 Firefox/4.0b8pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101213 Firefox/4.0b8pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101128 Firefox/4.0b8pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b8pre) Gecko/20101114 Firefox/4.0b8pre", + "Mozilla/5.0 (Windows NT 5.1; rv:2.0b8pre) Gecko/20101127 Firefox/4.0b8pre", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8", + "Mozilla/4.0 (compatible; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8)", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b7pre) Gecko/20100921 Firefox/4.0b7pre", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b7) Gecko/20101111 Firefox/4.0b7", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b7) Gecko/20100101 Firefox/4.0b7", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b6pre) Gecko/20100903 Firefox/4.0b6pre Firefox/4.0b6pre", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.0b4) Gecko/20100818 Firefox/4.0b4", + "Mozilla/5.0 (X11; Linux i686; rv:2.0b3pre) Gecko/20100731 Firefox/4.0b3pre", + "Mozilla/5.0 (Windows NT 5.2; rv:2.0b13pre) Gecko/20110304 Firefox/4.0b13pre", + "Mozilla/5.0 (Windows NT 5.1; rv:2.0b13pre) Gecko/20110223 Firefox/4.0b13pre", + "Mozilla/5.0 (X11; Linux i686; rv:2.0b12pre) Gecko/20110204 Firefox/4.0b12pre", + "Mozilla/5.0 (X11; Linux i686; rv:2.0b12pre) Gecko/20100101 Firefox/4.0b12pre", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0b11pre) Gecko/20110128 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110131 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110129 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b11pre) Gecko/20110128 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b11pre) Gecko/20110126 Firefox/4.0b11pre", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b11pre) Gecko/20110126 Firefox/4.0b11pre", + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b10pre) Gecko/20110118 Firefox/4.0b10pre", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b10pre) Gecko/20110113 Firefox/4.0b10pre", + "Mozilla/5.0 (X11; Linux i686; rv:2.0b10) Gecko/20100101 Firefox/4.0b10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:2.0b10) Gecko/20110126 Firefox/4.0b10", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0b10) Gecko/20110126 Firefox/4.0b10", + "Mozilla/5.0 (X11; Linux x86_64; rv:2.0.1) Gecko/20110506 Firefox/4.0.1", + "Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20110518 Firefox/4.0.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:2.0.1) Gecko/20110606 Firefox/4.0.1", + "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:2.0) Gecko/20110307 Firefox/4.0", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:2.0) Gecko/20110404 Fedora/16-dev Firefox/4.0", + "Mozilla/5.0 (X11; Arch Linux i686; rv:2.0) Gecko/20110321 Firefox/4.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/4.0 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0) Gecko/20110319 Firefox/4.0", + "Mozilla/5.0 (Windows NT 6.1; rv:1.9) Gecko/20100101 Firefox/4.0", + "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/20121223 Ubuntu/9.25 (jaunty) Firefox/3.8", + "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", + "Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401 Mozilla/5.0 (X11; U; Linux i686; it-IT; rv:1.9.0.2) Gecko/2008092313 Ubuntu/9.25 (jaunty) Firefox/3.8", + "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.3a5pre) Gecko/20100526 Firefox/3.7a5pre", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b5) Gecko/20091204 Firefox/3.6b5", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2) Gecko/20091218 Firefox 3.6b5", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2a1pre) Gecko/20090428 Firefox/3.6a1pre", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2a1pre) Gecko/20090405 Firefox/3.6a1pre", + "Mozilla/5.0 (X11; U; Linux i686; ru-RU; rv:1.9.2a1pre) Gecko/20090405 Ubuntu/9.04 (jaunty) Firefox/3.6a1pre", + "Mozilla/5.0 (Windows; Windows NT 5.1; es-ES; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre", + "Mozilla/5.0 (Windows; Windows NT 5.1; en-US; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2a1pre) Gecko/20090402 Firefox/3.6a1pre (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.9) Gecko/20100915 Gentoo Firefox/3.6.9", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.9) Gecko/20100827 Red Hat/3.6.9-2.el6 Firefox/3.6.9", + "Mozilla/5.0 (X11; U; FreeBSD i386; en-US; rv:1.9.2.9) Gecko/20100913 Firefox/3.6.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9 ( .NET CLR 3.5.30729; .NET CLR 4.0.20506)", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-GB; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6;en-US; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9", + "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.2.8) Gecko/20101230 Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100804 Gentoo Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.8) Gecko/20100723 SUSE/3.6.8-0.1.1 Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.2.8) Gecko/20100722 Ubuntu/10.04 (lucid) Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; fi-FI; rv:1.9.2.8) Gecko/20100723 Ubuntu/10.04 (lucid) Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.8) Gecko/20100727 Firefox/3.6.8", + "Mozilla/5.0 (X11; U; Linux i686; de-DE; rv:1.9.2.8) Gecko/20100725 Gentoo Firefox/3.6.8", + "Mozilla/5.0 (X11; U; FreeBSD i386; de-CH; rv:1.9.2.8) Gecko/20100729 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.8) Gecko/20100722 AskTbADAP/3.9.1.14019 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.8) Gecko/20100722 Firefox 3.6.8 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.8) Gecko/20100722 Firefox 3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.2.3) Gecko/20121221 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-TW; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100809 Fedora/3.6.7-1.fc14 Firefox/3.6.7", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.7) Gecko/20100723 Fedora/3.6.7-1.fc13 Firefox/3.6.7", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.7) Gecko/20100726 CentOS/3.6-3.el5.centos Firefox/3.6.7", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.7 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-PT; rv:1.9.2.7) Gecko/20100713 Firefox/3.6.7 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 GTB7.0", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-PT; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; de-AT; rv:1.9.1.8) Gecko/20100625 Firefox/3.6.6", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.4) Gecko/20100614 Ubuntu/10.04 (lucid) Firefox/3.6.4", + "Mozilla/5.0 (X11; U; Linux i686; fa; rv:1.8.1.4) Gecko/20100527 Firefox/3.6.4", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.4) Gecko/20100625 Gentoo Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100527 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-CA; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 GTB7.0 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100513 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.4) Gecko/20100503 Firefox/3.6.4 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; nb-NO; rv:1.9.2.4) Gecko/20100611 Firefox/3.6.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.4) Gecko/20100523 Firefox/3.6.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3pre) Gecko/20100405 Firefox/3.6.3plugin1 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; he; rv:1.9.1b4pre) Gecko/20100405 Firefox/3.6.3plugin1", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.3) Gecko/20100403 Fedora/3.6.3-4.fc13 Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100403 Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.3) Gecko/20100401 SUSE/3.6.3-1.1 Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100404 Ubuntu/10.04 (lucid) Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3", + "Mozilla/5.0 (X11; U; Linux AMD64; en-US; rv:1.9.2.3) Gecko/20100403 Ubuntu/10.10 (maverick) Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; pl; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 GTB7.0 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ca; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.25) Gecko/20111212 Firefox/3.6.25 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.24) Gecko/20111103 Firefox/3.6.24", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-US; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.21) Gecko/20110830 Ubuntu/10.10 (maverick) Firefox/3.6.21", + "Mozilla/5.0 (X11; U; OpenBSD i386; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.20) Gecko/20110805 Ubuntu/10.04 (lucid) Firefox/3.6.20", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.20) Gecko/20110804 Red Hat/3.6-2.el5 Firefox/3.6.20", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; hu; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.20) Gecko/20110803 AskTbFWV5/3.13.0.17701 Firefox/3.6.20 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; cs; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.20", + "Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 GTB7.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.2) Gecko/20100316 AskTbSPC2/3.9.1.14019 Firefox/3.6.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; pl; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 GTB6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.0.04506.648)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2 ( .NET CLR 3.0.04506.30)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.7; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10pre) Gecko/20100902 Ubuntu/9.10 (karmic) Firefox/3.6.1pre", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.20) Gecko/20110803 Firefox/3.6.19", + "Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10.4; en-GB; rv:1.9.2.19) Gecko/20110707 Firefox/3.6.19", + "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", + "Mozilla/5.0 (X11; U; Linux i686; pl; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.18) Gecko/20110628 Ubuntu/10.10 (maverick) Firefox/3.6.18", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.18) Gecko/20110615 Ubuntu/10.10 (maverick) Firefox/3.6.18", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; pt-BR; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ar; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; pt-BR; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.18) Gecko/20110614 Firefox/3.6.18 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-GB; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", + "Mozilla/5.0 (X11; Linux i686 on x86_64; rv:5.0) Gecko/20100101 Firefox/3.6.17 Firefox/3.6.17", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; tr; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; sl; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (X11; U; Linux x86_64; ja-JP; rv:1.9.2.16) Gecko/20110323 Ubuntu/10.10 (maverick) Firefox/3.6.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16) Gecko/20110323 Ubuntu/9.10 (karmic) Firefox/3.6.16 FirePHP/0.5", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en; rv:1.9.1.13) Gecko/20100914 Firefox/3.6.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.16) Gecko/20110319 AskTbUTR/3.11.3.15590 Firefox/3.6.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.16pre) Gecko/20110304 Ubuntu/10.10 (maverick) Firefox/3.6.15pre", + "Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.2.15) Gecko/20110303 Ubuntu/8.04 (hardy) Firefox/3.6.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.15) Gecko/20110303 Ubuntu/10.04 (lucid) Firefox/3.6.15 FirePHP/0.5", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.15) Gecko/20110330 CentOS/3.6-1.el5.centos Firefox/3.6.15", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; es-ES; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C) FirePHP/0.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.2.15) Gecko/20110303 AskTbBT4/3.11.3.15590 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.14pre) Gecko/20110105 Firefox/3.6.14pre", + "Mozilla/5.0 (X11; U; Linux armv7l; en-US; rv:1.9.2.14) Gecko/20110224 Firefox/3.6.14 MB860/Version.0.43.3.MB860.AmericaMovil.en.MX", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-AU; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.14) Gecko/20110218 Firefox/3.6.14 GTB7.1 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.2.13) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; nb-NO; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.04 (lucid) Firefox/3.6.13 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.13) Gecko/20110103 Fedora/3.6.13-1.fc14 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101223 Gentoo Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101219 Gentoo Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Red Hat/3.6-3.el4 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-NZ; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.13) Gecko/20101206 Ubuntu/9.10 (karmic) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-GB; rv:1.9.2.13) Gecko/20101206 Red Hat/3.6-2.el5 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux x86_64; da-DK; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux MIPS32 1074Kf CPS QuadCore; en-US; rv:1.9.2.13) Gecko/20110103 Fedora/3.6.13-1.fc14 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; pt-BR; rv:1.9.2.13) Gecko/20101209 Fedora/3.6.13-1.fc13 Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.2.13) Gecko/20101206 Ubuntu/9.10 (karmic) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101209 CentOS/3.6-2.el5.centos Firefox/3.6.13", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13", + "Mozilla/5.0 (X11; U; NetBSD i386; en-US; rv:1.9.2.12) Gecko/20101030 Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; es-MX; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.04 (lucid) Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.2.12) Gecko/20101027 Fedora/3.6.12-1.fc13 Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.2.12) Gecko/20101026 SUSE/3.6.12-0.7.1 Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101102 Gentoo Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.12) Gecko/20101102 Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux ppc; fr; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux i686; ko-KR; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101114 Gentoo Firefox/3.6.12", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.10 (maverick) Firefox/3.6.12 GTB7.1", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.12) Gecko/20101027 Fedora/3.6.12-1.fc13 Firefox/3.6.12", + "Mozilla/5.0 (X11; FreeBSD x86_64; rv:2.0) Gecko/20100101 Firefox/3.6.12", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; sv-SE; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 (.NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; .NET CLR 3.5.21022)", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; de; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12 GTB5", + "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.2.11) Gecko/20101028 CentOS/3.6-2.el5.centos Firefox/3.6.11", + "Mozilla/5.0 (X11; U; Linux armv7l; en-GB; rv:1.9.2.3pre) Gecko/20100723 Firefox/3.6.11", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; ru; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux x86_64; pt-BR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux x86_64; pl-PL; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10 GTB7.1", + "Mozilla/5.0 (X11; U; Linux x86_64; el-GR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10 GTB7.1", + "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; pl-PL; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.10) Gecko/20100915 Ubuntu/9.04 (jaunty) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.2.11) Gecko/20101013 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; en-CA; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100915 Ubuntu/9.10 (karmic) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100915 Ubuntu/10.04 (lucid) Firefox/3.6.10", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.2.10) Gecko/20100914 SUSE/3.6.10-0.3.1 Firefox/3.6.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ro; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; nl; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.1) Gecko/20100122 firefox/3.6.1", + "Mozilla/5.0(Windows; U; Windows NT 7.0; rv:1.9.2) Gecko/20100101 Firefox/3.6", + "Mozilla/5.0(Windows; U; Windows NT 5.2; rv:1.9.2) Gecko/20100101 Firefox/3.6", + "Mozilla/5.0 (X11; U; x86_64 Linux; en_GB, en_US; rv:1.9.2) Gecko/20100115 Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100222 Ubuntu/10.04 (lucid) Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100130 Gentoo Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2) Gecko/20100308 Ubuntu/10.04 (lucid) Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2pre) Gecko/20100312 Ubuntu/9.04 (jaunty) Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100128 Gentoo Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100115 Ubuntu/10.04 (lucid) Firefox/3.6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2) Gecko/20100115 Firefox/3.6 FirePHP/0.4", + "Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20100101 Firefox/3.6", + "Mozilla/5.0 (X11; FreeBSD i686) Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU; rv:1.9.2) Gecko/20100105 MRA 5.6 (build 03278) Firefox/3.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; lt; rv:1.9.2) Gecko/20100115 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.3a3pre) Gecko/20100306 Firefox3.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100806 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.3) Gecko/20100401 Firefox/3.6;MEGAUPLOAD 1.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ar; rv:1.9.2) Gecko/20100115 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.2) Gecko/20100115 Firefox/3.6", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b5pre) Gecko/20090517 Firefox/3.5b4pre (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b4pre) Gecko/20090409 Firefox/3.5b4pre", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b4pre) Gecko/20090401 Firefox/3.5b4pre", + "Mozilla/5.0 (X11; U; Linux i686; nl-NL; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 GTB5 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; fr; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b4) Gecko/20090423 Firefox/3.5b4 GTB5", + "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.1.9) Gecko/20100402 Ubuntu/9.10 (karmic) Firefox/3.5.9 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.1.9) Gecko/20100330 Fedora/3.5.9-2.fc12 Firefox/3.5.9", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1.1 Firefox/3.5.9 GTB7.0", + "Mozilla/5.0 (X11; U; Linux x86_64; es-CL; rv:1.9.1.9) Gecko/20100402 Ubuntu/9.10 (karmic) Firefox/3.5.9", + "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1.1 Firefox/3.5.9", + "Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.1.9) Gecko/20100401 Ubuntu/9.10 (karmic) Firefox/3.5.9", + "Mozilla/5.0 (X11; U; Linux i686; hu-HU; rv:1.9.1.9) Gecko/20100330 Fedora/3.5.9-1.fc12 Firefox/3.5.9", + "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.9) Gecko/20100317 SUSE/3.5.9-0.1 Firefox/3.5.9", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100401 Ubuntu/9.10 (karmic) Firefox/3.5.9 GTB7.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100315 Ubuntu/9.10 (karmic) Firefox/3.5.9", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.4) Gecko/20091028 Ubuntu/9.10 (karmic) Firefox/3.5.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; tr; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; hu; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; et; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; es-ES; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB5 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.2.13) Gecko/20101203 Firefox/3.5.9 (de)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9 GTB7.0 (.NET CLR 3.0.30618)", + "Mozilla/5.0 (X11; U; Linux x86_64; ru; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8", + "Mozilla/5.0 (X11; U; Linux x86_64; es-ES; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc11 Firefox/3.5.8", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.8) Gecko/20100318 Gentoo Firefox/3.5.8", + "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8", + "Mozilla/5.0 (X11; U; Linux i686; ja-JP; rv:1.9.1.8) Gecko/20100216 Fedora/3.5.8-1.fc12 Firefox/3.5.8", + "Mozilla/5.0 (X11; U; Linux i686; es-AR; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8", + "Mozilla/5.0 (X11; U; FreeBSD i386; ja-JP; rv:1.9.1.8) Gecko/20100305 Firefox/3.5.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; sl; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 (.NET CLR 3.5.30729) FirePHP/0.4", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 GTB6", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8 GTB7.0 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2) Gecko/20100305 Gentoo Firefox/3.5.7", + "Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.1.7) Gecko/20100106 Ubuntu/9.10 (karmic) Firefox/3.5.7", + "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.7) Gecko/20091222 SUSE/3.5.7-1.1.1 Firefox/3.5.7", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; ja; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; fr; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 (.NET CLR 3.0.04506.648)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; fa; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.7) Gecko/20091221 MRA 5.5 (build 02842) Firefox/3.5.7 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.6) Gecko/20100117 Gentoo Firefox/3.5.6", + "Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.9.1.6) Gecko/20091216 Fedora/3.5.6-1.fc11 Firefox/3.5.6 GTB6", + "Mozilla/5.0 (X11; U; Linux i686; es-ES; rv:1.9.1.6) Gecko/20091201 SUSE/3.5.6-1.1.1 Firefox/3.5.6 GTB6", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.6) Gecko/20100118 Gentoo Firefox/3.5.6", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6 GTB6", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6 GTB7.0", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091201 SUSE/3.5.6-1.1.1 Firefox/3.5.6", + "Mozilla/5.0 (X11; U; Linux i686; cs-CZ; rv:1.9.1.6) Gecko/20100107 Fedora/3.5.6-1.fc12 Firefox/3.5.6", + "Mozilla/5.0 (X11; U; Linux i686; ca; rv:1.9.1.6) Gecko/20091215 Ubuntu/9.10 (karmic) Firefox/3.5.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; it; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; id; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.4 (build 02647) Firefox/3.5.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.5 (build 02842) Firefox/3.5.6 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 MRA 5.5 (build 02842) Firefox/3.5.6", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 GTB6 (.NET CLR 3.5.30729) FBSMTWB", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729) FBSMTWB", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.5) Gecko/20091109 Ubuntu/9.10 (karmic) Firefox/3.5.5", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.8pre) Gecko/20091227 Ubuntu/9.10 (karmic) Firefox/3.5.5", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.5) Gecko/20091114 Gentoo Firefox/3.5.5", + "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; uk; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 MRA 5.5 (build 02842) Firefox/3.5.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; ru; rv:1.9.1.5) Gecko/20091102 MRA 5.5 (build 02842) Firefox/3.5.5", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.1.5) Gecko/Firefox/3.5.5", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.5) Gecko/20091102 MRA 5.5 (build 02842) Firefox/3.5.5 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.5) Gecko/20091102 MRA 5.5 (build 02842) Firefox/3.5.5", + "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.8.1) Gecko/20091102 Firefox/3.5.5", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; pl; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 FBSMTWB", + "Mozilla/5.0 (X11; U; Linux x86_64; ja; rv:1.9.1.4) Gecko/20091016 SUSE/3.5.4-1.1.2 Firefox/3.5.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4 (.NET CLR 3.5.30729) FBSMTWB", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.4) Gecko/20091007 Firefox/3.5.4", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru-RU; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.4) Gecko/20091016 Firefox/3.5.4 ( .NET CLR 3.5.30729; .NET4.0E)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.4) Gecko/20091007 Firefox/3.5.4", + "Mozilla/5.0 (X11; U; Linux x86_64; fr; rv:1.9.1.5) Gecko/20091109 Ubuntu/9.10 (karmic) Firefox/3.5.3pre", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090914 Slackware/13.0_stable Firefox/3.5.3", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3", + "Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9.1.3) Gecko/20091020 Ubuntu/9.10 (karmic) Firefox/3.5.3", + "Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.3) Gecko/20090919 Firefox/3.5.3", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.3) Gecko/20090912 Gentoo Firefox/3.5.3 FirePHP/0.3", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB5", + "Mozilla/5.0 (X11; U; FreeBSD i386; ru-RU; rv:1.9.1.3) Gecko/20090913 Firefox/3.5.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.5.3;MEGAUPLOAD 1.0 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; ko; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; fi; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 2.0.50727; .NET CLR 3.0.30618; .NET CLR 3.5.21022; .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; bg; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ko; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; pl; rv:1.9.1.2) Gecko/20090911 Slackware Firefox/3.5.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.2) Gecko/20090803 Slackware Firefox/3.5.2", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.2) Gecko/20090803 Firefox/3.5.2 Slackware", + "Mozilla/5.0 (X11; U; Linux i686; ru-RU; rv:1.9.1.2) Gecko/20090804 Firefox/3.5.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.2) Gecko/20090729 Slackware/13.0 Firefox/3.5.2", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", + "Mozilla/5.0 (X11; U; Linux i686 (x86_64); fr; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; pl; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 GTB7.1 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; es-MX; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; uk; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; es-ES; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 FirePHP/0.4", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.1.16) Gecko/20101130 AskTbMYC/3.9.1.14019 Firefox/3.5.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; it; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 GTB7.1 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.16) Gecko/20101130 MRA 5.4 (build 02647) Firefox/3.5.16 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 GTB7.1", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.16) Gecko/20101130 AskTbPLTV5/3.8.0.12304 Firefox/3.5.16 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 GTB7.1 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.1.16) Gecko/20101130 Firefox/3.5.16 GTB7.1", + "Mozilla/5.0 (X11; U; Linux x86_64; it; rv:1.9.1.15) Gecko/20101027 Fedora/3.5.15-1.fc12 Firefox/3.5.15", + "Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.1.15) Gecko/20101027 Fedora/3.5.15-1.fc12 Firefox/3.5.15", + "Mozilla/5.0 (Windows; U; Windows NT 5.0; ru; rv:1.9.1.13) Gecko/20100914 Firefox/3.5.13", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.12) Gecko/2009070611 Firefox/3.5.12", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.1.12) Gecko/20100824 MRA 5.7 (build 03755) Firefox/3.5.12", + "Mozilla/5.0 (X11; U; Linux; en-US; rv:1.9.1.11) Gecko/20100720 Firefox/3.5.11", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.1.11) Gecko/20100701 Firefox/3.5.11 ( .NET CLR 3.5.30729; .NET4.0C)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; pt-BR; rv:1.9.1.11) Gecko/20100701 Firefox/3.5.11 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; hu; rv:1.9.1.11) Gecko/20100701 Firefox/3.5.11", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.10) Gecko/20100504 Firefox/3.5.11 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.1.10) Gecko/20100506 SUSE/3.5.10-0.1.1 Firefox/3.5.10", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-GB; rv:1.9.1.10) Gecko/20100504 Firefox/3.5.10 GTB7.0 ( .NET CLR 3.5.30729)", + "Mozilla/5.0 (X11; U; Linux x86_64; rv:1.9.1.1) Gecko/20090716 Linux Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100524 Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.1) Gecko/20090716 Linux Mint/7 (Gloria) Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.1) Gecko/20090716 Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.1) Gecko/20090714 SUSE/3.5.1-1.1 Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux x86; rv:1.9.1.1) Gecko/20090716 Linux Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux i686; nl-NL; rv:1.9.0.19) Gecko/20090720 Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.2pre) Gecko/20090729 Ubuntu/9.04 (jaunty) Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1 GTB5", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.1) Gecko/20090722 Gentoo Firefox/3.5.1", + "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.1) Gecko/20090714 SUSE/3.5.1-1.1 Firefox/3.5.1", + "Mozilla/5.0 (X11; U; DragonFly i386; de; rv:1.9.1) Gecko/20090720 Firefox/3.5.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) Gecko/20090718 Firefox/3.5.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; tr; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1 (.NET CLR 3.5.30729)", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; sv-SE; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1 (.NET CLR 3.5.30729)", + "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00", + "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", + "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52", + "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.9.168 Version/11.51", + "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51", + "Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50", + "Opera/9.80 (X11; Linux i686; U; hu) Presto/2.9.168 Version/11.50", + "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11", + "Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.8.131 Version/11.11", + "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/5.0 Opera 11.11", + "Opera/9.80 (X11; Linux x86_64; U; bg) Presto/2.8.131 Version/11.10", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.8.99 Version/11.10", + "Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10", + "Opera/9.80 (Windows NT 6.1; Opera Tablet/15165; U; en) Presto/2.8.149 Version/11.1", + "Opera/9.80 (X11; Linux x86_64; U; Ubuntu/10.10 (maverick); pl) Presto/2.7.62 Version/11.01", + "Opera/9.80 (X11; Linux i686; U; ja) Presto/2.7.62 Version/11.01", + "Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; sv) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 5.1; U;) Presto/2.7.62 Version/11.01", + "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.7.62 Version/11.01", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101213 Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", + "Mozilla/5.0 (Windows NT 6.1; U; nl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01", + "Mozilla/5.0 (Windows NT 6.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.01", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; de) Opera 11.01", + "Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.00", + "Opera/9.80 (X11; Linux i686; U; it) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.37 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; ko) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1; U; en-GB) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.1 x64; U; en) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.7.39 Version/11.00", + "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.7.39 Version/11.00", + "Opera/9.80 (Windows NT 5.1; U; MRA 5.5 (build 02842); ru) Presto/2.7.62 Version/11.00", + "Opera/9.80 (Windows NT 5.1; U; it) Presto/2.7.62 Version/11.00", + "Mozilla/5.0 (Windows NT 6.0; U; ja; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", + "Mozilla/5.0 (Windows NT 5.1; U; pl; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", + "Mozilla/5.0 (Windows NT 5.1; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; pl) Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; fr) Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; ja) Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; en) Opera 11.00", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; pl) Opera 11.00", + "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.6.31 Version/10.70", + "Mozilla/5.0 (Windows NT 5.2; U; ru; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.70", + "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.70", + "Opera/9.80 (Windows NT 5.2; U; zh-cn) Presto/2.6.30 Version/10.63", + "Opera/9.80 (Windows NT 5.2; U; en) Presto/2.6.30 Version/10.63", + "Opera/9.80 (Windows NT 5.1; U; MRA 5.6 (build 03278); ru) Presto/2.6.30 Version/10.63", + "Opera/9.80 (Windows NT 5.1; U; pl) Presto/2.6.30 Version/10.62", + "Mozilla/5.0 (X11; Linux x86_64; U; de; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.62", + "Mozilla/4.0 (compatible; MSIE 8.0; X11; Linux x86_64; de) Opera 10.62", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; en) Opera 10.62", + "Opera/9.80 (X11; Linux i686; U; pl) Presto/2.6.30 Version/10.61", + "Opera/9.80 (X11; Linux i686; U; es-ES) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows NT 6.0; U; it) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Windows 98; U; de) Presto/2.6.30 Version/10.61", + "Opera/9.80 (Macintosh; Intel Mac OS X; U; nl) Presto/2.6.30 Version/10.61", + "Opera/9.80 (X11; Linux i686; U; en) Presto/2.5.27 Version/10.60", + "Opera/9.80 (Windows NT 6.0; U; nl) Presto/2.6.30 Version/10.60", + "Opera/10.60 (Windows NT 5.1; U; zh-cn) Presto/2.6.30 Version/10.60", + "Opera/10.60 (Windows NT 5.1; U; en-US) Presto/2.6.30 Version/10.60", + "Opera/9.80 (X11; Linux i686; U; it) Presto/2.5.24 Version/10.54", + "Opera/9.80 (X11; Linux i686; U; en-GB) Presto/2.5.24 Version/10.53", + "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", + "Mozilla/5.0 (Windows NT 5.1; U; Firefox/5.0; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", + "Mozilla/5.0 (Windows NT 5.1; U; Firefox/4.5; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", + "Mozilla/5.0 (Windows NT 5.1; U; Firefox/3.5; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.53", + "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; ko) Opera 10.53", + "Opera/9.80 (Windows NT 6.1; U; fr) Presto/2.5.24 Version/10.52", + "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.5.22 Version/10.51", + "Opera/9.80 (Windows NT 6.0; U; cs) Presto/2.5.22 Version/10.51", + "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.5.22 Version/10.51", + "Opera/9.80 (Linux i686; U; en) Presto/2.5.22 Version/10.51", + "Mozilla/5.0 (Windows NT 6.1; U; en-GB; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.51", + "Mozilla/5.0 (Linux i686; U; en; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 Opera 10.51", + "Mozilla/4.0 (compatible; MSIE 8.0; Linux i686; en) Opera 10.51", + "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 6.1; U; sk) Presto/2.6.22 Version/10.50", + "Opera/9.80 (Windows NT 6.1; U; ja) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 6.0; U; zh-cn) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 5.1; U; sk) Presto/2.5.22 Version/10.50", + "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.5.22 Version/10.50", + "Opera/10.50 (Windows NT 6.1; U; en-GB) Presto/2.2.2", + "Opera/9.80 (S60; SymbOS; Opera Tablet/9174; U; en) Presto/2.7.81 Version/10.5", + "Opera/9.80 (X11; U; Linux i686; en-US; rv:1.9.2.3) Presto/2.2.15 Version/10.10", + "Opera/9.80 (X11; Linux x86_64; U; it) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 6.1; U; de) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 6.0; U; Gecko/20100115; pl) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 5.1; U; de) Presto/2.2.15 Version/10.10", + "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.2.15 Version/10.10", + "Mozilla/5.0 (Windows NT 6.0; U; tr; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 10.10", + "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux i686; de) Opera 10.10", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0; tr) Opera 10.10", + "Opera/9.80 (X11; Linux x86_64; U; en-GB) Presto/2.2.15 Version/10.01", + "Opera/9.80 (X11; Linux x86_64; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux x86_64; U; de) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; ru) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; pt-BR) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; pl) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; nb) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; en-GB) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; Debian; pl) Presto/2.2.15 Version/10.00", + "Opera/9.80 (X11; Linux i686; U; de) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; de) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 6.0; U; de) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 5.2; U; en) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.2.15 Version/10.00", + "Opera/9.80 (Windows NT 5.1; U; ru) Presto/2.2.15 Version/10.00", + "Opera/9.99 (X11; U; sk)", + "Opera/9.99 (Windows NT 5.1; U; pl) Presto/9.9.9", + "Opera/9.80 (J2ME/MIDP; Opera Mini/5.0 (Windows; U; Windows NT 5.1; en) AppleWebKit/886; U; en) Presto/2.4.15", + "Opera/9.70 (Linux ppc64 ; U; en) Presto/2.2.1", + "Opera/9.70 (Linux i686 ; U; zh-cn) Presto/2.2.0", + "Opera/9.70 (Linux i686 ; U; en-us) Presto/2.2.0", + "Opera/9.70 (Linux i686 ; U; en) Presto/2.2.1", + "Opera/9.70 (Linux i686 ; U; en) Presto/2.2.0", + "Opera/9.70 (Linux i686 ; U; ; en) Presto/2.2.1", + "Opera/9.70 (Linux i686 ; U; ; en) Presto/2.2.1", + "Mozilla/5.0 (Linux i686 ; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.70", + "Mozilla/4.0 (compatible; MSIE 6.0; Linux i686 ; en) Opera 9.70", + "Opera/9.64(Windows NT 5.1; U; en) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; pl) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; hr) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; en-GB) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; en) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; de) Presto/2.1.1", + "Opera/9.64 (X11; Linux x86_64; U; cs) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; tr) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; sv) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; pl) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; nb) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; Linux Mint; nb) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; Linux Mint; it) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; en) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; de) Presto/2.1.1", + "Opera/9.64 (X11; Linux i686; U; da) Presto/2.1.1", + "Opera/9.64 (Windows NT 6.1; U; MRA 5.5 (build 02842); ru) Presto/2.1.1", + "Opera/9.64 (Windows NT 6.1; U; de) Presto/2.1.1", + "Opera/9.64 (Windows NT 6.0; U; zh-cn) Presto/2.1.1", + "Opera/9.64 (Windows NT 6.0; U; pl) Presto/2.1.1", + "Opera/9.63 (X11; Linux x86_64; U; ru) Presto/2.1.1", + "Opera/9.63 (X11; Linux x86_64; U; cs) Presto/2.1.1", + "Opera/9.63 (X11; Linux i686; U; ru) Presto/2.1.1", + "Opera/9.63 (X11; Linux i686; U; ru)", + "Opera/9.63 (X11; Linux i686; U; nb) Presto/2.1.1", + "Opera/9.63 (X11; Linux i686; U; en)", + "Opera/9.63 (X11; Linux i686; U; de) Presto/2.1.1", + "Opera/9.63 (X11; Linux i686)", + "Opera/9.63 (X11; FreeBSD 7.1-RELEASE i386; U; en) Presto/2.1.1", + "Opera/9.63 (Windows NT 6.1; U; hu) Presto/2.1.1", + "Opera/9.63 (Windows NT 6.1; U; en) Presto/2.1.1", + "Opera/9.63 (Windows NT 6.1; U; de) Presto/2.1.1", + "Opera/9.63 (Windows NT 6.0; U; pl) Presto/2.1.1", + "Opera/9.63 (Windows NT 6.0; U; nb) Presto/2.1.1", + "Opera/9.63 (Windows NT 6.0; U; fr) Presto/2.1.1", + "Opera/9.63 (Windows NT 6.0; U; en) Presto/2.1.1", + "Opera/9.63 (Windows NT 6.0; U; cs) Presto/2.1.1", + "Opera/9.63 (Windows NT 5.2; U; en) Presto/2.1.1", + "Opera/9.63 (Windows NT 5.2; U; de) Presto/2.1.1", + "Opera/9.63 (Windows NT 5.1; U; pt-BR) Presto/2.1.1", + "Opera/9.62 (X11; Linux x86_64; U; ru) Presto/2.1.1", + "Opera/9.62 (X11; Linux x86_64; U; en_GB, en_US) Presto/2.1.1", + "Opera/9.62 (X11; Linux i686; U; pt-BR) Presto/2.1.1", + "Opera/9.62 (X11; Linux i686; U; Linux Mint; en) Presto/2.1.1", + "Opera/9.62 (X11; Linux i686; U; it) Presto/2.1.1", + "Opera/9.62 (X11; Linux i686; U; fi) Presto/2.1.1", + "Opera/9.62 (X11; Linux i686; U; en) Presto/2.1.1", + "Opera/9.62 (Windows NT 6.1; U; en) Presto/2.1.1", + "Opera/9.62 (Windows NT 6.1; U; de) Presto/2.1.1", + "Opera/9.62 (Windows NT 6.0; U; pl) Presto/2.1.1", + "Opera/9.62 (Windows NT 6.0; U; nb) Presto/2.1.1", + "Opera/9.62 (Windows NT 6.0; U; en-GB) Presto/2.1.1", + "Opera/9.62 (Windows NT 6.0; U; en) Presto/2.1.1", + "Opera/9.62 (Windows NT 6.0; U; de) Presto/2.1.1", + "Opera/9.62 (Windows NT 5.2; U; en) Presto/2.1.1", + "Opera/9.62 (Windows NT 5.1; U; zh-tw) Presto/2.1.1", + "Opera/9.62 (Windows NT 5.1; U; zh-cn) Presto/2.1.1", + "Opera/9.62 (Windows NT 5.1; U; tr) Presto/2.1.1", + "Opera/9.62 (Windows NT 5.1; U; ru) Presto/2.1.1", + "Opera/9.62 (Windows NT 5.1; U; pt-BR) Presto/2.1.1", + "Opera/9.61 (X11; Linux x86_64; U; fr) Presto/2.1.1", + "Opera/9.61 (X11; Linux i686; U; ru) Presto/2.1.1", + "Opera/9.61 (X11; Linux i686; U; pl) Presto/2.1.1", + "Opera/9.61 (X11; Linux i686; U; en) Presto/2.1.1", + "Opera/9.61 (X11; Linux i686; U; de) Presto/2.1.1", + "Opera/9.61 (Windows NT 6.0; U; ru) Presto/2.1.1", + "Opera/9.61 (Windows NT 6.0; U; pt-BR) Presto/2.1.1", + "Opera/9.61 (Windows NT 6.0; U; http://lucideer.com; en-GB) Presto/2.1.1", + "Opera/9.61 (Windows NT 6.0; U; en) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.2; U; en) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.1; U; zh-tw) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.1; U; zh-cn) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.1; U; ru) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.1; U; fr) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.1; U; en-GB) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.1; U; en) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.1; U; de) Presto/2.1.1", + "Opera/9.61 (Windows NT 5.1; U; cs) Presto/2.1.1", + "Opera/9.61 (Macintosh; Intel Mac OS X; U; de) Presto/2.1.1", + "Mozilla/5.0 (Windows NT 5.1; U; en-GB; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.61", + "Opera/9.60 (X11; Linux x86_64; U)", + "Opera/9.60 (X11; Linux i686; U; ru) Presto/2.1.1", + "Opera/9.60 (X11; Linux i686; U; en-GB) Presto/2.1.1", + "Opera/9.60 (Windows NT 6.0; U; uk) Presto/2.1.1", + "Opera/9.60 (Windows NT 6.0; U; ru) Presto/2.1.1", + "Opera/9.60 (Windows NT 6.0; U; pl) Presto/2.1.1", + "Opera/9.60 (Windows NT 6.0; U; de) Presto/2.1.1", + "Opera/9.60 (Windows NT 6.0; U; bg) Presto/2.1.1", + "Opera/9.60 (Windows NT 5.1; U; tr) Presto/2.1.1", + "Opera/9.60 (Windows NT 5.1; U; sv) Presto/2.1.1", + "Opera/9.60 (Windows NT 5.1; U; es-ES) Presto/2.1.1", + "Opera/9.60 (Windows NT 5.1; U; en-GB) Presto/2.1.1", + "Opera/9.60 (Windows NT 5.0; U; en) Presto/2.1.1", + "Mozilla/5.0 (X11; Linux x86_64; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.60", + "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux x86_64; en) Opera 9.60", + "Opera/9.52 (X11; Linux x86_64; U; ru)", + "Opera/9.52 (X11; Linux x86_64; U; en)", + "Opera/9.52 (X11; Linux x86_64; U)", + "Opera/9.52 (X11; Linux ppc; U; de)", + "Opera/9.52 (X11; Linux i686; U; fr)", + "Opera/9.52 (X11; Linux i686; U; en)", + "Opera/9.52 (X11; Linux i686; U; cs)", + "Opera/9.52 (Windows NT 6.0; U; Opera/9.52 (X11; Linux x86_64; U); en)", + "Opera/9.52 (Windows NT 6.0; U; fr)", + "Opera/9.52 (Windows NT 6.0; U; en)", + "Opera/9.52 (Windows NT 6.0; U; de)", + "Opera/9.52 (Windows NT 5.2; U; ru)", + "Opera/9.52 (Windows NT 5.0; U; en)", + "Opera/9.52 (Macintosh; PPC Mac OS X; U; ja)", + "Opera/9.52 (Macintosh; PPC Mac OS X; U; fr)", + "Opera/9.52 (Macintosh; Intel Mac OS X; U; pt-BR)", + "Opera/9.52 (Macintosh; Intel Mac OS X; U; pt)", + "Mozilla/5.0 (Windows NT 5.1; U; de; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.52", + "Mozilla/5.0 (Windows NT 5.1; U; ; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.52", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; ru) Opera 9.52", + "Opera/9.51 (X11; Linux i686; U; Linux Mint; en)", + "Opera/9.51 (X11; Linux i686; U; fr)", + "Opera/9.51 (X11; Linux i686; U; de)", + "Opera/9.51 (Windows NT 6.0; U; sv)", + "Opera/9.51 (Windows NT 6.0; U; es)", + "Opera/9.51 (Windows NT 6.0; U; en)", + "Opera/9.51 (Windows NT 5.2; U; en)", + "Opera/9.51 (Windows NT 5.1; U; nn)", + "Opera/9.51 (Windows NT 5.1; U; fr)", + "Opera/9.51 (Windows NT 5.1; U; es-LA)", + "Opera/9.51 (Windows NT 5.1; U; es-AR)", + "Opera/9.51 (Windows NT 5.1; U; en-GB)", + "Opera/9.51 (Windows NT 5.1; U; en)", + "Opera/9.51 (Windows NT 5.1; U; da)", + "Opera/9.51 (Macintosh; Intel Mac OS X; U; en)", + "Mozilla/5.0 (X11; Linux i686; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", + "Mozilla/5.0 (Windows NT 6.0; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", + "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", + "Mozilla/5.0 (Windows NT 5.1; U; en-GB; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", + "Mozilla/5.0 (Windows NT 5.1; U; de; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.51", + "Opera/9.50 (X11; Linux x86_64; U; pl)", + "Opera/9.50 (X11; Linux x86_64; U; nb)", + "Opera/9.50 (X11; Linux ppc; U; en)", + "Opera/9.50 (X11; Linux i686; U; es-ES)", + "Opera/9.50 (Windows NT 5.2; U; it)", + "Opera/9.50 (Windows NT 5.1; U; ru)", + "Opera/9.50 (Windows NT 5.1; U; nn)", + "Opera/9.50 (Windows NT 5.1; U; nl)", + "Opera/9.50 (Windows NT 5.1; U; it)", + "Opera/9.50 (Windows NT 5.1; U; es-ES)", + "Opera/9.50 (Macintosh; Intel Mac OS X; U; en)", + "Opera/9.50 (Macintosh; Intel Mac OS X; U; de)", + "Mozilla/5.0 (Windows NT 5.1; U; zh-cn; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50", + "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux x86_64; en) Opera 9.50", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0; en) Opera 9.50", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; en) Opera 9.50", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; de) Opera 9.50", + "Opera/9.5 (Windows NT 6.0; U; en)", + "Opera/9.5 (Windows NT 5.1; U; fr)", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9b3) Gecko/2008020514 Opera 9.5", + "Opera 9.4 (Windows NT 6.1; U; en)", + "Opera 9.4 (Windows NT 5.3; U; en)", + "Opera/9.30 (Nintendo Wii; U; ; 2071; Wii Shop Channel/1.0; en)", + "Opera/9.30 (Nintendo Wii; U; ; 2047-7;pt-br)", + "Opera/9.30 (Nintendo Wii; U; ; 2047-7;es)", + "Opera/9.30 (Nintendo Wii; U; ; 2047-7;en)", + "Opera/9.30 (Nintendo Wii; U; ; 2047-7; fr)", + "Opera/9.30 (Nintendo Wii; U; ; 2047-7; de)", + "Opera/9.27 (X11; Linux i686; U; fr)", + "Opera/9.27 (X11; Linux i686; U; en)", + "Opera/9.27 (Windows NT 5.2; U; en)", + "Opera/9.27 (Windows NT 5.1; U; ja)", + "Opera/9.27 (Macintosh; Intel Mac OS X; U; sv)", + "Mozilla/5.0 (Windows NT 5.2; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.27", + "Mozilla/5.0 (Windows NT 5.1; U; es-la; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.27", + "Mozilla/5.0 (Macintosh; Intel Mac OS X; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.27", + "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux i686; en) Opera 9.27", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; en) Opera 9.27", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; es-la) Opera 9.27", + "Opera/9.26 (Windows; U; pl)", + "Opera/9.26 (Windows NT 5.1; U; zh-cn)", + "Opera/9.26 (Windows NT 5.1; U; pl)", + "Opera/9.26 (Windows NT 5.1; U; nl)", + "Opera/9.26 (Windows NT 5.1; U; MEGAUPLOAD 2.0; en)", + "Opera/9.26 (Windows NT 5.1; U; de)", + "Opera/9.26 (Macintosh; PPC Mac OS X; U; en)", + "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.26", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 6.0; en) Opera 9.26", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.26", + "Opera/9.25 (X11; Linux i686; U; fr-ca)", + "Opera/9.25 (X11; Linux i686; U; fr)", + "Opera/9.25 (X11; Linux i686; U; en)", + "Opera/9.25 (Windows NT 6.0; U; SV1; MEGAUPLOAD 2.0; ru)", + "Opera/9.25 (Windows NT 6.0; U; sv)", + "Opera/9.25 (Windows NT 6.0; U; ru)", + "Opera/9.25 (Windows NT 6.0; U; MEGAUPLOAD 1.0; ru)", + "Opera/9.25 (Windows NT 6.0; U; en-US)", + "Opera/9.25 (Windows NT 5.2; U; en)", + "Opera/9.25 (Windows NT 5.1; U; zh-cn)", + "Opera/9.25 (Windows NT 5.1; U; ru)", + "Opera/9.25 (Windows NT 5.1; U; MEGAUPLOAD 1.0; pt-br)", + "Opera/9.25 (Windows NT 5.1; U; lt)", + "Opera/9.25 (Windows NT 5.1; U; de)", + "Opera/9.25 (Windows NT 5.0; U; en)", + "Opera/9.25 (Windows NT 5.0; U; cs)", + "Opera/9.25 (Windows NT 4.0; U; en)", + "Opera/9.25 (OpenSolaris; U; en)", + "Opera/9.25 (Macintosh; PPC Mac OS X; U; en)", + "Opera/9.25 (Macintosh; Intel Mac OS X; U; en)", + "Opera/9.24 (X11; SunOS i86pc; U; en)", + "Opera/9.24 (X11; Linux i686; U; de)", + "Opera/9.24 (Windows NT 5.1; U; tr)", + "Opera/9.24 (Windows NT 5.1; U; ru)", + "Opera/9.24 (Windows NT 5.0; U; ru)", + "Opera/9.24 (Macintosh; PPC Mac OS X; U; en)", + "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.24", + "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.24", + "Mozilla/4.0 (compatible; MSIE 6.0; Mac_PowerPC; en) Opera 9.24", + "Opera/9.23 (X11; Linux x86_64; U; en)", + "Opera/9.23 (X11; Linux i686; U; es-es)", + "Opera/9.23 (X11; Linux i686; U; en)", + "Opera/9.23 (Windows NT 6.0; U; de)", + "Opera/9.23 (Windows NT 5.1; U; zh-cn)", + "Opera/9.23 (Windows NT 5.1; U; SV1; MEGAUPLOAD 1.0; ru)", + "Opera/9.23 (Windows NT 5.1; U; pt)", + "Opera/9.23 (Windows NT 5.1; U; ja)", + "Opera/9.23 (Windows NT 5.1; U; it)", + "Opera/9.23 (Windows NT 5.1; U; fi)", + "Opera/9.23 (Windows NT 5.1; U; en)", + "Opera/9.23 (Windows NT 5.1; U; de)", + "Opera/9.23 (Windows NT 5.1; U; da)", + "Opera/9.23 (Windows NT 5.0; U; en)", + "Opera/9.23 (Windows NT 5.0; U; de)", + "Opera/9.23 (Nintendo Wii; U; ; 1038-58; Wii Internet Channel/1.0; en)", + "Opera/9.23 (Macintosh; Intel Mac OS X; U; ja)", + "Opera/9.23 (Mac OS X; ru)", + "Opera/9.23 (Mac OS X; fr)", + "Mozilla/5.0 (X11; Linux i686; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.23", + "Opera/9.22 (X11; OpenBSD i386; U; en)", + "Opera/9.22 (X11; Linux i686; U; en)", + "Opera/9.22 (X11; Linux i686; U; de)", + "Opera/9.22 (Windows NT 6.0; U; ru)", + "Opera/9.22 (Windows NT 6.0; U; en)", + "Opera/9.22 (Windows NT 5.1; U; SV1; MEGAUPLOAD 2.0; ru)", + "Opera/9.22 (Windows NT 5.1; U; SV1; MEGAUPLOAD 1.0; ru)", + "Opera/9.22 (Windows NT 5.1; U; pl)", + "Opera/9.22 (Windows NT 5.1; U; fr)", + "Opera/9.22 (Windows NT 5.1; U; en)", + "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.0) Gecko/20060728 Firefox/1.5.0 Opera 9.22", + "Mozilla/4.0 (compatible; MSIE 6.0; X11; Linux i686; en) Opera 9.22", + "Opera/9.21 (X11; Linux x86_64; U; en)", + "Opera/9.21 (X11; Linux i686; U; es-es)", + "Opera/9.21 (X11; Linux i686; U; en)", + "Opera/9.21 (X11; Linux i686; U; de)", + "Opera/9.21 (Windows NT 6.0; U; nb)", + "Opera/9.21 (Windows NT 6.0; U; en)", + "Opera/9.21 (Windows NT 5.2; U; en)", + "Opera/9.21 (Windows NT 5.1; U; SV1; MEGAUPLOAD 1.0; ru)", + "Opera/9.21 (Windows NT 5.1; U; ru)", + "Opera/9.21 (Windows NT 5.1; U; pt-br)", + "Opera/9.21 (Windows NT 5.1; U; pl)", + "Opera/9.21 (Windows NT 5.1; U; nl)", + "Opera/9.21 (Windows NT 5.1; U; MEGAUPLOAD 1.0; en)", + "Opera/9.21 (Windows NT 5.1; U; fr)", + "Opera/9.21 (Windows NT 5.1; U; en)", + "Opera/9.21 (Windows NT 5.1; U; de)", + "Opera/9.21 (Windows NT 5.0; U; de)", + "Opera/9.21 (Windows 98; U; en)", + "Opera/9.21 (Macintosh; PPC Mac OS X; U; en)", + "Opera/9.21 (Macintosh; Intel Mac OS X; U; en)", + "Opera/9.20(Windows NT 5.1; U; en)", + "Opera/9.20 (X11; Linux x86_64; U; en)", + "Opera/9.20 (X11; Linux ppc; U; en)", + "Opera/9.20 (X11; Linux i686; U; tr)", + "Opera/9.20 (X11; Linux i686; U; ru)", + "Opera/9.20 (X11; Linux i686; U; pl)", + "Opera/9.20 (X11; Linux i686; U; es-es)", + "Opera/9.20 (X11; Linux i686; U; en)", + "Opera/9.20 (X11; Linux i586; U; en)", + "Opera/9.20 (Windows NT 6.0; U; es-es)", + "Opera/9.20 (Windows NT 6.0; U; en)", + "Opera/9.20 (Windows NT 6.0; U; de)", + "Opera/9.20 (Windows NT 5.2; U; en)", + "Opera/9.20 (Windows NT 5.1; U; zh-tw)", + "Opera/9.20 (Windows NT 5.1; U; nb)", + "Opera/9.20 (Windows NT 5.1; U; MEGAUPLOAD=1.0; es-es)", + "Opera/9.20 (Windows NT 5.1; U; it)", + "Opera/9.20 (Windows NT 5.1; U; es-es)", + "Opera/9.20 (Windows NT 5.1; U; es-AR)", + "Opera/9.20 (Windows NT 5.1; U; en)", + "Opera/9.12 (X11; Linux i686; U; en) (Ubuntu)", + "Opera/9.12 (Windows NT 5.0; U; ru)", + "Opera/9.12 (Windows NT 5.0; U)", + "Opera/9.10 (X11; Linux; U; en)", + "Opera/9.10 (X11; Linux x86_64; U; en)", + "Opera/9.10 (X11; Linux i686; U; pl)", + "Opera/9.10 (X11; Linux i686; U; kubuntu;pl)", + "Opera/9.10 (X11; Linux i686; U; en)", + "Opera/9.10 (X11; Linux i386; U; en)", + "Opera/9.10 (Windows NT 6.0; U; it-IT)", + "Opera/9.10 (Windows NT 6.0; U; en)", + "Opera/9.10 (Windows NT 5.2; U; en)", + "Opera/9.10 (Windows NT 5.2; U; de)", + "Opera/9.10 (Windows NT 5.1; U; zh-tw)", + "Opera/9.10 (Windows NT 5.1; U; sv)", + "Opera/9.10 (Windows NT 5.1; U; pt)", + "Opera/9.10 (Windows NT 5.1; U; pl)", + "Opera/9.10 (Windows NT 5.1; U; nl)", + "Opera/9.10 (Windows NT 5.1; U; MEGAUPLOAD 1.0; pl)", + "Opera/9.10 (Windows NT 5.1; U; it)", + "Opera/9.10 (Windows NT 5.1; U; hu)", + "Opera/9.10 (Windows NT 5.1; U; fi)", + "Opera/9.10 (Windows NT 5.1; U; es-es)", + "Opera/9.02 (X11; Linux i686; U; pl)", + "Opera/9.02 (X11; Linux i686; U; hu)", + "Opera/9.02 (X11; Linux i686; U; en)", + "Opera/9.02 (X11; Linux i686; U; de)", + "Opera/9.02 (Windows NT 5.2; U; en)", + "Opera/9.02 (Windows NT 5.2; U; de)", + "Opera/9.02 (Windows NT 5.1; U; zh-cn)", + "Opera/9.02 (Windows NT 5.1; U; ru)", + "Opera/9.02 (Windows NT 5.1; U; pt-br)", + "Opera/9.02 (Windows NT 5.1; U; pl)", + "Opera/9.02 (Windows NT 5.1; U; nb)", + "Opera/9.02 (Windows NT 5.1; U; ja)", + "Opera/9.02 (Windows NT 5.1; U; fi)", + "Opera/9.02 (Windows NT 5.1; U; en)", + "Opera/9.02 (Windows NT 5.1; U; de)", + "Opera/9.02 (Windows NT 5.0; U; sv)", + "Opera/9.02 (Windows NT 5.0; U; pl)", + "Opera/9.02 (Windows NT 5.0; U; en)", + "Opera/9.01 (X11; OpenBSD i386; U; en)", + "Opera/9.01 (X11; Linux i686; U; en)", + "Opera/9.01 (X11; FreeBSD 6 i386; U;pl)", + "Opera/9.01 (X11; FreeBSD 6 i386; U; en)", + "Opera/9.01 (Windows NT 5.2; U; ru)", + "Opera/9.01 (Windows NT 5.2; U; en)", + "Opera/9.01 (Windows NT 5.1; U; ru)", + "Opera/9.01 (Windows NT 5.1; U; pl)", + "Opera/9.01 (Windows NT 5.1; U; ja)", + "Opera/9.01 (Windows NT 5.1; U; es-es)", + "Opera/9.01 (Windows NT 5.1; U; en)", + "Opera/9.01 (Windows NT 5.1; U; de)", + "Opera/9.01 (Windows NT 5.1; U; da)", + "Opera/9.01 (Windows NT 5.1; U; cs)", + "Opera/9.01 (Windows NT 5.1; U; bg)", + "Opera/9.01 (Windows NT 5.0; U; en)", + "Opera/9.01 (Windows NT 5.0; U; de)", + "Opera/9.01 (Macintosh; PPC Mac OS X; U; it)", + "Opera/9.01 (Macintosh; PPC Mac OS X; U; en)", + "Opera/9.00 (X11; Linux i686; U; pl)", + "Opera/9.00 (X11; Linux i686; U; en)", + "Opera/9.00 (X11; Linux i686; U; de)", + "Opera/9.00 (Windows NT 5.2; U; ru)", + "Opera/9.00 (Windows NT 5.2; U; pl)", + "Opera/9.00 (Windows NT 5.2; U; en)", + "Opera/9.00 (Windows NT 5.1; U; ru)", + "Opera/9.00 (Windows NT 5.1; U; pl)", + "Opera/9.00 (Windows NT 5.1; U; nl)", + "Opera/9.00 (Windows NT 5.1; U; ja)", + "Opera/9.00 (Windows NT 5.1; U; it)", + "Opera/9.00 (Windows NT 5.1; U; fr)", + "Opera/9.00 (Windows NT 5.1; U; fi)", + "Opera/9.00 (Windows NT 5.1; U; es-es)", + "Opera/9.00 (Windows NT 5.1; U; en)", + "Opera/9.00 (Windows NT 5.1; U; de)", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.12 Safari/535.11", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.8 (KHTML, like Gecko) Chrome/17.0.940.0 Safari/535.8", + "Mozilla/5.0 (X11; CrOS i686 1193.158.0) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7xs5D9rRDFpg2g", + "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.36 Safari/535.7", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.6 (KHTML, like Gecko) Chrome/16.0.897.0 Safari/535.6", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.54 Safari/535.2", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.10 Chromium/15.0.874.120 Chrome/15.0.874.120 Safari/535.2", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.120 Safari/535.2", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.872.0 Safari/535.2", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.2 (KHTML, like Gecko) Ubuntu/11.04 Chromium/15.0.871.0 Chrome/15.0.871.0 Safari/535.2", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.864.0 Safari/535.2", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.861.0 Safari/535.2", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.860.0 Safari/535.2", + "Chrome/15.0.860.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/15.0.860.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.186 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.825.0 Chrome/14.0.825.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.824.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.10913 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.815.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.814.0 Chrome/14.0.814.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.814.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.813.0 Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.813.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.812.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.811.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.810.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.809.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.10 Chromium/14.0.808.0 Chrome/14.0.808.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.808.0 Chrome/14.0.808.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/10.04 Chromium/14.0.804.0 Chrome/14.0.804.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/14.0.803.0 Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.803.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.801.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.801.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.794.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.792.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.790.0 Safari/535.1", + "Mozilla/5.0 (X11; CrOS i686 13.587.48) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.43 Safari/535.1", + "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41", + "Mozilla/5.0 ArchLinux (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Ubuntu/11.04 Chromium/13.0.782.41 Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.2; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_3) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.32 Safari/535.1", + "Mozilla/5.0 (X11; Linux amd64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.24 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.215 Safari/535.1", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.20 Safari/535.1", + "Mozilla/5.0 (X11; CrOS i686 0.13.587) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.14 Safari/535.1", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.107 Safari/535.1", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.766.0 Safari/534.36", + "Mozilla/5.0 (X11; Linux amd64) AppleWebKit/534.36 (KHTML, like Gecko) Chrome/13.0.766.0 Safari/534.36", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.35 (KHTML, like Gecko) Ubuntu/10.10 Chromium/13.0.764.0 Chrome/13.0.764.0 Safari/534.35", + "Mozilla/5.0 (X11; CrOS i686 0.13.507) AppleWebKit/534.35 (KHTML, like Gecko) Chrome/13.0.763.0 Safari/534.35", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.33 (KHTML, like Gecko) Ubuntu/9.10 Chromium/13.0.752.0 Chrome/13.0.752.0 Safari/534.33", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.31 (KHTML, like Gecko) Chrome/13.0.748.0 Safari/534.31", + "Mozilla/5.0 (Windows NT 6.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.750.0 Safari/534.30", + "Mozilla/5.0 (X11; CrOS i686 12.433.109) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.93 Safari/534.30", + "Mozilla/5.0 (X11; CrOS i686 12.0.742.91) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.93 Safari/534.30", + "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/12.0.742.91", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.91 Chromium/12.0.742.91 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30", + "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.60 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.53 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.113 Safari/534.30", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/11.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/11.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Ubuntu/10.04 Chromium/12.0.742.112 Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Windows NT 7.1) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Windows 8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30", + "Mozilla/5.0 (X11; CrOS i686 12.433.216) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.105 Safari/534.30", + "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 ArchLinux (X11; U; Linux x86_64; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Slackware/Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_4) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.100 Safari/534.30", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.724.100 Safari/534.30", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.706.0 Safari/534.25", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.25 (KHTML, like Gecko) Chrome/12.0.704.0 Safari/534.25", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.703.0 Chrome/12.0.703.0 Safari/534.24", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.10 Chromium/12.0.702.0 Chrome/12.0.702.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/12.0.702.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.700.3 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.699.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.699.0 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.698.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.697.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_7) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24", + "Mozilla/5.0 Slackware/13.37 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/11.0.696.50", + "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.43 Safari/534.24", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.3 Safari/534.24", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.14 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.12 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_6) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.12 Safari/534.24", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Ubuntu/10.04 Chromium/11.0.696.0 Chrome/11.0.696.0 Safari/534.24", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.0 Safari/534.24", + "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.694.0 Safari/534.24", + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.23 (KHTML, like Gecko) Chrome/11.0.686.3 Safari/534.23", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.682.0 Safari/534.21", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_0; en-US) AppleWebKit/534.21 (KHTML, like Gecko) Chrome/11.0.678.0 Safari/534.21", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", + "Mozilla/5.0 (Windows NT) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.672.2 Safari/534.20", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Chrome/11.0.669.0 Safari/534.20", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.19 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.19", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.661.0 Safari/534.18", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.18 (KHTML, like Gecko) Chrome/11.0.660.0 Safari/534.18", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.655.0 Safari/534.17", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.654.0 Safari/534.17", + "Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.652.0 Safari/534.17", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/10.0.649.0 Safari/534.17", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.82 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux armv7l; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", + "Mozilla/5.0 (X11; U; FreeBSD x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", + "Mozilla/5.0 (X11; U; FreeBSD i386; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.204", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.134 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.133 Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.133 Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.127 Chrome/10.0.648.127 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.127 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; ru-RU) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.11 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.0 Chrome/10.0.648.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.648.0 Chrome/10.0.648.0 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.642.0 Chrome/10.0.642.0 Safari/534.16", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.639.0 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.638.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.634.0 Safari/534.16", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.634.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 SUSE/10.0.626.0 (KHTML, like Gecko) Chrome/10.0.626.0 Safari/534.16", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.613.0 Safari/534.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.613.0 Chrome/10.0.613.0 Safari/534.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.04 Chromium/10.0.612.3 Chrome/10.0.612.3 Safari/534.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Chrome/10.0.612.1 Safari/534.15", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.15 (KHTML, like Gecko) Ubuntu/10.10 Chromium/10.0.611.0 Chrome/10.0.611.0 Safari/534.15", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.602.0 Safari/534.14", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/10.0.601.0 Safari/534.14", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML,like Gecko) Chrome/9.1.0.0 Safari/540.0", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML, like Gecko) Ubuntu/10.10 Chrome/9.1.0.0 Safari/540.0", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.601.0 Safari/534.14", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Ubuntu/10.10 Chromium/9.0.600.0 Chrome/9.0.600.0 Safari/534.14", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.14 (KHTML, like Gecko) Chrome/9.0.600.0 Safari/534.14", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.599.0 Safari/534.13", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.84 Safari/534.13", + "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.44 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.19 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13", + "Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.0 Safari/534.13" + ] +} diff --git a/lib/addic7ed/services/get_shows_list.rb b/lib/addic7ed/services/get_shows_list.rb index adb44f5..9a66bfb 100644 --- a/lib/addic7ed/services/get_shows_list.rb +++ b/lib/addic7ed/services/get_shows_list.rb @@ -1,3 +1,5 @@ +require "singleton" + module Addic7ed class GetShowsList include Singleton diff --git a/spec/lib/addic7ed/common_spec.rb b/spec/lib/addic7ed/common_spec.rb index d4e6faf..ee34d15 100644 --- a/spec/lib/addic7ed/common_spec.rb +++ b/spec/lib/addic7ed/common_spec.rb @@ -9,12 +9,12 @@ expect(Addic7ed::EPISODES_URL).to_not be_nil end - it "defines EPISODE_REDIRECT_URL" do - expect(Addic7ed::EPISODE_REDIRECT_URL).to_not be_nil - end - it "defines LANGUAGES" do expect(Addic7ed::LANGUAGES).to_not be_nil - expect(Addic7ed::LANGUAGES).to include "fr" => { name: "French", id: 8 } + expect(Addic7ed::LANGUAGES).to include fr: { name: "French", id: 8 } + end + + it "defines USER_AGENTS" do + expect(Addic7ed::USER_AGENTS).to_not be_nil end end diff --git a/spec/lib/addic7ed/models/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb index b87307b..fdee1ce 100644 --- a/spec/lib/addic7ed/models/episode_spec.rb +++ b/spec/lib/addic7ed/models/episode_spec.rb @@ -7,7 +7,7 @@ let(:episode) { described_class.new(showname, season, episode_nbr) } before do - %w(fr en it).each do |lang| + [:fr, :en, :it].each do |lang| lang_id = Addic7ed::LANGUAGES[lang][:id] stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/#{lang_id}") .to_return File.new("spec/responses/walking-dead-3-2-#{lang_id}.http") @@ -15,17 +15,17 @@ end it "returns an array of Addic7ed::Subtitle given valid episode and language" do - expect(episode.subtitles("fr").size).to eq 4 - expect(episode.subtitles("en").size).to eq 3 - expect(episode.subtitles("it").size).to eq 1 + expect(episode.subtitles(:fr).size).to eq 4 + expect(episode.subtitles(:en).size).to eq 3 + expect(episode.subtitles(:it).size).to eq 1 end it "raises LanguageNotSupported given an unsupported language code" do - expect { episode.subtitles("aa") }.to raise_error Addic7ed::LanguageNotSupported + expect { episode.subtitles(:aa) }.to raise_error Addic7ed::LanguageNotSupported end it "memoizes results to minimize network requests" do - 2.times { episode.subtitles("en") } + 2.times { episode.subtitles(:en) } expect( a_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1") ).to have_been_made.times(1) @@ -43,7 +43,7 @@ it "raises EpisodeNotFound" do expect do - described_class.new(showname, season, episode_nbr).subtitles("fr") + described_class.new(showname, season, episode_nbr).subtitles(:fr) end.to raise_error Addic7ed::EpisodeNotFound end end @@ -57,7 +57,7 @@ end it "raises NoSubtitleFound" do - expect { episode.subtitles("az") }.to raise_error Addic7ed::NoSubtitleFound + expect { episode.subtitles(:az) }.to raise_error Addic7ed::NoSubtitleFound end end end @@ -69,18 +69,18 @@ let(:episode) { described_class.new(showname, season, episode_nbr) } it "returns an episode page URL for given language" do - expect(episode.page_url("fr")).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" - expect(episode.page_url("es")).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4" + expect(episode.page_url(:fr)).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" + expect(episode.page_url(:es)).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4" end it "uses URLEncodeShowName to generate the localized URLs" do expect( Addic7ed::URLEncodeShowName ).to receive(:call).with(showname).and_return("The_Walking_Dead") - episode.page_url("fr") + episode.page_url(:fr) end it "raises LanguageNotSupported given an unsupported language code" do - expect { episode.page_url("aa") }.to raise_error Addic7ed::LanguageNotSupported + expect { episode.page_url(:aa) }.to raise_error Addic7ed::LanguageNotSupported end end From 25a14ac76ddae2ac6d7800e9286fbafdf9acd7c0 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 9 Jul 2016 17:05:14 +0400 Subject: [PATCH 25/89] Minor one-line refactor --- lib/addic7ed/services/download_subtitle.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/addic7ed/services/download_subtitle.rb b/lib/addic7ed/services/download_subtitle.rb index f9db309..7903a4a 100644 --- a/lib/addic7ed/services/download_subtitle.rb +++ b/lib/addic7ed/services/download_subtitle.rb @@ -45,9 +45,7 @@ def follow_redirection(location_header) end def write(content) - Kernel.open(filename, "w") do |f| - f << content - end + Kernel.open(filename, "w") { |f| f << content } rescue raise DownloadError, "Cannot write to disk" end From 57d9c58d52c64d9a5f1b7ad3f8a620a446134c9f Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 11 Jul 2016 22:43:11 +0400 Subject: [PATCH 26/89] Move compatibility and best-subtitle logics to new CheckCompatibility and SubtitlesCollection classes --- README.md | 14 +- lib/addic7ed/models/search.rb | 30 ++-- lib/addic7ed/models/subtitle.rb | 53 ------ lib/addic7ed/models/subtitles_collection.rb | 21 +++ lib/addic7ed/services/check_compatibility.rb | 44 +++++ spec/lib/addic7ed/models/search_spec.rb | 46 ----- spec/lib/addic7ed/models/subtitle_spec.rb | 160 ------------------ .../models/subtitles_collection_spec.rb | 62 +++++++ .../services/check_compatibility_spec.rb | 88 ++++++++++ 9 files changed, 241 insertions(+), 277 deletions(-) create mode 100644 lib/addic7ed/models/subtitles_collection.rb create mode 100644 lib/addic7ed/services/check_compatibility.rb create mode 100644 spec/lib/addic7ed/models/subtitles_collection_spec.rb create mode 100644 spec/lib/addic7ed/services/check_compatibility_spec.rb diff --git a/README.md b/README.md index 86f8d38..a9fc974 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,25 @@ Ruby command-line script to fetch subtitles on Addic7ed * [x] move download logic to a service object * [x] add a new `Search` model -* [ ] move compatibility logic to a `CheckCompatibility` service object -* [ ] move best subtitle logic to a service object +* [x] move compatibility logic to a `CheckCompatibility` service object +* [x] create a `SubtitlesCollection` class to hold and filter subtitles +* [x] move best subtitle logic to `SubtitlesCollection` +* [ ] rename `Subtitle#via` to `Subtitle#source` +* [ ] rename `Subtitle#version` to `Subtitle#group` * [x] refactor how `Episode` holds `Subtitle`s * [x] rename `ShowList` and make it a service object +* [ ] write code documentation * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") * [x] use symbols rather than strings for languages -* [ ] write code documentation -* [ ] update README * [x] add Rubocop * [x] move user agents and languages to a config file * [ ] add specs for parsing * [ ] move CLI to a separate gem (and use Thor or similar) +* [ ] update README to include only API stuff +* [ ] remove `bin/addic7ed` +* [ ] move all `to_s` and `to_inspect` methods to the CLI +* [ ] add `bin/console` ### Is it working ? diff --git a/lib/addic7ed/models/search.rb b/lib/addic7ed/models/search.rb index 1eb20b8..021eca9 100644 --- a/lib/addic7ed/models/search.rb +++ b/lib/addic7ed/models/search.rb @@ -1,11 +1,11 @@ module Addic7ed class Search - attr_reader :video_filename, :language, :options + attr_reader :video_filename, :language, :criterias - def initialize(video_filename, language, options = {}) + def initialize(video_filename, language, criterias = {}) @video_filename = video_filename @language = language - @options = default_options.merge(options) + @criterias = default_criterias.merge(criterias) end def video_file @@ -16,22 +16,24 @@ def episode @episode ||= Episode.new(video_file.showname, video_file.season, video_file.episode) end - def best_subtitle - episode.subtitles(language).each do |subtitle| - @best_subtitle = subtitle if better_than_best_subtitle?(subtitle) - end - @best_subtitle || raise(NoSubtitleFound) + def all_subtitles + @all_subtitles ||= SubtitlesCollection.new(episode.subtitles(language)) end - private + def matching_subtitles + @matching_subtitles ||= all_subtitles.completed.compatible_with(video_file.group) + end - def better_than_best_subtitle?(subtitle) - subtitle.works_for?(video_file.group, options[:no_hi]) && - subtitle.can_replace?(@best_subtitle) + def best_subtitle + @best_subtitle ||= matching_subtitles.most_popular end - def default_options - { no_hi: false } + private + + def default_criterias + { + no_hi: false + } end end end diff --git a/lib/addic7ed/models/subtitle.rb b/lib/addic7ed/models/subtitle.rb index cb20a67..fed2e55 100644 --- a/lib/addic7ed/models/subtitle.rb +++ b/lib/addic7ed/models/subtitle.rb @@ -20,61 +20,8 @@ def to_s str end - def works_for?(version = "", no_hi = false) - hi_works = !@hi || !no_hi - completed? && compatible_with?(version) && hi_works - end - - def can_replace?(other_subtitle) - return false unless completed? - return true if other_subtitle.nil? - language == other_subtitle.language && - compatible_with?(other_subtitle.version) && - more_popular_than?(other_subtitle) - end - - def featured? - via == "http://addic7ed.com" - end - def completed? status == "Completed" end - - protected - - def compatible_with?(other_version) - defined_as_compatible_with(other_version) || - generally_compatible_with?(other_version) || - commented_as_compatible_with?(other_version) - end - - def defined_as_compatible_with(other_version) - version.split(",").include? other_version - end - - def generally_compatible_with?(other_version) - COMPATIBILITY_720P[version] == other_version || COMPATIBILITY_720P[other_version] == version - end - - def commented_as_compatible_with?(other_version) - return false if /(won't|doesn't|not) +work/i =~ comment - return false if /resync +(from|of)/i =~ comment - known_compatible_versions(other_version) - .push(other_version) - .map(&:downcase) - .map { |compatible_version| comment.include? compatible_version } - .reduce(:|) - end - - def known_compatible_versions(other_version) - [COMPATIBILITY_720P[other_version], COMPATIBILITY_720P[version]].compact - end - - def more_popular_than?(other_subtitle) - return true if other_subtitle.nil? - return false if other_subtitle.featured? - downloads > other_subtitle.downloads - end end end diff --git a/lib/addic7ed/models/subtitles_collection.rb b/lib/addic7ed/models/subtitles_collection.rb new file mode 100644 index 0000000..5df361b --- /dev/null +++ b/lib/addic7ed/models/subtitles_collection.rb @@ -0,0 +1,21 @@ +module Addic7ed + class SubtitlesCollection < Array + def compatible_with(group) + select { |subtitle| CheckCompatibility.call(subtitle, group) } + end + + def completed + select(&:completed?) + end + + def most_popular + sort_by(&:downloads).last + end + + private + + def select + SubtitlesCollection.new(super) + end + end +end diff --git a/lib/addic7ed/services/check_compatibility.rb b/lib/addic7ed/services/check_compatibility.rb new file mode 100644 index 0000000..d4e64db --- /dev/null +++ b/lib/addic7ed/services/check_compatibility.rb @@ -0,0 +1,44 @@ +module Addic7ed + class CheckCompatibility + include Service + + attr_reader :subtitle, :group + + def initialize(subtitle, group) + @subtitle = subtitle + @group = group + end + + def call + defined_as_compatible? || generally_compatible? || commented_as_compatible? + end + + private + + def defined_as_compatible? + subtitle.version.split(",").include? group + end + + def generally_compatible? + COMPATIBILITY_720P[subtitle.version] == group || COMPATIBILITY_720P[group] == subtitle.version + end + + def commented_as_compatible? + return false if /(won'?t|doesn'?t|not) +work/i =~ subtitle.comment + return false if /resync +(from|of|for)/i =~ subtitle.comment + !!comment_matches_a_compatible_group? + end + + def comment_matches_a_compatible_group? + Regexp.new("(#{compatible_groups.join('|')})", "i") =~ subtitle.comment + end + + def compatible_groups + @compatible_groups ||= [ + group, + COMPATIBILITY_720P[group], + COMPATIBILITY_720P[subtitle.version] + ].compact.uniq + end + end +end diff --git a/spec/lib/addic7ed/models/search_spec.rb b/spec/lib/addic7ed/models/search_spec.rb index 7870279..2a8e877 100644 --- a/spec/lib/addic7ed/models/search_spec.rb +++ b/spec/lib/addic7ed/models/search_spec.rb @@ -36,50 +36,4 @@ expect(Addic7ed::Episode).to have_received(:new).with("Game.of.Thrones", 6, 9) end end - -# rubocop:disable all - #### - #### THE FOLLOWING WILL BE MOVED TO A NEW SERVICE OBJECT AND REWRITTEN - #### - # describe "#best_subtitle" do - # let(:video_filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.mkv" } - # let(:video_filename_no_hi) { "The.Walking.Dead.S03E02.720p.HDTV.x264-KILLERS.mkv" } - # let(:video_filename_compatible_group) { "The.Walking.Dead.S03E04.HDTV.XviD-ASAP.mkv" } - # let(:episode) { Addic7ed::Episode.new(video_filename, language, options) } - # - # it "finds the subtitle with status completed and same group name" do - # stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8") - # .to_return File.new("spec/responses/walking-dead-3-2-8.http") - # expect(episode.best_subtitle("fr").url).to eq "http://www.addic7ed.com/original/68018/4" - # end - # - # it "finds the subtitle with status completed, compatible group name and as many downloads as possible" do - # stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/4/8") - # .to_return File.new("spec/responses/walking-dead-3-4-8.http") - # compatible_episode = Addic7ed::Episode.new(filename_compatible_group) - # expect(compatible_episode.best_subtitle("fr").url).to eq "http://www.addic7ed.com/updated/8/68508/3" - # end - # - # it "finds the subtitle with status completed, same group name and not hearing impaired" do - # stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1") - # .to_return File.new("spec/responses/walking-dead-3-2-1.http") - # episode = Addic7ed::Episode.new(filename_no_hi) - # expect(episode.best_subtitle("en", true).url).to eq "http://www.addic7ed.com/updated/1/68018/0" - # end - # - # it "uses English as default language" do - # expect(episode.best_subtitle).to eq episode.best_subtitle("en") - # end - # - # it "raises LanguageNotSupported given an unsupported language code" do - # expect{ episode.best_subtitle("aa") }.to raise_error Addic7ed::LanguageNotSupported - # end - # - # it "raises NoSubtitleFound given valid episode which has no subtitle on Addic7ed" do - # stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48") - # .to_return File.new("spec/responses/walking-dead-3-2-48.http") - # expect{ episode.best_subtitle("az") }.to raise_error Addic7ed::NoSubtitleFound - # end - # end -# rubocop:enable:all end diff --git a/spec/lib/addic7ed/models/subtitle_spec.rb b/spec/lib/addic7ed/models/subtitle_spec.rb index 43018d3..4a8bf43 100644 --- a/spec/lib/addic7ed/models/subtitle_spec.rb +++ b/spec/lib/addic7ed/models/subtitle_spec.rb @@ -37,166 +37,6 @@ end end -describe Addic7ed::Subtitle, "#works_for?" do - let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION") } - - context "when it is incomplete" do - before { allow(subtitle).to receive(:completed?).and_return(false) } - - it "returns false" do - expect(subtitle.works_for?("DIMENSION")).to be false - end - end - - context "when it is completed" do - before { allow(subtitle).to receive(:completed?).and_return(true) } - - it "returns true given the exact same version" do - expect(subtitle.works_for?("DIMENSION")).to be true - end - - it "returns true given a compatible version" do - expect(subtitle.works_for?("LOL")).to be true - end - - it "returns false given an incompatible version" do - expect(subtitle.works_for?("EVOLVE")).to be false - end - - context "when is has a compatibility comment" do - let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Works with IMMERSE") } - - it "returns true given the same version as comment" do - expect(subtitle.works_for?("IMMERSE")).to be true - end - - it "returns true given a compatible version as comment" do - expect(subtitle.works_for?("ASAP")).to be true - end - - it "returns false given an incompatible version as comment" do - expect(subtitle.works_for?("KILLERS")).to be false - end - end - - context "when is has an incompatibility comment" do - let(:subtitle) do - Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Doesn't work with IMMERSE") - end - - it "returns false" do - expect(subtitle.works_for?("IMMERSE")).to be false - end - end - - context "when is has an ambiguous comment" do - let(:subtitle) do - Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Resync from IMMERSE") - end - - it "returns false" do - expect(subtitle.works_for?("IMMERSE")).to be false - end - end - - context "when it has multiple versions" do - let(:subtitle) do - Addic7ed::Subtitle.new(version: "FOV,TLA") - end - - it "returns true if it works for one of them" do - expect(subtitle.works_for?("TLA")).to be true - expect(subtitle.works_for?("FOV")).to be true - end - - it "returns false when none of them work" do - expect(subtitle.works_for?("LOL")).to be false - end - end - end -end - -describe Addic7ed::Subtitle, "#can_replace?" do - let(:subtitle) { Addic7ed::Subtitle.new(defaults) } - let(:other_subtitle) { Addic7ed::Subtitle.new(defaults) } - let(:defaults) do - { - version: "DIMENSION", - language: "fr", - status: "Completed", - downloads: "10" - } - end - - context "when it is incomplete" do - before { allow(subtitle).to receive(:completed?).and_return(false) } - - it "returns false" do - expect(subtitle.can_replace?(other_subtitle)).to be false - end - end - - context "when it is completed" do - before { allow(subtitle).to receive(:completed?).and_return(true) } - - it "returns true given no other_subtitle" do - expect(subtitle.can_replace?(nil)).to be true - end - - context "when other_subtitle has a different language" do - before { allow(other_subtitle).to receive(:language).and_return("en") } - - it "returns false" do - expect(subtitle.can_replace?(other_subtitle)).to be false - end - end - - context "when other_subtitle has an incompatible version" do - before do - allow(subtitle).to receive(:compatible_with?).with(other_subtitle.version).and_return(false) - end - - it "returns false" do - expect(subtitle.can_replace?(other_subtitle)).to be false - end - end - - context "when other_subtitle is featured by Addic7ed" do - before { allow(other_subtitle).to receive(:featured?).and_return(true) } - - it "returns false" do - expect(subtitle.can_replace?(other_subtitle)).to be false - end - end - - context "when other_subtitle has more downloads" do - before { allow(other_subtitle).to receive(:downloads).and_return(subtitle.downloads + 1) } - - it "returns false" do - expect(subtitle.can_replace?(other_subtitle)).to be false - end - end - - context "when other_subtitle has less downloads" do - before { allow(other_subtitle).to receive(:downloads).and_return(subtitle.downloads - 1) } - - it "returns true" do - expect(subtitle.can_replace?(other_subtitle)).to be true - end - end - end -end - -describe Addic7ed::Subtitle, "#featured?" do - it "returns true when 'via' is 'http://addic7ed.com'" do - expect(Addic7ed::Subtitle.new(via: "http://addic7ed.com").featured?).to be true - end - - it "returns false otherwise" do - expect(Addic7ed::Subtitle.new(via: "anything else").featured?).to be false - end -end - describe Addic7ed::Subtitle, "#completed?" do it "returns true when 'status' is 'Completed'" do expect(Addic7ed::Subtitle.new(status: "Completed").completed?).to be true diff --git a/spec/lib/addic7ed/models/subtitles_collection_spec.rb b/spec/lib/addic7ed/models/subtitles_collection_spec.rb new file mode 100644 index 0000000..98e34ef --- /dev/null +++ b/spec/lib/addic7ed/models/subtitles_collection_spec.rb @@ -0,0 +1,62 @@ +require "spec_helper" + +describe Addic7ed::SubtitlesCollection do + it "inherits from Array" do + expect(described_class.superclass).to eq Array + end +end + +describe Addic7ed::SubtitlesCollection, "#compatible_with(group)" do + let(:compatible_subtitle) { double(:compatible_subtitle) } + let(:incompatible_subtitle) { double(:incompatible_subtitle) } + + before do + allow(Addic7ed::CheckCompatibility).to receive(:call) do |subtitle, _| + case subtitle + when compatible_subtitle then true + when incompatible_subtitle then false + end + end + end + + subject { described_class.new([compatible_subtitle, incompatible_subtitle]) } + + it "uses Addic7ed::CheckCompatibility" do + subject.compatible_with("group") + expect(Addic7ed::CheckCompatibility).to have_received(:call).twice + end + + it "keeps compatible subtitles" do + expect(subject.compatible_with("group")).to include compatible_subtitle + end + + it "filers out incompatible subtitles" do + expect(subject.compatible_with("group")).to_not include incompatible_subtitle + end +end + +describe Addic7ed::SubtitlesCollection, "#completed" do + let(:completed_subtitle) { double(:completed_subtitle, completed?: true) } + let(:incomplete_subtitle) { double(:incomplete_subtitle, completed?: false) } + + subject { described_class.new([completed_subtitle, incomplete_subtitle]) } + + it "keeps completed subtitles" do + expect(subject.completed).to include completed_subtitle + end + + it "filters out incomplete subtitles" do + expect(subject.completed).to_not include incomplete_subtitle + end +end + +describe Addic7ed::SubtitlesCollection, "#most_popular" do + let(:popular_subtitle) { double(:popular_subtitle, downloads: 100) } + let(:unpopular_subtitle) { double(:popular_subtitle, downloads: 20) } + + subject { described_class.new([popular_subtitle, unpopular_subtitle]) } + + it "returns the subtitle with the most downloads" do + expect(subject.most_popular).to eq popular_subtitle + end +end diff --git a/spec/lib/addic7ed/services/check_compatibility_spec.rb b/spec/lib/addic7ed/services/check_compatibility_spec.rb new file mode 100644 index 0000000..bec4dba --- /dev/null +++ b/spec/lib/addic7ed/services/check_compatibility_spec.rb @@ -0,0 +1,88 @@ +require "spec_helper" + +describe Addic7ed::CheckCompatibility, "#call(subtitle, group)" do + let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION") } + let(:group) { subtitle.version } + + subject { described_class.call(subtitle, group) } + + context "when group is matching exactly" do + let(:group) { subtitle.version } + + it "returns true" do + expect(subject).to be true + end + end + + context "when group is one of the multiple groups defined for subtitle" do + let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION,FOV") } + let(:group) { "FOV" } + + it "returns true" do + expect(subject).to be true + end + end + + context "when group is the generally compatible 720p version of the subtitle" do + let(:group) { "LOL" } + + it "returns true" do + expect(subject).to be true + end + end + + context "when group is the generally compatible low-def version of the subtitle" do + let(:subtitle) { Addic7ed::Subtitle.new(version: "LOL") } + let(:group) { "DIMENSION" } + + it "returns true" do + expect(subject).to be true + end + end + + context "when group is different" do + let(:group) { "EVOLVE" } + + it "returns false" do + expect(subject).to be false + end + end + + context "when subtitle has a compatibility comment" do + let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Works with FOV") } + + context "when group is mentioned in the comment" do + let(:group) { "FOV" } + + it "returns true" do + expect(subject).to be true + end + end + + context "when group is not mentioned in the comment" do + let(:group) { "EVOLVE" } + + it "returns false" do + expect(subject).to be false + end + end + end + + context "when subtitle has an incompatibility comment" do + let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Doesn't work for FOV") } + let(:group) { "FOV" } + + it "returns false" do + expect(subject).to be false + end + end + + context "when subtitle has an ambiguous comment" do + let(:subtitle) { Addic7ed::Subtitle.new(version: "DIMENSION", comment: "Resync of FOV") } + let(:group) { "FOV" } + + it "returns false" do + expect(subject).to be false + end + end +end From 7188b64639f0645e5b78395a373c7f2b330367b8 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 11 Jul 2016 22:45:54 +0400 Subject: [PATCH 27/89] Rename Subtitle#via to Subtitle#source --- README.md | 3 +-- lib/addic7ed/models/subtitle.rb | 6 +++--- spec/lib/addic7ed/models/subtitle_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a9fc974..08d0c54 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,7 @@ Ruby command-line script to fetch subtitles on Addic7ed * [x] move compatibility logic to a `CheckCompatibility` service object * [x] create a `SubtitlesCollection` class to hold and filter subtitles * [x] move best subtitle logic to `SubtitlesCollection` -* [ ] rename `Subtitle#via` to `Subtitle#source` -* [ ] rename `Subtitle#version` to `Subtitle#group` +* [x] rename `Subtitle#via` to `Subtitle#source` * [x] refactor how `Episode` holds `Subtitle`s * [x] rename `ShowList` and make it a service object * [ ] write code documentation diff --git a/lib/addic7ed/models/subtitle.rb b/lib/addic7ed/models/subtitle.rb index fed2e55..a54188c 100644 --- a/lib/addic7ed/models/subtitle.rb +++ b/lib/addic7ed/models/subtitle.rb @@ -1,6 +1,6 @@ module Addic7ed class Subtitle - attr_reader :version, :language, :status, :via, :downloads, :comment + attr_reader :version, :language, :status, :source, :downloads, :comment attr_accessor :url def initialize(options = {}) @@ -8,7 +8,7 @@ def initialize(options = {}) @language = options[:language] @status = options[:status] @url = options[:url] - @via = options[:via] + @source = options[:source] @hi = options[:hi] @downloads = options[:downloads].to_i || 0 @comment = NormalizeComment.call(options[:comment]) @@ -16,7 +16,7 @@ def initialize(options = {}) def to_s str = "#{url}\t->\t#{version} (#{language}, #{status}) [#{downloads} downloads]" - str += " (via #{via})" if via + str += " (source #{source})" if source str end diff --git a/spec/lib/addic7ed/models/subtitle_spec.rb b/spec/lib/addic7ed/models/subtitle_spec.rb index 4a8bf43..393d941 100644 --- a/spec/lib/addic7ed/models/subtitle_spec.rb +++ b/spec/lib/addic7ed/models/subtitle_spec.rb @@ -24,12 +24,12 @@ language: "fr", status: "Completed", url: "http://some.fancy.url", - via: "http://addic7ed.com", + source: "http://addic7ed.com", downloads: "42" ) end let(:expected) do - "http://some.fancy.url\t->\tDIMENSION (fr, Completed) [42 downloads] (via http://addic7ed.com)" + "http://some.fancy.url\t->\tDIMENSION (fr, Completed) [42 downloads] (source http://addic7ed.com)" # rubocop:disable Metrics/LineLength end it "prints a human readable version" do From 928edd3f2224b5d55efadc482f339340f2847488 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Wed, 13 Jul 2016 05:57:51 +0400 Subject: [PATCH 28/89] Disable Brakeman on CodeClimate as it's Rails-only :confused: --- .codeclimate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 53e74d3..e1367c3 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -10,7 +10,7 @@ engines: rubocop: enabled: true brakeman: - enabled: true + enabled: false ratings: paths: - "**.rb" From 676938b3dded8dbae449df28ce39ea623b3f686d Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 16 Jul 2016 14:39:23 +0400 Subject: [PATCH 29/89] Add support for normalization of subtitles versions with multiple, dash-separated main compatible release groups --- lib/addic7ed/services/normalize_version.rb | 4 +++- spec/lib/addic7ed/services/normalize_version_spec.rb | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/addic7ed/services/normalize_version.rb b/lib/addic7ed/services/normalize_version.rb index 6fc1bbc..6bdb8d8 100644 --- a/lib/addic7ed/services/normalize_version.rb +++ b/lib/addic7ed/services/normalize_version.rb @@ -14,7 +14,9 @@ def call .upcase .gsub(/,[\d\. ]+MBS$/, "") .gsub(/(^VERSION *|720P|1080P|HDTV|PROPER|RERIP|INTERNAL|X\.?264)/, "") - .gsub(/[- \.]/, "") + .gsub(/[- \.\,]/, " ") + .strip + .gsub(/ +/, ",") end end end diff --git a/spec/lib/addic7ed/services/normalize_version_spec.rb b/spec/lib/addic7ed/services/normalize_version_spec.rb index 497f0a4..ce70e5c 100644 --- a/spec/lib/addic7ed/services/normalize_version_spec.rb +++ b/spec/lib/addic7ed/services/normalize_version_spec.rb @@ -66,7 +66,11 @@ def normalized_version(version) expect(normalized_version("Version 720P PROPER X264 HDTV DIMENSION")).to eq "DIMENSION" end - it "supports multiple concatenated versions" do + it "supports comma-separated multiple concatenated versions" do expect(normalized_version("-TLA, -FoV")).to eq "TLA,FOV" end + + it "supports dash-separated multiple concatenated versions" do + expect(normalized_version("KILLERS - AVS")).to eq "KILLERS,AVS" + end end From b5d4836e2da1e4dbafc67b9cac014b7f7fc71fb4 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Fri, 22 Jul 2016 10:00:21 +0400 Subject: [PATCH 30/89] Fix a typo --- lib/addic7ed/services/parse_subtitle.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/services/parse_subtitle.rb b/lib/addic7ed/services/parse_subtitle.rb index 3741781..73962d7 100644 --- a/lib/addic7ed/services/parse_subtitle.rb +++ b/lib/addic7ed/services/parse_subtitle.rb @@ -36,7 +36,7 @@ def extract_version def extract_language extract_field(".language") do |node| - node.content.gsub(/\A\W*/, ").gsub(/[^\w\)]*\z/, ") + node.content.gsub(/\A\W*/, "").gsub(/[^\w\)]*\z/, "") end end From ce4e1f3937990742cef6764b24048d16a3cac922 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Fri, 22 Jul 2016 10:04:11 +0400 Subject: [PATCH 31/89] Strip parsed subtitle comment --- lib/addic7ed/services/normalize_comment.rb | 2 +- spec/lib/addic7ed/services/normalize_comment_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/addic7ed/services/normalize_comment.rb b/lib/addic7ed/services/normalize_comment.rb index aef53d0..badb6a2 100644 --- a/lib/addic7ed/services/normalize_comment.rb +++ b/lib/addic7ed/services/normalize_comment.rb @@ -9,7 +9,7 @@ def initialize(comment) end def call - comment.downcase + comment.downcase.strip end end end diff --git a/spec/lib/addic7ed/services/normalize_comment_spec.rb b/spec/lib/addic7ed/services/normalize_comment_spec.rb index 55502a4..19c61d5 100644 --- a/spec/lib/addic7ed/services/normalize_comment_spec.rb +++ b/spec/lib/addic7ed/services/normalize_comment_spec.rb @@ -8,4 +8,8 @@ def normalized_comment(comment) it "downcases everything" do expect(normalized_comment("DiMENSiON")).to eq "dimension" end + + it "removes extra heading and trailing whitespaces" do + expect(normalized_comment(" \t AVS\t\r\n ")).to eq "avs" + end end From 55cbd8e4fed5aa8d231ed9cb9c56901fc236ad2e Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 23 Jul 2016 15:00:55 +0400 Subject: [PATCH 32/89] Add doc for models --- .gitignore | 4 + .yardopts | 1 + README.md | 14 ++-- addic7ed.gemspec | 2 + lib/addic7ed/models/episode.rb | 79 +++++++++++++++++- lib/addic7ed/models/search.rb | 51 ++++++++++++ lib/addic7ed/models/subtitle.rb | 84 ++++++++++++++++++- lib/addic7ed/models/subtitles_collection.rb | 42 ++++++++++ lib/addic7ed/models/video_file.rb | 89 +++++++++++++++++++++ 9 files changed, 355 insertions(+), 11 deletions(-) create mode 100644 .yardopts diff --git a/.gitignore b/.gitignore index 4d0a136..4fed060 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ pkg # Ignore built gems /*.gem + +# YARD doc stuff +/.yardoc +/doc/ diff --git a/.yardopts b/.yardopts new file mode 100644 index 0000000..5ccbf0e --- /dev/null +++ b/.yardopts @@ -0,0 +1 @@ +lib/addic7ed/models/**/*.rb diff --git a/README.md b/README.md index 08d0c54..f49e2be 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![Coverage Status](https://coveralls.io/repos/michaelbaudino/addic7ed-ruby/badge.svg?branch=master)](https://coveralls.io/r/michaelbaudino/addic7ed-ruby) [![Gem Version](https://badge.fury.io/rb/addic7ed.svg)](http://badge.fury.io/rb/addic7ed) [![security](https://hakiri.io/github/michaelbaudino/addic7ed-ruby/master.svg)](https://hakiri.io/github/michaelbaudino/addic7ed-ruby/master) +[![Inline docs](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby.svg?branch=api-refactor)](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby?branch=api-refactor) Ruby command-line script to fetch subtitles on Addic7ed @@ -20,18 +21,19 @@ Ruby command-line script to fetch subtitles on Addic7ed * [x] rename `Subtitle#via` to `Subtitle#source` * [x] refactor how `Episode` holds `Subtitle`s * [x] rename `ShowList` and make it a service object -* [ ] write code documentation +* [x] write code documentation +* [ ] Configure GitHub hook for [RubyDoc](http://www.rubydoc.info) +* [ ] move CLI to a separate gem (and use Thor or similar) +* [ ] update README to include only API stuff +* [x] add `bin/console` +* [ ] remove `bin/addic7ed` +* [ ] move all `to_s` and `to_inspect` methods to the CLI * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") * [x] use symbols rather than strings for languages * [x] add Rubocop * [x] move user agents and languages to a config file * [ ] add specs for parsing -* [ ] move CLI to a separate gem (and use Thor or similar) -* [ ] update README to include only API stuff -* [ ] remove `bin/addic7ed` -* [ ] move all `to_s` and `to_inspect` methods to the CLI -* [ ] add `bin/console` ### Is it working ? diff --git a/addic7ed.gemspec b/addic7ed.gemspec index da59dd3..c7d723f 100644 --- a/addic7ed.gemspec +++ b/addic7ed.gemspec @@ -17,6 +17,8 @@ Gem::Specification.new do |s| s.add_development_dependency("webmock") s.add_development_dependency("pry") s.add_development_dependency("rubocop") + s.add_development_dependency("inch") + s.add_development_dependency("yard") s.add_runtime_dependency("nokogiri", "~> 1.6.8") s.add_runtime_dependency("json", "~> 1.8.3") diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index 3db1ada..d1b69ec 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -2,9 +2,31 @@ require "open-uri" module Addic7ed + # Represents a TV sho episode. + # + # @attr showname [String] TV show name. + # @attr season [Numeric] Season number. + # @attr episode [Numeric] Episode number in the season. + class Episode attr_reader :showname, :season, :episode + # Creates a new instance of {Episode}. + # + # @param showname [String] TV show name, as extracted from the video file name + # (_e.g._ both +"Game.of.Thrones"+ and +"Game of Thrones"+ are valid) + # @param season [Numeric] Season number + # @param episode [Numeric] Episode number in the season + # + # @example + # Addic7ed::Episode.new("Game of Thrones", 6, 9) + # #=> #nil, :ar=>nil, :az=>nil, ..., :th=>nil, :tr=>nil, :vi=>nil} + # # > + def initialize(showname, season, episode) @showname = showname @season = season @@ -12,12 +34,61 @@ def initialize(showname, season, episode) @subtitles = languages_hash { |code, _| { code => nil } } end - def subtitles(lang) - @subtitles[lang] ||= Addic7ed::ParsePage.call(page_url(lang)) + # Returns a list of all available {Subtitle}s for this {Episode} in the given +language+. + # + # @param language [String] Language code we want returned {Subtitle}s to be in. + # + # @return [Array] List of {Subtitle}s available on Addic7ed for the given +language+. + # + # @example + # Addic7ed::Episode.new("Game.of.Thrones", 3, 9).subtitles(:fr) + # #=> [ + # # #, + # # #, + # # # + # # ] + + def subtitles(language) + @subtitles[language] ||= Addic7ed::ParsePage.call(page_url(language)) end - def page_url(lang) - localized_urls[lang] + # Returns the URL of the Addic7ed webpage listing subtitles for this {Episode} + # in the given +language+. + # + # @param language [String] Language code we want the webpage to list subtitles in. + # + # @return [String] Fully qualified URL of the webpage on Addic7ed. + # + # @example + # Addic7ed::Episode.new("Game of Thrones", 6, 9).page_url + # #=> "http://www.addic7ed.com/serie/Game_of_Thrones/6/9/8" + + def page_url(language) + localized_urls[language] end protected diff --git a/lib/addic7ed/models/search.rb b/lib/addic7ed/models/search.rb index 021eca9..a879d91 100644 --- a/lib/addic7ed/models/search.rb +++ b/lib/addic7ed/models/search.rb @@ -1,29 +1,80 @@ module Addic7ed + # Represents a subtitle search for a +video_filename+ in a +language+ with + # multiple search +criterias+. + # + # @attr video_filename [String] Video file name we're searching subtitles for. + # @attr language [String] ISO code of language (_e.g._ "en", "fr", "es"). + # @attr criterias [Hash] List of search criterias as a {Hash}. + class Search attr_reader :video_filename, :language, :criterias + # Creates a new instance of {Search}. + # + # Currently supported search criterias are: + # * +no_hi+: do not include hearing impaired subtitles (defaults to +false+) + # + # @param video_filename [String] Path to the video file for which we search subtitles + # (either relative or absolute path) + # @param language [String] ISO code of target subtitles language + # @param criterias [Hash] List of search criterias the subtitles must match + # (will be merged with default criterias as described above) + # + # @example + # Addic7ed::Search.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv", :fr) + # #=> #false}, + # # @language=:fr, + # # @video_filename="Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv" + # # > + def initialize(video_filename, language, criterias = {}) @video_filename = video_filename @language = language @criterias = default_criterias.merge(criterias) end + # Returns the {VideoFile} object representing the video file we're searching subtitles for. + # + # @return [VideoFile] the {VideoFile} associated with this {Search} + def video_file @video_file ||= VideoFile.new(video_filename) end + # Returns the {Episode} object we're searching subtitles for. + # + # @return [Episode] the {Episode} associated with this {Search} + def episode @episode ||= Episode.new(video_file.showname, video_file.season, video_file.episode) end + # Returns the list of all available subtitles for this search, + # regardless of completeness, compatibility or search +criterias+. + # + # @return [SubtitlesCollection] all subtitles for the searched episode + def all_subtitles @all_subtitles ||= SubtitlesCollection.new(episode.subtitles(language)) end + # Returns the list of subtitles completed and compatible with the episode we're searching + # subtitles for, regardless of search +criterias+. + # + # @return [SubtitlesCollection] subtitles completed and compatible with the searched episode + def matching_subtitles @matching_subtitles ||= all_subtitles.completed.compatible_with(video_file.group) end + # Returns the best subtitle for the episode we're searching subtitles for. + # + # The best subtitle means the more popular (_i.e._ most downloaded) subtitle among completed, + # compatible subtitles for the episode we're searching subtitles for. + # + # @return [SubtitlesCollection] subtitles completed and compatible with the searched episode + def best_subtitle @best_subtitle ||= matching_subtitles.most_popular end diff --git a/lib/addic7ed/models/subtitle.rb b/lib/addic7ed/models/subtitle.rb index a54188c..766b4cc 100644 --- a/lib/addic7ed/models/subtitle.rb +++ b/lib/addic7ed/models/subtitle.rb @@ -1,8 +1,65 @@ module Addic7ed + # Represents a subtitle on Addic7ed. + # + # This model contains as much information as possible for a subtitle + # as parsed on Addic7ed website. + # + # @attr version [String] Main compatible release group(s) + # (evenually as a comma-separated list). + # @attr language [String] Language the subtitle is written in + # (_e.g._: "French", "English"). + # @attr status [String] Translation/synchronization advancement status + # (_e.g._: "60%"" or "Completed"). + # @attr source [String] URL of website the subtitle was first published on. + # @attr downloads [Numeric] Number of times this subtitle has been downloaded. + # @attr comment [String] Comment manually added by the subtitle author/publisher + # (usually related to extra-compatibilities or resync source). + # @attr hi [Boolean] Hearing-impaired support. + # @attr url [String] Download URL of actual subtitle file (warning: Addic7ed servers + # won't serve a subtite file without a proper +Referer+ HTTP header which can be + # retrieved from +episode.page_url+). + class Subtitle - attr_reader :version, :language, :status, :source, :downloads, :comment + attr_reader :version, :language, :status, :source, :downloads, :comment, :hi attr_accessor :url + # Creates a new instance of {Subtitle} created using +options+, + # usually from data parsed from an Addic7ed page. + # + # The +options+ hash can have the following keys as symbols: + # * +version+: main compatible release group(s) as parsed on the Addic7ed website + # * +language+: language full name + # * +status+: full-text advancement status + # * +source+: URL of website the subtitle was originally published on + # * +downloads+: number of times the subtitle has been downloaded + # * +comment+: manually added comment from the author or publisher + # * +hi+: hearing-impaired support + # * +url+: download URL for the subtitle file + # + # @param options [Hash] subtitle information as a {Hash} + # + # @example + # Addic7ed::Subtitle.new( + # version: "Version KILLERS, 720p AVS, 0.00 MBs", + # language: "French", + # status: "Completed", + # source: "http://sous-titres.eu", + # downloads: 10335, + # comment: "works with 1080p.BATV", + # hi: false, + # url: "http://www.addic7ed.com/original/113643/4" + # ) + # #=> # + def initialize(options = {}) @version = NormalizeVersion.call(options[:version]) @language = options[:language] @@ -14,12 +71,37 @@ def initialize(options = {}) @comment = NormalizeComment.call(options[:comment]) end + # Returns a human-friendly readable string representing the {Subtitle} + # + # TODO: move to the CLI codebase + # + # @return [String] + # + # @example + # Addic7ed::Subtitle.new( + # version: "Version 720p AVS, 0.00 MBs", + # language: "French", + # status: "Completed", + # downloads: 42, + # url: "http://www.addic7ed.com/original/113643/4" + # ).to_s + # #=> "http://www.addic7ed.com/original/113643/4\t->\tAVS (French, Completed) [42 downloads]" + def to_s str = "#{url}\t->\t#{version} (#{language}, #{status}) [#{downloads} downloads]" str += " (source #{source})" if source str end + # Completeness status of the {Subtitle}. + # + # @return [Boolean] Returns +true+ if {Subtitle} is complete, + # +false+ otherwise (partially complete) + # + # @example + # Addic7ed::Subtitle.new(status: "50%").completed? #=> false + # Addic7ed::Subtitle.new(status: "Completed").completed? #=> true + def completed? status == "Completed" end diff --git a/lib/addic7ed/models/subtitles_collection.rb b/lib/addic7ed/models/subtitles_collection.rb index 5df361b..622f74b 100644 --- a/lib/addic7ed/models/subtitles_collection.rb +++ b/lib/addic7ed/models/subtitles_collection.rb @@ -1,13 +1,55 @@ module Addic7ed + # Represents a collection of {Subtitle} objects. + # + # This collection inherits from {Array} so it behaves exaclty like it, + # but it also provides some methods to filter, order and choose the best subtitle. class SubtitlesCollection < Array + # Returns only subtitles that are compatible with +group+. + # @param group [String] Release group we want the returned subtitles to be compatible with + # + # @return [SubtitleCollection] Copy of collection with subtitles compatible with +group+ only + # + # @example + # fov = Addic7ed::Subtitle.new(version: "FOV") + # lol = Addic7ed::Subtitle.new(version: "LOL") + # dimension = Addic7ed::Subtitle.new(version: "DIMENSION") + # collection = Addic7ed::SubtitlesCollection.new([fov, lol, dimension]) + # + # collection.compatible_with("DIMENSION") + # #=> [#, #] + def compatible_with(group) select { |subtitle| CheckCompatibility.call(subtitle, group) } end + # Returns only completed subtitles. + # + # @return [SubtitleCollection] Copy of collection with completed subtitles only + # + # @example + # complete = Addic7ed::Subtitle.new(status: "Completed") + # wip = Addic7ed::Subtitle.new(status: "50%") + # collection = Addic7ed::SubtitlesCollection.new([complete, wip]) + # + # collection.completed + # #=> [#] + def completed select(&:completed?) end + # Returns the most downloaded subtitle. + # + # @return [Subtitle] Subtitle of the collection with the more downloads + # + # @example + # popular = Addic7ed::Subtitle.new(downloads: 1000) + # unpopular = Addic7ed::Subtitle.new(downloads: 3) + # collection = Addic7ed::SubtitlesCollection.new([popular, unpopular]) + # + # collection.most_popular + # #=> # + def most_popular sort_by(&:downloads).last end diff --git a/lib/addic7ed/models/video_file.rb b/lib/addic7ed/models/video_file.rb index b87eeab..f9ed2f4 100644 --- a/lib/addic7ed/models/video_file.rb +++ b/lib/addic7ed/models/video_file.rb @@ -1,47 +1,136 @@ module Addic7ed + # Represents the video file you're looking a subtitle for. + # + # This class will extract from the video file name the show name, season, episode, group, etc. + # + # It expects the file to be named according to The Scene rules, + # but actually supports a wide variety of common naming schemes. class VideoFile + # @!visibility private TVSHOW_REGEX = /\A(?.*\w)[\[\. ]+S?(?\d{1,2})[-\. ]?[EX]?(?\d{2})([-\. ]?[EX]?\d{2})*[\]\. ]+(?.*)-(?\w*)\[?(?\w*)\]?(\.\w{3})?\z/i # rubocop:disable Metrics/LineLength + # @!visibility private attr_reader :filename, :regexp_matches + # Returns a new instance of {VideoFile}. + # It expects a video file name, either with or without path and + # either absolute or relative. + # + # @return [String] Distribution group name + # + # @example + # Addic7ed::VideoFile.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # Addic7ed::VideoFile.new("/home/mike/Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # Addic7ed::VideoFile.new("../Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + def initialize(filename) @filename = filename @regexp_matches = TVSHOW_REGEX.match(basename) raise InvalidFilename if regexp_matches.nil? end + # Returns the TV show name extracted from the file name. + # + # @return [String] TV show name + # + # @example + # video_file = Addic7ed::VideoFile.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # video_file.showname #=> "Game.of.Thrones" + def showname @showname ||= regexp_matches[:showname] end + # Returns the TV show season number extracted from the file name. + # + # @return [Integer] TV show season number + # + # @example + # video_file = Addic7ed::VideoFile.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # video_file.season #=> 6 + def season @season ||= regexp_matches[:season].to_i end + # Returns the TV show episode number extracted from the file name. + # + # @return [Integer] TV show episode number + # + # @example + # video_file = Addic7ed::VideoFile.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # video_file.episode #=> 9 + def episode @episode ||= regexp_matches[:episode].to_i end + # Returns the upcased release tags extracted from the file name + # (like +HDTV+, +720P+, +XviD+, +PROPER+, ...). + # + # @return [Array] Release video, audio or packaging tags + # + # @example + # video_file = Addic7ed::VideoFile.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # video_file.tags #=> ["720P", "HDTV", "X264"] + def tags @tags ||= regexp_matches[:tags].upcase.split(/[\. ]/) end + # Returns the upcased release group extracted from the file name. + # + # @return [String] Release group name + # + # @example + # video_file = Addic7ed::VideoFile.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # video_file.group #=> "AVS" + def group @group ||= regexp_matches[:group].upcase end + # Returns the upcased distribution group extracted from the file name. + # + # @return [String] Distribution group name + # + # @example + # video_file = Addic7ed::VideoFile.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS[eztv].mkv") + # video_file.distribution #=> "EZTV" + def distribution @distribution ||= regexp_matches[:distribution].upcase end + # Returns the base file name (without path). + # + # @return [String] Base file name + # + # @example + # video_file = Addic7ed::VideoFile.new("../Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # video_file.basename #=> "Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv" + def basename @basename ||= File.basename(filename) end + # Returns the video file name as passed to {#initialize}. + # + # @return [String] File name + # + # @example + # video_file = Addic7ed::VideoFile.new("../Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") + # video_file.to_s #=> "../Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv" + def to_s filename end + # Returns a multiline, human-readable breakdown of the file name + # including all detected attributes (show name, season, episode, tags, ...). + # + # @return [String] a human-readable breakdown of the file name + def inspect "Guesses for #{filename}:\n" \ " show: #{showname}\n" \ From 8214fb8245e11d1bd752ceff03b0a2dd8cf5af62 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 23 Jul 2016 15:14:34 +0400 Subject: [PATCH 33/89] Change inch badge for v3 branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f49e2be..1d63b68 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Coverage Status](https://coveralls.io/repos/michaelbaudino/addic7ed-ruby/badge.svg?branch=master)](https://coveralls.io/r/michaelbaudino/addic7ed-ruby) [![Gem Version](https://badge.fury.io/rb/addic7ed.svg)](http://badge.fury.io/rb/addic7ed) [![security](https://hakiri.io/github/michaelbaudino/addic7ed-ruby/master.svg)](https://hakiri.io/github/michaelbaudino/addic7ed-ruby/master) -[![Inline docs](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby.svg?branch=api-refactor)](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby?branch=api-refactor) +[![Inline docs](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby.svg?branch=v3)](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby?branch=v3) Ruby command-line script to fetch subtitles on Addic7ed From 165f9a6bc8783697389c2d0affa7270975ce5660 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 23 Jul 2016 15:17:04 +0400 Subject: [PATCH 34/89] Check GitHub hook for RubyDoc in TODO list --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d63b68..147c9f0 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Ruby command-line script to fetch subtitles on Addic7ed * [x] refactor how `Episode` holds `Subtitle`s * [x] rename `ShowList` and make it a service object * [x] write code documentation -* [ ] Configure GitHub hook for [RubyDoc](http://www.rubydoc.info) +* [x] Configure GitHub hook for [RubyDoc](http://www.rubydoc.info) * [ ] move CLI to a separate gem (and use Thor or similar) * [ ] update README to include only API stuff * [x] add `bin/console` From 7034d0dadca0e741307e80e9e1cd93a9dfc04025 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 23 Jul 2016 15:24:10 +0400 Subject: [PATCH 35/89] Change Episode protected methods to be private --- lib/addic7ed/models/episode.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index d1b69ec..a540aca 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -91,7 +91,7 @@ def page_url(language) localized_urls[language] end - protected + private def localized_urls @localized_urls ||= languages_hash { |code, lang| { code => localized_url(lang[:id]) } } From 01b521b604f0245c9e6e2faa7177702272bd00e1 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 23 Jul 2016 15:30:00 +0400 Subject: [PATCH 36/89] Add missing doc for VideoFile initialize parameter --- lib/addic7ed/models/video_file.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/addic7ed/models/video_file.rb b/lib/addic7ed/models/video_file.rb index f9ed2f4..9a0ae6a 100644 --- a/lib/addic7ed/models/video_file.rb +++ b/lib/addic7ed/models/video_file.rb @@ -16,6 +16,9 @@ class VideoFile # It expects a video file name, either with or without path and # either absolute or relative. # + # @param filename [String] File name of the video file on disk + # (either relative or absolute). + # # @return [String] Distribution group name # # @example From 9e4e064a7dd322074e09a0cfdbaa33a590ed2dc2 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 23 Jul 2016 16:00:09 +0400 Subject: [PATCH 37/89] Change Travis email notifications setting --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4309da5..e4f1b1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,6 @@ rvm: - jruby before_install: - gem update bundler +notifications: + email: change + From 87587db23bca93883eab7a5f68857ce711e5031a Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 23 Jul 2016 16:00:56 +0400 Subject: [PATCH 38/89] Fix Travis email notifications setting --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e4f1b1b..2752030 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,5 +10,6 @@ rvm: before_install: - gem update bundler notifications: - email: change - + email: + on_success: change + on_failure: change From 3fef232392b7d14f22bd88d5ac2e3404d63686c2 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 7 Aug 2016 20:59:33 +0400 Subject: [PATCH 39/89] Bump version to 3.0.0-beta.1 :tada: --- lib/addic7ed/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index 6aa5dc3..4a97458 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,3 +1,3 @@ module Addic7ed - VERSION = "2.1.0".freeze + VERSION = "3.0.0-beta.1".freeze end From fb6fd05a2ef9e9d0043ebd7cf1436c3cd468e5c2 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 8 Aug 2016 08:52:37 +0400 Subject: [PATCH 40/89] Fix internal requiring --- addic7ed.gemspec | 9 ++++----- lib/addic7ed.rb | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/addic7ed.gemspec b/addic7ed.gemspec index c7d723f..3930065 100644 --- a/addic7ed.gemspec +++ b/addic7ed.gemspec @@ -23,10 +23,9 @@ Gem::Specification.new do |s| s.add_runtime_dependency("nokogiri", "~> 1.6.8") s.add_runtime_dependency("json", "~> 1.8.3") - s.executables = ["addic7ed"] - s.files = `git ls-files -- lib/* LICENSE.md`.split("\n") - s.test_files = `git ls-files -- spec/*`.split("\n") + s.executables = ["addic7ed"] + s.files = `git ls-files -- lib/* LICENSE.md`.split("\n") + s.test_files = `git ls-files -- spec/*`.split("\n") s.require_paths = ["lib"] - s.has_rdoc = false - s.license = "MIT" + s.license = "MIT" end diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index 618955b..98a1ba0 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -1,5 +1,5 @@ Dir[ - File.expand_path("lib/addic7ed/*.rb"), - File.expand_path("lib/addic7ed/services/**/*.rb"), - File.expand_path("lib/addic7ed/models/**/*.rb") + File.join(File.dirname(__FILE__), "addic7ed/*.rb"), + File.join(File.dirname(__FILE__), "addic7ed/services/**/*.rb"), + File.join(File.dirname(__FILE__), "addic7ed/models/**/*.rb") ].each { |file| require file } From dab51b63efa1061b2a4676e17c7efac0ba557924 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 8 Aug 2016 08:53:11 +0400 Subject: [PATCH 41/89] Bump version to 3.0.0-beta.2 --- lib/addic7ed/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index 4a97458..b0ef83d 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,3 +1,3 @@ module Addic7ed - VERSION = "3.0.0-beta.1".freeze + VERSION = "3.0.0-beta.2".freeze end From 96ffed550c0268b065e5b4991bbc4b3014820306 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 8 Aug 2016 09:00:19 +0400 Subject: [PATCH 42/89] Fix config file requiring --- lib/addic7ed/common.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/addic7ed/common.rb b/lib/addic7ed/common.rb index 66c4ec4..5b7bdf7 100644 --- a/lib/addic7ed/common.rb +++ b/lib/addic7ed/common.rb @@ -1,7 +1,8 @@ require "json" module Addic7ed - CONFIG = JSON.load(File.open("lib/addic7ed/config.json"), nil, symbolize_names: true).freeze + CONFIG_FILE = File.join(File.dirname(__FILE__), "config.json").freeze + CONFIG = JSON.load(File.open(CONFIG_FILE), nil, symbolize_names: true).freeze LANGUAGES = CONFIG[:languages].freeze USER_AGENTS = CONFIG[:user_agents].freeze SHOWS_URL = CONFIG[:urls][:shows].freeze From 06e65d2eabc0480fb63c40e467f55e7cff2a5634 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 8 Aug 2016 09:00:35 +0400 Subject: [PATCH 43/89] Bump version to 3.0.0-beta.3 --- lib/addic7ed/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index b0ef83d..2a596c5 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,3 +1,3 @@ module Addic7ed - VERSION = "3.0.0-beta.2".freeze + VERSION = "3.0.0-beta.3".freeze end From 24f0dbddf1af5c8bdc998a036313fcb077c5f43f Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Thu, 11 Aug 2016 09:48:48 +0400 Subject: [PATCH 44/89] Remove legacy executable script --- README.md | 2 +- addic7ed.gemspec | 4 +- bin/addic7ed | 139 ----------------------------------------------- 3 files changed, 2 insertions(+), 143 deletions(-) delete mode 100755 bin/addic7ed diff --git a/README.md b/README.md index 147c9f0..9c4e4c6 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Ruby command-line script to fetch subtitles on Addic7ed * [ ] move CLI to a separate gem (and use Thor or similar) * [ ] update README to include only API stuff * [x] add `bin/console` -* [ ] remove `bin/addic7ed` +* [x] remove `bin/addic7ed` * [ ] move all `to_s` and `to_inspect` methods to the CLI * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) * [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") diff --git a/addic7ed.gemspec b/addic7ed.gemspec index 3930065..a92a6c5 100644 --- a/addic7ed.gemspec +++ b/addic7ed.gemspec @@ -23,9 +23,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency("nokogiri", "~> 1.6.8") s.add_runtime_dependency("json", "~> 1.8.3") - s.executables = ["addic7ed"] - s.files = `git ls-files -- lib/* LICENSE.md`.split("\n") - s.test_files = `git ls-files -- spec/*`.split("\n") + s.files = `git ls-files -z lib LICENSE.md`.split("\x0") s.require_paths = ["lib"] s.license = "MIT" end diff --git a/bin/addic7ed b/bin/addic7ed deleted file mode 100755 index 3e6dab4..0000000 --- a/bin/addic7ed +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env ruby - -# rubocop:disable all -def require_dependencies - require 'optparse' - require 'nokogiri' - require 'addic7ed' -end - -begin - require_dependencies # People don't all use rubygems, you know... -rescue LoadError - require 'rubygems' # But most do :-) - require_dependencies -end - -options = {} -OptionParser.new do |opts| - opts.banner = "Usage: addic7ed [options] [, , ...]" - - opts.on("-l [LANGUAGE]", "--language [LANGUAGE]", "Language code to look subtitles for (default: French)") do |l| - options[:language] = l - end - - opts.on("--no-hi", "Only download subtitles without Hearing Impaired lines") do |hi| - options[:no_hi] = !hi - end - - opts.on("-a", "--all-subtitles", "Display all available subtitles") do |a| - options[:all] = a - end - - opts.on("-n", "--do-not-download", "Do not download the subtitle") do |n| - options[:nodownload] = n - end - - opts.on("-f", "--force", "Overwrite existing subtitle") do |f| - options[:force] = f - end - - opts.on("-u", "--untagged", "Do not include language code in subtitle filename") do |u| - options[:untagged] = u - end - - opts.on("-v", "--[no-]verbose", "Run verbosely") do |v| - options[:verbose] = v - end - - opts.on("-q", "--quiet", "Run without output (cron-mode)") do |q| - options[:quiet] = q - end - - opts.on("-d", "--debug", "Debug mode [do not use]") do |d| - options[:debug] = d - end - - opts.on_tail("-h", "--help", "Show this message") do - puts opts - exit - end - - opts.on_tail("-L", "--list-languages", "List all available languages") do - puts "All available languages (with their corresponding ISO code):" - Addic7ed::LANGUAGES.each do |lang, infos| - puts "#{lang}:\t#{infos[:name]}" - end - exit - end - - opts.on_tail("-V", "--version", "Show version number") do - puts "This is addic7ed-ruby version #{Addic7ed::VERSION} by Michael Baudino (https://github.com/michaelbaudino)" - puts "Licensed under the terms of the MIT License" - exit - end -end.parse! - -options[:filenames] = ARGV -options[:language] ||= 'fr' - -# Main loop over mandatory arguments (e.g. filenames) - -options[:filenames].each do |filename| - unless File.file? filename or options[:debug] - puts "Warning: #{filename} does not exist or is not a regular file. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - end - - begin - ep = Addic7ed::Episode.new(filename, options[:untagged]) - puts "Searching subtitles for #{ep.video_file.basename}" if options[:verbose] - if File.file?(filename.gsub(/\.\w{3}$/, '.srt')) and not options[:force] - puts "A subtitle already exists (#{filename.gsub(/\.\w{3}$/, '.srt')}). Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - end - puts ep.video_file.inspect.gsub(/^/, ' ') if options[:verbose] - ep.subtitles(options[:language]) - if options[:all] or options[:verbose] - puts 'Available subtitles:'.gsub(/^/, options[:verbose] ? ' ' : '') - ep.subtitles(options[:language]).each do |sub| - puts "#{sub}".gsub(/^/, options[:verbose] ? ' ' : ' ') - end - next if options[:all] - end - ep.best_subtitle(options[:language], options[:no_hi]) - if options[:verbose] - puts ' Best subtitle:' - puts " #{ep.best_subtitle(options[:language])}" - end - unless options[:nodownload] - ep.download_best_subtitle!(options[:language], options[:no_hi]) - puts "New subtitle downloaded for #{filename}.\nEnjoy your show :-)".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - end - rescue Addic7ed::InvalidFilename - puts "#{filename} does not seem to be a valid TV show filename. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - rescue Addic7ed::ShowNotFound - puts "Show not found on Addic7ed : #{ep.video_file.filename}. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - rescue Addic7ed::EpisodeNotFound - puts "Episode not found on Addic7ed : #{ep.video_file.filename}. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - rescue Addic7ed::LanguageNotSupported - puts "Addic7ed does not support language '#{options[:language]}'. Exiting.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - break - rescue Addic7ed::ParsingError - puts "HTML parsing failed. Either you've found a bug (please submit an issue) or Addic7ed website has been updated and cannot be crawled anymore (in this case, please wait for an update or submit a pull request). Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - rescue Addic7ed::NoSubtitleFound - puts "No (acceptable) subtitle has been found on Addic7ed for #{filename}. Maybe try again later.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - rescue Addic7ed::DailyLimitExceeded => e - puts "#{e.message}. Exiting.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - break - rescue Addic7ed::DownloadError => e - puts "The subtitle could not be saved: #{e.message}. Skipping.".gsub(/^/, options[:verbose] ? ' ' : '') unless options[:quiet] - next - end -end -# rubocop:enable all From 737176a48b32eb3b4399677486457c8d71b7f4d0 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Thu, 11 Aug 2016 09:49:45 +0400 Subject: [PATCH 45/89] Bump version to 3.0.0-beta.4 --- lib/addic7ed/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index 2a596c5..1ab0e1a 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,3 +1,3 @@ module Addic7ed - VERSION = "3.0.0-beta.3".freeze + VERSION = "3.0.0-beta.4".freeze end From ea4869635cf05a959c33c3ac0247a21239277f02 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Thu, 1 Dec 2016 10:27:52 +0400 Subject: [PATCH 46/89] Fix a doc typo --- lib/addic7ed/models/episode.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index a540aca..6cd7f81 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -2,7 +2,7 @@ require "open-uri" module Addic7ed - # Represents a TV sho episode. + # Represents a TV show episode. # # @attr showname [String] TV show name. # @attr season [Numeric] Season number. From 08db02d793c9cded91d149d23d9e159f90c4244b Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 4 Feb 2017 16:28:33 +0400 Subject: [PATCH 47/89] Minor README changes --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0beb9fd..548f4d2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ [![Inline docs](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby.svg?branch=v3)](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby?branch=v3) -Ruby command-line script to fetch subtitles on Addic7ed +Fetch TV shows subtitles on Addic7ed using Ruby. ### Refactoring TODO list @@ -44,10 +44,11 @@ Ruby command-line script to fetch subtitles on Addic7ed * [x] add Rubocop * [x] move user agents and languages to a config file * [ ] add specs for parsing +* [ ] Create a `Dockerfile` for the CLI ### Is it working ? -Until next time Addic7ed break their HTML/CSS structure, yes :smile: +As long as Addic7ed HTML/CSS structure remains the same: yes :smile: ### How to use it ? From e560f6fc9ea7ffcfa5886ef7a58480dafaf6db14 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 4 Feb 2017 16:45:57 +0400 Subject: [PATCH 48/89] Update Travis setup --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2752030..d89965e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,12 @@ language: ruby cache: bundler +dist: trusty rvm: - ruby-2.0 - ruby-2.1 - ruby-2.2 - - ruby-2.3.0 + - ruby-2.3 + - ruby-2.4 - rbx - jruby before_install: From 4a4a1011482b16209dd578e8b4706e8485a2ca2e Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 4 Feb 2017 17:00:05 +0400 Subject: [PATCH 49/89] Update RubyGems before Travis builds --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d89965e..ad02e90 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ rvm: - rbx - jruby before_install: + - gem update --system - gem update bundler notifications: email: From 46035a27f499ae7f5b96a3ba7da4012194173f31 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 4 Feb 2017 17:01:53 +0400 Subject: [PATCH 50/89] Specify Rubinius version in Travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ad02e90..552de00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ rvm: - ruby-2.2 - ruby-2.3 - ruby-2.4 - - rbx + - rbx-2.2.7 - jruby before_install: - gem update --system From 03b099dc319af7c41e6b291e398827ab45c1193f Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 4 Feb 2017 17:15:24 +0400 Subject: [PATCH 51/89] Update Rubinius version on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 552de00..9c51777 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ rvm: - ruby-2.2 - ruby-2.3 - ruby-2.4 - - rbx-2.2.7 + - rbx-3.70 - jruby before_install: - gem update --system From 6d4276e96b0666f2bfbba761c5e02de539e8ac63 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 4 Feb 2017 17:58:02 +0400 Subject: [PATCH 52/89] Fix README badges --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 548f4d2..40db3f2 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,13 @@ # addic7ed-ruby -[![Build Status](https://api.travis-ci.org/michaelbaudino/addic7ed-ruby.svg?branch=master)](https://travis-ci.org/michaelbaudino/addic7ed-ruby) +[![Build Status](https://api.travis-ci.org/michaelbaudino/addic7ed-ruby.svg?branch=full-rewrite)](https://travis-ci.org/michaelbaudino/addic7ed-ruby) [![Dependency Status](https://gemnasium.com/michaelbaudino/addic7ed-ruby.svg?travis)](https://gemnasium.com/michaelbaudino/addic7ed-ruby) [![Code Climate](https://codeclimate.com/github/michaelbaudino/addic7ed-ruby.svg)](https://codeclimate.com/github/michaelbaudino/addic7ed-ruby) [![Coverage Status](https://coveralls.io/repos/michaelbaudino/addic7ed-ruby/badge.svg?branch=master)](https://coveralls.io/r/michaelbaudino/addic7ed-ruby) [![Gem Version](https://badge.fury.io/rb/addic7ed.svg)](http://badge.fury.io/rb/addic7ed) [![security](https://hakiri.io/github/michaelbaudino/addic7ed-ruby/master.svg)](https://hakiri.io/github/michaelbaudino/addic7ed-ruby/master) -[![Inline docs](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby.svg?branch=v3)](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby?branch=v3) - +[![Inline docs](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby.svg?branch=full-rewrite)](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby?branch=full-rewrite) Fetch TV shows subtitles on Addic7ed using Ruby. From 7b461d18176187968083429aa3742d13058b0855 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 13:22:38 +0200 Subject: [PATCH 53/89] Update Rubocop and comply to new rules --- .gitignore | 1 + .rspec | 2 + .rubocop.yml | 1160 +---------------- Gemfile | 6 +- Rakefile | 2 + addic7ed.gemspec | 2 + bin/console | 1 + lib/addic7ed.rb | 2 + lib/addic7ed/common.rb | 4 +- lib/addic7ed/errors.rb | 2 + lib/addic7ed/models/episode.rb | 4 +- lib/addic7ed/models/search.rb | 4 +- lib/addic7ed/models/subtitle.rb | 2 + lib/addic7ed/models/subtitles_collection.rb | 4 +- lib/addic7ed/models/video_file.rb | 2 + lib/addic7ed/service.rb | 2 + lib/addic7ed/services/check_compatibility.rb | 12 +- lib/addic7ed/services/download_subtitle.rb | 6 +- lib/addic7ed/services/get_shows_list.rb | 4 +- lib/addic7ed/services/normalize_comment.rb | 2 + lib/addic7ed/services/normalize_version.rb | 2 + lib/addic7ed/services/parse_page.rb | 4 +- lib/addic7ed/services/parse_subtitle.rb | 8 +- lib/addic7ed/services/url_encode_show_name.rb | 4 +- lib/addic7ed/version.rb | 4 +- spec/lib/addic7ed/common_spec.rb | 2 + spec/lib/addic7ed/models/episode_spec.rb | 4 +- spec/lib/addic7ed/models/search_spec.rb | 2 + spec/lib/addic7ed/models/subtitle_spec.rb | 2 + .../models/subtitles_collection_spec.rb | 2 + spec/lib/addic7ed/models/video_file_spec.rb | 22 +- .../services/check_compatibility_spec.rb | 2 + .../services/download_subtitle_spec.rb | 2 + .../addic7ed/services/get_shows_list_spec.rb | 2 + .../services/normalize_comment_spec.rb | 2 + .../services/normalize_version_spec.rb | 2 + spec/lib/addic7ed/services/parse_page_spec.rb | 2 + .../addic7ed/services/parse_subtitle_spec.rb | 2 + .../services/url_encode_show_name_spec.rb | 2 + spec/spec_helper.rb | 2 + 40 files changed, 116 insertions(+), 1182 deletions(-) create mode 100644 .rspec diff --git a/.gitignore b/.gitignore index 4fed060..6f087a8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ pkg # Ignore Bundler config /.bundle +/vendor/bundle/ # Ignore built gems /*.gem diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml index 357b564..80cbe6c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,1162 +1,20 @@ AllCops: - DisabledByDefault: true - DisplayCopNames: true - DisplayStyleGuide: true - ExtraDetails: true - -#################### Lint ################################ - -Lint/AmbiguousOperator: - Description: >- - Checks for ambiguous operators in the first argument of a - method invocation without parentheses. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' - Enabled: true - -Lint/AmbiguousRegexpLiteral: - Description: >- - Checks for ambiguous regexp literals in the first argument of - a method invocation without parenthesis. - Enabled: true - -Lint/AssignmentInCondition: - Description: "Don't use assignment in conditions." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' - Enabled: true - -Lint/BlockAlignment: - Description: 'Align block ends correctly.' - Enabled: true - -Lint/CircularArgumentReference: - Description: "Don't refer to the keyword argument in the default value." - Enabled: true - -Lint/ConditionPosition: - Description: >- - Checks for condition placed in a confusing position relative to - the keyword. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' - Enabled: true - -Lint/Debugger: - Description: 'Check for debugger calls.' - Enabled: true - -Lint/DefEndAlignment: - Description: 'Align ends corresponding to defs correctly.' - Enabled: true - -Lint/DeprecatedClassMethods: - Description: 'Check for deprecated class method calls.' - Enabled: true - -Lint/DuplicateMethods: - Description: 'Check for duplicate methods calls.' - Enabled: true - -Lint/EachWithObjectArgument: - Description: 'Check for immutable argument given to each_with_object.' - Enabled: true - -Lint/ElseLayout: - Description: 'Check for odd code arrangement in an else block.' - Enabled: true - -Lint/EmptyEnsure: - Description: 'Checks for empty ensure block.' - Enabled: true - -Lint/EmptyInterpolation: - Description: 'Checks for empty string interpolation.' - Enabled: true - -Lint/EndAlignment: - Description: 'Align ends correctly.' - Enabled: true - -Lint/EndInMethod: - Description: 'END blocks should not be placed inside method definitions.' - Enabled: true - -Lint/EnsureReturn: - Description: 'Do not use return in an ensure block.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure' - Enabled: true - -Lint/Eval: - Description: 'The use of eval represents a serious security risk.' - Enabled: true - -Lint/FormatParameterMismatch: - Description: 'The number of parameters to format/sprint must match the fields.' - Enabled: true - -Lint/HandleExceptions: - Description: "Don't suppress exception." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' - Enabled: true - -Lint/InvalidCharacterLiteral: - Description: >- - Checks for invalid character literals with a non-escaped - whitespace character. - Enabled: true - -Lint/LiteralInCondition: - Description: 'Checks of literals used in conditions.' - Enabled: true - -Lint/LiteralInInterpolation: - Description: 'Checks for literals used in interpolation.' - Enabled: true - -Lint/Loop: - Description: >- - Use Kernel#loop with break rather than begin/end/until or - begin/end/while for post-loop tests. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' - Enabled: true - -Lint/NestedMethodDefinition: - Description: 'Do not use nested method definitions.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' - Enabled: true - -Lint/NonLocalExitFromIterator: - Description: 'Do not use return in iterator to cause non-local exit.' - Enabled: true - -Lint/ParenthesesAsGroupedExpression: - Description: >- - Checks for method calls with a space before the opening - parenthesis. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' - Enabled: true - -Lint/RequireParentheses: - Description: >- - Use parentheses in the method call to avoid confusion - about precedence. - Enabled: true - -Lint/RescueException: - Description: 'Avoid rescuing the Exception class.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' - Enabled: true - -Lint/ShadowingOuterLocalVariable: - Description: >- - Do not use the same name as outer local variable - for block arguments or block local variables. - Enabled: true - -Lint/StringConversionInInterpolation: - Description: 'Checks for Object#to_s usage in string interpolation.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s' - Enabled: true - -Lint/UnderscorePrefixedVariableName: - Description: 'Do not use prefix `_` for a variable that is used.' - Enabled: true - -Lint/UnneededDisable: - Description: >- - Checks for rubocop:disable comments that can be removed. - Note: this cop is not disabled when disabling all cops. - It must be explicitly disabled. - Enabled: true - -Lint/UnusedBlockArgument: - Description: 'Checks for unused block arguments.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' - Enabled: true - -Lint/UnusedMethodArgument: - Description: 'Checks for unused method arguments.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' - Enabled: true - -Lint/UnreachableCode: - Description: 'Unreachable code.' - Enabled: true - -Lint/UselessAccessModifier: - Description: 'Checks for useless access modifiers.' - Enabled: true - -Lint/UselessAssignment: - Description: 'Checks for useless assignment to a local variable.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' - Enabled: true - -Lint/UselessComparison: - Description: 'Checks for comparison of something with itself.' - Enabled: true - -Lint/UselessElseWithoutRescue: - Description: 'Checks for useless `else` in `begin..end` without `rescue`.' - Enabled: true - -Lint/UselessSetterCall: - Description: 'Checks for useless setter call to a local variable.' - Enabled: true - -Lint/Void: - Description: 'Possible use of operator/literal/variable in void context.' - Enabled: true - -###################### Metrics #################################### - -Metrics/AbcSize: - Description: >- - A calculated magnitude based on number of assignments, - branches, and conditions. - Reference: 'http://c2.com/cgi/wiki?AbcMetric' - Enabled: true - Max: 20 - -Metrics/BlockNesting: - Description: 'Avoid excessive block nesting' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' - Enabled: true - Max: 4 - -Metrics/ClassLength: - Description: 'Avoid classes longer than 250 lines of code.' - Enabled: true - Max: 250 - -Metrics/CyclomaticComplexity: - Description: >- - A complexity metric that is strongly correlated to the number - of test cases needed to validate a method. - Enabled: true - -Metrics/LineLength: - Description: 'Limit lines to 80 characters.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' - Max: 100 - Enabled: true - -Metrics/MethodLength: - Description: 'Avoid methods longer than 30 lines of code.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' - Enabled: true - Max: 30 - -Metrics/ModuleLength: - Description: 'Avoid modules longer than 250 lines of code.' - Enabled: true - Max: 250 - -Metrics/ParameterLists: - Description: 'Avoid parameter lists longer than three or four parameters.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' - Enabled: true - -Metrics/PerceivedComplexity: - Description: >- - A complexity metric geared towards measuring complexity for a - human reader. - Enabled: true - -##################### Performance ############################# - -Performance/Count: - Description: >- - Use `count` instead of `select...size`, `reject...size`, - `select...count`, `reject...count`, `select...length`, - and `reject...length`. - Enabled: true - -Performance/Detect: - Description: >- - Use `detect` instead of `select.first`, `find_all.first`, - `select.last`, and `find_all.last`. - Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' - Enabled: true - -Performance/FlatMap: - Description: >- - Use `Enumerable#flat_map` - instead of `Enumerable#map...Array#flatten(1)` - or `Enumberable#collect..Array#flatten(1)` - Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code' - Enabled: true - EnabledForFlattenWithoutParams: false - # If enabled, this cop will warn about usages of - # `flatten` being called without any parameters. - # This can be dangerous since `flat_map` will only flatten 1 level, and - # `flatten` without any parameters can flatten multiple levels. - -Performance/ReverseEach: - Description: 'Use `reverse_each` instead of `reverse.each`.' - Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' - Enabled: true - -Performance/Sample: - Description: >- - Use `sample` instead of `shuffle.first`, - `shuffle.last`, and `shuffle[Fixnum]`. - Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' - Enabled: true - -Performance/Size: - Description: >- - Use `size` instead of `count` for counting - the number of elements in `Array` and `Hash`. - Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code' - Enabled: true - -Performance/StringReplacement: - Description: >- - Use `tr` instead of `gsub` when you are replacing the same - number of characters. Use `delete` instead of `gsub` when - you are deleting characters. - Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code' - Enabled: true - -##################### Rails ################################## - -Rails/ActionFilter: - Description: 'Enforces consistent use of action filter methods.' - Enabled: true - -Rails/Date: - Description: >- - Checks the correct usage of date aware methods, - such as Date.today, Date.current etc. - Enabled: true - -Rails/Delegate: - Description: 'Prefer delegate method for delegations.' - Enabled: true - -Rails/FindBy: - Description: 'Prefer find_by over where.first.' - Enabled: true - -Rails/FindEach: - Description: 'Prefer all.find_each over all.find.' - Enabled: true - -Rails/HasAndBelongsToMany: - Description: 'Prefer has_many :through to has_and_belongs_to_many.' - Enabled: true - -Rails/Output: - Description: 'Checks for calls to puts, print, etc.' - Enabled: true - -Rails/ReadWriteAttribute: - Description: >- - Checks for read_attribute(:attr) and - write_attribute(:attr, val). - Enabled: true - -Rails/ScopeArgs: - Description: 'Checks the arguments of ActiveRecord scopes.' - Enabled: true - -Rails/TimeZone: - Description: 'Checks the correct usage of time zone aware methods.' - StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' - Reference: 'http://danilenko.org/2012/7/6/rails_timezones' - Enabled: true - -Rails/Validation: - Description: 'Use validates :attribute, hash of validations.' - Enabled: true - -################## Style ################################# - -Style/AccessModifierIndentation: - Description: Check indentation of private/protected visibility modifiers. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected' - EnforcedStyle: outdent - Enabled: true - -Style/AccessorMethodName: - Description: Check the naming of accessor methods for get_/set_. - Enabled: true - -Style/Alias: - Description: 'Use alias_method instead of alias.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' - Enabled: true - -Style/AlignArray: - Description: >- - Align the elements of an array literal if they span more than - one line. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays' - Enabled: true - -Style/AlignHash: - Description: >- - Align the elements of a hash literal if they span more than - one line. - Enabled: true - -Style/AlignParameters: - Description: >- - Align the parameters of a method call if they span more - than one line. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' - Enabled: true - -Style/AndOr: - Description: 'Use &&/|| instead of and/or.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or' - Enabled: true - -Style/ArrayJoin: - Description: 'Use Array#join instead of Array#*.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' - Enabled: true - -Style/AsciiComments: - Description: 'Use only ascii symbols in comments.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' - Enabled: true - -Style/AsciiIdentifiers: - Description: 'Use only ascii symbols in identifiers.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' - Enabled: true - -Style/Attr: - Description: 'Checks for uses of Module#attr.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' - Enabled: true - -Style/BeginBlock: - Description: 'Avoid the use of BEGIN blocks.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' - Enabled: true - -Style/BarePercentLiterals: - Description: 'Checks if usage of %() or %Q() matches configuration.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand' - Enabled: true - -Style/BlockComments: - Description: 'Do not use block comments.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' - Enabled: true - -Style/BlockEndNewline: - Description: 'Put end statement of multiline block on its own line.' - Enabled: true - -Style/BlockDelimiters: - Description: >- - Avoid using {...} for multi-line blocks (multiline chaining is - always ugly). - Prefer {...} over do...end for single-line blocks. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' - Enabled: true - -Style/BracesAroundHashParameters: - Description: 'Enforce braces style around hash parameters.' - Enabled: true - -Style/CaseEquality: - Description: 'Avoid explicit use of the case equality operator(===).' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' - Enabled: true - -Style/CaseIndentation: - Description: 'Indentation of when in a case/when/[else/]end.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case' - Enabled: true - -Style/CharacterLiteral: - Description: 'Checks for uses of character literals.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' - Enabled: true - -Style/ClassAndModuleCamelCase: - Description: 'Use CamelCase for classes and modules.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' - Enabled: true - -Style/ClassAndModuleChildren: - Description: 'Checks style of children classes and modules.' - Enabled: true - -Style/ClassCheck: - Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' - Enabled: true - -Style/ClassMethods: - Description: 'Use self when defining module/class methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-class-methods' - Enabled: true - -Style/ClassVars: - Description: 'Avoid the use of class variables.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' - Enabled: true - -Style/ClosingParenthesisIndentation: - Description: 'Checks the indentation of hanging closing parentheses.' - Enabled: true - -Style/ColonMethodCall: - Description: 'Do not use :: for method call.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' - Enabled: true - -Style/CommandLiteral: - Description: 'Use `` or %x around command literals.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x' - Enabled: true - -Style/CommentAnnotation: - Description: 'Checks formatting of annotation comments.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' - Enabled: true - -Style/CommentIndentation: - Description: 'Indentation of comments.' - Enabled: true - -Style/ConstantName: - Description: 'Constants should use SCREAMING_SNAKE_CASE.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' - Enabled: true - -Style/DefWithParentheses: - Description: 'Use def with parentheses when there are arguments.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' - Enabled: true - -Style/DeprecatedHashMethods: - Description: 'Checks for use of deprecated Hash methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key' - Enabled: true - -Style/Documentation: - Description: 'Document classes and non-namespace modules.' - Enabled: false - -Style/DotPosition: - Description: 'Checks the position of the dot in multi-line method calls.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' - Enabled: true - -Style/DoubleNegation: - Description: 'Checks for uses of double negation (!!).' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' - Enabled: false - -Style/EachWithObject: - Description: 'Prefer `each_with_object` over `inject` or `reduce`.' - Enabled: true - -Style/ElseAlignment: - Description: 'Align elses and elsifs correctly.' - Enabled: true - -Style/EmptyElse: - Description: 'Avoid empty else-clauses.' - Enabled: true - -Style/EmptyLineBetweenDefs: - Description: 'Use empty lines between defs.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' - Enabled: true - -Style/EmptyLines: - Description: "Don't use several empty lines in a row." - Enabled: true - -Style/EmptyLinesAroundAccessModifier: - Description: "Keep blank lines around access modifiers." - Enabled: true - -Style/EmptyLinesAroundBlockBody: - Description: "Keeps track of empty lines around block bodies." - Enabled: true - -Style/EmptyLinesAroundClassBody: - Description: "Keeps track of empty lines around class bodies." - Enabled: true - -Style/EmptyLinesAroundModuleBody: - Description: "Keeps track of empty lines around module bodies." - Enabled: true - -Style/EmptyLinesAroundMethodBody: - Description: "Keeps track of empty lines around method bodies." - Enabled: true - -Style/EmptyLiteral: - Description: 'Prefer literals to Array.new/Hash.new/String.new.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' - Enabled: true - -Style/EndBlock: - Description: 'Avoid the use of END blocks.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' - Enabled: true - -Style/EndOfLine: - Description: 'Use Unix-style line endings.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' - Enabled: true - -Style/EvenOdd: - Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' - Enabled: true - -Style/ExtraSpacing: - Description: 'Do not use unnecessary spacing.' - Enabled: true - -Style/FileName: - Description: 'Use snake_case for source file names.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' - Enabled: true - -Style/InitialIndentation: - Description: >- - Checks the indentation of the first non-blank non-comment line in a file. - Enabled: true - -Style/FirstParameterIndentation: - Description: 'Checks the indentation of the first parameter in a method call.' - Enabled: true - -Style/FlipFlop: - Description: 'Checks for flip flops' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' - Enabled: true - -Style/For: - Description: 'Checks use of for or each in multiline loops.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops' - Enabled: true - -Style/FormatString: - Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' - Enabled: true - -Style/GlobalVars: - Description: 'Do not introduce global variables.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' - Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' - Enabled: true - -Style/GuardClause: - Description: 'Check for conditionals that can be replaced with guard clauses' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' - Enabled: true - -Style/HashSyntax: - Description: >- - Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax - { :a => 1, :b => 2 }. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals' - Enabled: true - -Style/IfUnlessModifier: - Description: >- - Favor modifier if/unless usage when you have a - single-line body. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' - Enabled: true - -Style/IfWithSemicolon: - Description: 'Do not use if x; .... Use the ternary operator instead.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' - Enabled: true - -Style/IndentationConsistency: - Description: 'Keep indentation straight.' - Enabled: true - -Style/IndentationWidth: - Description: 'Use 2 spaces for indentation.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' - Enabled: true - -Style/IndentArray: - Description: >- - Checks the indentation of the first element in an array - literal. - Enabled: true - -Style/IndentHash: - Description: 'Checks the indentation of the first key in a hash literal.' - Enabled: true - -Style/InfiniteLoop: - Description: 'Use Kernel#loop for infinite loops.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop' - Enabled: true - -Style/Lambda: - Description: 'Use the new lambda literal syntax for single-line blocks.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' - Enabled: true - -Style/LambdaCall: - Description: 'Use lambda.call(...) instead of lambda.(...).' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' - Enabled: true - -Style/LeadingCommentSpace: - Description: 'Comments should start with a space.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space' - Enabled: true - -Style/LineEndConcatenation: - Description: >- - Use \ instead of + or << to concatenate two string literals at - line end. - Enabled: true - -Style/MethodCallParentheses: - Description: 'Do not use parentheses for method calls with no arguments.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens' - Enabled: true - -Style/MethodDefParentheses: - Description: >- - Checks if the method definitions have or don't have - parentheses. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' - Enabled: true - -Style/MethodName: - Description: 'Use the configured style when naming methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' - Enabled: true - -Style/ModuleFunction: - Description: 'Checks for usage of `extend self` in modules.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' - Enabled: true - -Style/MultilineBlockChain: - Description: 'Avoid multi-line chains of blocks.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' - Enabled: true - -Style/MultilineBlockLayout: - Description: 'Ensures newlines after multiline block do statements.' - Enabled: true - -Style/MultilineIfThen: - Description: 'Do not use then for multi-line if/unless.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then' - Enabled: true - -Style/MultilineOperationIndentation: - Description: >- - Checks indentation of binary operations that span more than - one line. - Enabled: true - -Style/MultilineTernaryOperator: - Description: >- - Avoid multi-line ?: (the ternary operator); - use if/unless instead. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary' - Enabled: true - -Style/NegatedIf: - Description: >- - Favor unless over if for negative conditions - (or control flow or). - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' - Enabled: true - -Style/NegatedWhile: - Description: 'Favor until over while for negative conditions.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' - Enabled: true - -Style/NestedTernaryOperator: - Description: 'Use one expression per branch in a ternary operator.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary' - Enabled: true - -Style/Next: - Description: 'Use `next` to skip iteration instead of a condition at the end.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' - Enabled: true - -Style/NilComparison: - Description: 'Prefer x.nil? to x == nil.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' - Enabled: true - -Style/NonNilCheck: - Description: 'Checks for redundant nil checks.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks' - Enabled: true - -Style/Not: - Description: 'Use ! instead of not.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' - Enabled: true - -Style/NumericLiterals: - Description: >- - Add underscores to large numeric literals to improve their - readability. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' - Enabled: true - -Style/OneLineConditional: - Description: >- - Favor the ternary operator(?:) over - if/then/else/end constructs. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' - Enabled: true - -Style/OpMethod: - Description: 'When defining binary operators, name the argument other.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' - Enabled: true - -Style/OptionalArguments: - Description: >- - Checks for optional arguments that do not appear at the end - of the argument list - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#optional-arguments' - Enabled: true - -Style/ParallelAssignment: - Description: >- - Check for simple usages of parallel assignment. - It will only warn when the number of variables - matches on both sides of the assignment. - This also provides performance benefits - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment' - Enabled: true - -Style/ParenthesesAroundCondition: - Description: >- - Don't use parentheses around the condition of an - if/unless/while. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if' - Enabled: true - -Style/PercentLiteralDelimiters: - Description: 'Use `%`-literal delimiters consistently' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' - Enabled: true - -Style/PercentQLiterals: - Description: 'Checks if uses of %Q/%q match the configured preference.' - Enabled: true - -Style/PerlBackrefs: - Description: 'Avoid Perl-style regex back references.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' - Enabled: true - -Style/PredicateName: - Description: 'Check the names of predicate methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' - Enabled: true - -Style/Proc: - Description: 'Use proc instead of Proc.new.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' - Enabled: true - -Style/RaiseArgs: - Description: 'Checks the arguments passed to raise/fail.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' - Enabled: true - -Style/RedundantBegin: - Description: "Don't use begin blocks when they are not needed." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit' - Enabled: true - -Style/RedundantException: - Description: "Checks for an obsolete RuntimeException argument in raise/fail." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror' - Enabled: true - -Style/RedundantReturn: - Description: "Don't use return where it's not required." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' - Enabled: true - -Style/RedundantSelf: - Description: "Don't use self where it's not needed." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required' - Enabled: true - -Style/RegexpLiteral: - Description: 'Use / or %r around regular expressions.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' - Enabled: true - -Style/RescueEnsureAlignment: - Description: 'Align rescues and ensures correctly.' - Enabled: true - -Style/RescueModifier: - Description: 'Avoid using rescue in its modifier form.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers' - Enabled: true - -Style/SelfAssignment: - Description: >- - Checks for places where self-assignment shorthand should have - been used. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' - Enabled: true - -Style/Semicolon: - Description: "Don't use semicolons to terminate expressions." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' - Enabled: true - -Style/SignalException: - Description: 'Checks for proper usage of fail and raise.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' - Enabled: true - -Style/SingleLineBlockParams: - Description: 'Enforces the names of some block params.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' - Enabled: true - -Style/SingleLineMethods: - Description: 'Avoid single-line methods.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' - Enabled: true - -Style/SpaceBeforeFirstArg: - Description: >- - Checks that exactly one space is used between a method name - and the first argument for method calls without parentheses. - Enabled: true - -Style/SpaceAfterColon: - Description: 'Use spaces after colons.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' - Enabled: true - -Style/SpaceAfterComma: - Description: 'Use spaces after commas.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' - Enabled: true - -Style/SpaceAroundKeyword: - Description: 'Use spaces around keywords.' - Enabled: true - -Style/SpaceAfterMethodName: - Description: >- - Do not put a space between a method name and the opening - parenthesis in a method definition. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' - Enabled: true - -Style/SpaceAfterNot: - Description: Tracks redundant space after the ! operator. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang' - Enabled: true - -Style/SpaceAfterSemicolon: - Description: 'Use spaces after semicolons.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' - Enabled: true - -Style/SpaceBeforeBlockBraces: - Description: >- - Checks that the left block brace has or doesn't have space - before it. - Enabled: true - -Style/SpaceBeforeComma: - Description: 'No spaces before commas.' - Enabled: true - -Style/SpaceBeforeComment: - Description: >- - Checks for missing space between code and a comment on the - same line. - Enabled: true - -Style/SpaceBeforeSemicolon: - Description: 'No spaces before semicolons.' - Enabled: true - -Style/SpaceInsideBlockBraces: - Description: >- - Checks that block braces have or don't have surrounding space. - For blocks taking parameters, checks that the left brace has - or doesn't have trailing space. - Enabled: true - -Style/SpaceAroundBlockParameters: - Description: 'Checks the spacing inside and after block parameters pipes.' - Enabled: true - -Style/SpaceAroundEqualsInParameterDefault: - Description: >- - Checks that the equals signs in parameter default assignments - have or don't have surrounding space depending on - configuration. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals' - Enabled: true - -Style/SpaceAroundOperators: - Description: 'Use a single space around operators.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' - Enabled: true - -Style/SpaceInsideBrackets: - Description: 'No spaces after [ or before ].' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' - Enabled: true - -Style/SpaceInsideHashLiteralBraces: - Description: "Use spaces inside hash literal braces - or don't." - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' - Enabled: true - -Style/SpaceInsideParens: - Description: 'No spaces after ( or before ).' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' - Enabled: true - -Style/SpaceInsideRangeLiteral: - Description: 'No spaces inside range literals.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals' - Enabled: true - -Style/SpaceInsideStringInterpolation: - Description: 'Checks for padding/surrounding spaces inside string interpolation.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#string-interpolation' - Enabled: true - -Style/SpecialGlobalVars: - Description: 'Avoid Perl-style global variables.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' - Enabled: true + TargetRubyVersion: 2.4 Style/StringLiterals: - Description: 'Checks if uses of quotes match the configured preference.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' EnforcedStyle: double_quotes Enabled: true Style/StringLiteralsInInterpolation: - Description: >- - Checks if uses of quotes inside expressions in interpolated - strings match the configured preference. - Enabled: true - -Style/StructInheritance: - Description: 'Checks for inheritance from Struct.new.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new' - Enabled: true - -Style/SymbolLiteral: - Description: 'Use plain symbols instead of string symbols when possible.' - Enabled: true - -Style/SymbolProc: - Description: 'Use symbols as procs instead of blocks when possible.' - Enabled: true - -Style/Tab: - Description: 'No hard tabs.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' - Enabled: true - -Style/TrailingBlankLines: - Description: 'Checks trailing blank lines and final newline.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' - Enabled: true - -Style/TrailingCommaInArguments: - Description: 'Checks for trailing comma in parameter lists.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma' - Enabled: true - -Style/TrailingCommaInLiteral: - Description: 'Checks for trailing comma in literals.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' - Enabled: true - -Style/TrailingWhitespace: - Description: 'Avoid trailing whitespace.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace' - Enabled: true - -Style/TrivialAccessors: - Description: 'Prefer attr_* methods to trivial readers/writers.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' - Enabled: true - -Style/UnlessElse: - Description: >- - Do not use unless with else. Rewrite these with the positive - case first. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless' - Enabled: true - -Style/UnneededCapitalW: - Description: 'Checks for %W when interpolation is not needed.' - Enabled: true - -Style/UnneededPercentQ: - Description: 'Checks for %q/%Q when single quotes or double quotes would do.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q' - Enabled: true - -Style/TrailingUnderscoreVariable: - Description: >- - Checks for the usage of unneeded trailing underscores at the - end of parallel variable assignment. - Enabled: true - -Style/VariableInterpolation: - Description: >- - Don't interpolate global, instance and class variables - directly in strings. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' - Enabled: true - -Style/VariableName: - Description: 'Use the configured style when naming variables.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' - Enabled: true - -Style/WhenThen: - Description: 'Use when x then ... for one-line cases.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' + EnforcedStyle: double_quotes Enabled: true -Style/WhileUntilDo: - Description: 'Checks for redundant do after while or until.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do' - Enabled: true +Style/Documentation: + Enabled: false -Style/WhileUntilModifier: - Description: >- - Favor modifier while/until usage when you have a - single-line body. - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' - Enabled: true +Metrics/LineLength: + Max: 100 -Style/WordArray: - Description: 'Use %w or %W for arrays of words.' - StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' - Enabled: true +Metrics/BlockLength: + Exclude: + - spec/**/*.rb diff --git a/Gemfile b/Gemfile index 2740e55..0cb79bb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source "https://rubygems.org" # The gem's dependencies will be specified in addic7ed.gemspec gemspec @@ -7,9 +9,9 @@ group :test do end platforms :rbx do + gem "iconv" gem "json" + gem "psych" gem "racc" gem "rubysl" - gem "psych" - gem "iconv" end diff --git a/Rakefile b/Rakefile index 7629c26..7f8613f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) diff --git a/addic7ed.gemspec b/addic7ed.gemspec index 3b13119..bd6d841 100644 --- a/addic7ed.gemspec +++ b/addic7ed.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.push File.expand_path("../lib", __FILE__) require "addic7ed/version" diff --git a/bin/console b/bin/console index c9b6903..1471566 100755 --- a/bin/console +++ b/bin/console @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require "bundler/setup" Bundler.require(:default, ENV["RACK_ENV"] || :development) diff --git a/lib/addic7ed.rb b/lib/addic7ed.rb index 98a1ba0..b194976 100644 --- a/lib/addic7ed.rb +++ b/lib/addic7ed.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Dir[ File.join(File.dirname(__FILE__), "addic7ed/*.rb"), File.join(File.dirname(__FILE__), "addic7ed/services/**/*.rb"), diff --git a/lib/addic7ed/common.rb b/lib/addic7ed/common.rb index 5b7bdf7..803a44c 100644 --- a/lib/addic7ed/common.rb +++ b/lib/addic7ed/common.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require "json" module Addic7ed CONFIG_FILE = File.join(File.dirname(__FILE__), "config.json").freeze - CONFIG = JSON.load(File.open(CONFIG_FILE), nil, symbolize_names: true).freeze + CONFIG = JSON.parse(File.open(CONFIG_FILE), nil, symbolize_names: true).freeze LANGUAGES = CONFIG[:languages].freeze USER_AGENTS = CONFIG[:user_agents].freeze SHOWS_URL = CONFIG[:urls][:shows].freeze diff --git a/lib/addic7ed/errors.rb b/lib/addic7ed/errors.rb index 0034850..e5f9827 100644 --- a/lib/addic7ed/errors.rb +++ b/lib/addic7ed/errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed class InvalidFilename < StandardError; end class ShowNotFound < StandardError; end diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index 6cd7f81..ba1b54f 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "net/http" require "open-uri" @@ -91,7 +93,7 @@ def page_url(language) localized_urls[language] end - private + private def localized_urls @localized_urls ||= languages_hash { |code, lang| { code => localized_url(lang[:id]) } } diff --git a/lib/addic7ed/models/search.rb b/lib/addic7ed/models/search.rb index a879d91..635cff9 100644 --- a/lib/addic7ed/models/search.rb +++ b/lib/addic7ed/models/search.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed # Represents a subtitle search for a +video_filename+ in a +language+ with # multiple search +criterias+. @@ -79,7 +81,7 @@ def best_subtitle @best_subtitle ||= matching_subtitles.most_popular end - private + private def default_criterias { diff --git a/lib/addic7ed/models/subtitle.rb b/lib/addic7ed/models/subtitle.rb index 766b4cc..6c20937 100644 --- a/lib/addic7ed/models/subtitle.rb +++ b/lib/addic7ed/models/subtitle.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed # Represents a subtitle on Addic7ed. # diff --git a/lib/addic7ed/models/subtitles_collection.rb b/lib/addic7ed/models/subtitles_collection.rb index 622f74b..7e89e9d 100644 --- a/lib/addic7ed/models/subtitles_collection.rb +++ b/lib/addic7ed/models/subtitles_collection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed # Represents a collection of {Subtitle} objects. # @@ -54,7 +56,7 @@ def most_popular sort_by(&:downloads).last end - private + private def select SubtitlesCollection.new(super) diff --git a/lib/addic7ed/models/video_file.rb b/lib/addic7ed/models/video_file.rb index 9a0ae6a..3024533 100644 --- a/lib/addic7ed/models/video_file.rb +++ b/lib/addic7ed/models/video_file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed # Represents the video file you're looking a subtitle for. # diff --git a/lib/addic7ed/service.rb b/lib/addic7ed/service.rb index e3c5d2e..e2f9211 100644 --- a/lib/addic7ed/service.rb +++ b/lib/addic7ed/service.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed module Service def self.included(base) diff --git a/lib/addic7ed/services/check_compatibility.rb b/lib/addic7ed/services/check_compatibility.rb index d4e64db..f917c84 100644 --- a/lib/addic7ed/services/check_compatibility.rb +++ b/lib/addic7ed/services/check_compatibility.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed class CheckCompatibility include Service @@ -13,7 +15,7 @@ def call defined_as_compatible? || generally_compatible? || commented_as_compatible? end - private + private def defined_as_compatible? subtitle.version.split(",").include? group @@ -24,13 +26,13 @@ def generally_compatible? end def commented_as_compatible? - return false if /(won'?t|doesn'?t|not) +work/i =~ subtitle.comment - return false if /resync +(from|of|for)/i =~ subtitle.comment - !!comment_matches_a_compatible_group? + return false if /(won'?t|doesn'?t|not) +work/i.match?(subtitle.comment) + return false if /resync +(from|of|for)/i.match?(subtitle.comment) + comment_matches_a_compatible_group? end def comment_matches_a_compatible_group? - Regexp.new("(#{compatible_groups.join('|')})", "i") =~ subtitle.comment + Regexp.new("(#{compatible_groups.join("|")})", "i") =~ subtitle.comment end def compatible_groups diff --git a/lib/addic7ed/services/download_subtitle.rb b/lib/addic7ed/services/download_subtitle.rb index 7903a4a..04f814f 100644 --- a/lib/addic7ed/services/download_subtitle.rb +++ b/lib/addic7ed/services/download_subtitle.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed class DownloadSubtitle include Service @@ -15,12 +17,12 @@ def initialize(url, filename, referer, redirect_count = 0) def call raise DownloadError, "Too many HTTP redirections" if redirect_count >= HTTP_REDIRECT_LIMIT - raise DailyLimitExceeded, "Daily limit exceeded" if %r{^/downloadexceeded.php} =~ url + raise DailyLimitExceeded, "Daily limit exceeded" if %r{^/downloadexceeded.php}.match?(url) return follow_redirection(response["location"]) if response.is_a? Net::HTTPRedirection write(response.body) end - private + private def uri @uri ||= URI(url) diff --git a/lib/addic7ed/services/get_shows_list.rb b/lib/addic7ed/services/get_shows_list.rb index 545db0a..9a20caa 100644 --- a/lib/addic7ed/services/get_shows_list.rb +++ b/lib/addic7ed/services/get_shows_list.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "singleton" module Addic7ed @@ -12,7 +14,7 @@ def call @shows ||= homepage_body.css("select#qsShow option:not(:first-child)").map(&:text) end - private + private def homepage_body @homepage_body ||= Oga.parse_html(addic7ed_homepage.body) diff --git a/lib/addic7ed/services/normalize_comment.rb b/lib/addic7ed/services/normalize_comment.rb index badb6a2..69485d6 100644 --- a/lib/addic7ed/services/normalize_comment.rb +++ b/lib/addic7ed/services/normalize_comment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed class NormalizeComment include Service diff --git a/lib/addic7ed/services/normalize_version.rb b/lib/addic7ed/services/normalize_version.rb index 6bdb8d8..680151a 100644 --- a/lib/addic7ed/services/normalize_version.rb +++ b/lib/addic7ed/services/normalize_version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed class NormalizeVersion include Service diff --git a/lib/addic7ed/services/parse_page.rb b/lib/addic7ed/services/parse_page.rb index 7af323e..2943d2a 100644 --- a/lib/addic7ed/services/parse_page.rb +++ b/lib/addic7ed/services/parse_page.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "oga" require "net/http" require "open-uri" @@ -17,7 +19,7 @@ def call subtitles_nodes.map { |subtitle_node| Addic7ed::ParseSubtitle.call(subtitle_node) } end - private + private def page_dom raise EpisodeNotFound if server_response.body.nil? || server_response.body.empty? diff --git a/lib/addic7ed/services/parse_subtitle.rb b/lib/addic7ed/services/parse_subtitle.rb index afc8473..eeea572 100644 --- a/lib/addic7ed/services/parse_subtitle.rb +++ b/lib/addic7ed/services/parse_subtitle.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "oga" module Addic7ed @@ -6,7 +8,7 @@ class ParseSubtitle attr_reader :subtitle_node - FIELDS = [:version, :language, :status, :url, :source, :hi, :downloads, :comment].freeze + FIELDS = %i[version language status url source hi downloads comment].freeze def initialize(subtitle_node) @subtitle_node = subtitle_node @@ -16,7 +18,7 @@ def call Addic7ed::Subtitle.new(extract_fields) end - private + private def extract_fields FIELDS.map do |field| @@ -48,7 +50,7 @@ def extract_status def extract_url extract_field("a.buttonDownload:last-of-type") do |node| - "http://www.addic7ed.com#{node['href']}" + "http://www.addic7ed.com#{node["href"]}" end end diff --git a/lib/addic7ed/services/url_encode_show_name.rb b/lib/addic7ed/services/url_encode_show_name.rb index 7a95404..d0c2fc9 100644 --- a/lib/addic7ed/services/url_encode_show_name.rb +++ b/lib/addic7ed/services/url_encode_show_name.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed class URLEncodeShowName include Service @@ -18,7 +20,7 @@ def call matching_shows.last.tr(" ", "_") end - private + private def matching_shows(opts) addic7ed_shows.select { |show_name| matching?(show_name, opts) } diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index 84a8a17..d0bbda8 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Addic7ed - VERSION = "4.0.0-beta.5".freeze + VERSION = "4.0.0-beta.5" end diff --git a/spec/lib/addic7ed/common_spec.rb b/spec/lib/addic7ed/common_spec.rb index ee34d15..03c567d 100644 --- a/spec/lib/addic7ed/common_spec.rb +++ b/spec/lib/addic7ed/common_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed do diff --git a/spec/lib/addic7ed/models/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb index fdee1ce..36191aa 100644 --- a/spec/lib/addic7ed/models/episode_spec.rb +++ b/spec/lib/addic7ed/models/episode_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::Episode, "#subtitles" do @@ -7,7 +9,7 @@ let(:episode) { described_class.new(showname, season, episode_nbr) } before do - [:fr, :en, :it].each do |lang| + %i[fr en it].each do |lang| lang_id = Addic7ed::LANGUAGES[lang][:id] stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/#{lang_id}") .to_return File.new("spec/responses/walking-dead-3-2-#{lang_id}.http") diff --git a/spec/lib/addic7ed/models/search_spec.rb b/spec/lib/addic7ed/models/search_spec.rb index 2a8e877..45b44ca 100644 --- a/spec/lib/addic7ed/models/search_spec.rb +++ b/spec/lib/addic7ed/models/search_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::Search do diff --git a/spec/lib/addic7ed/models/subtitle_spec.rb b/spec/lib/addic7ed/models/subtitle_spec.rb index 393d941..6b1d61e 100644 --- a/spec/lib/addic7ed/models/subtitle_spec.rb +++ b/spec/lib/addic7ed/models/subtitle_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::Subtitle, "#initialize" do diff --git a/spec/lib/addic7ed/models/subtitles_collection_spec.rb b/spec/lib/addic7ed/models/subtitles_collection_spec.rb index 98e34ef..73a47d8 100644 --- a/spec/lib/addic7ed/models/subtitles_collection_spec.rb +++ b/spec/lib/addic7ed/models/subtitles_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::SubtitlesCollection do diff --git a/spec/lib/addic7ed/models/video_file_spec.rb b/spec/lib/addic7ed/models/video_file_spec.rb index 85f193f..926c185 100644 --- a/spec/lib/addic7ed/models/video_file_spec.rb +++ b/spec/lib/addic7ed/models/video_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::VideoFile do @@ -10,9 +12,9 @@ expect(file.showname).to eq(expected_show_name || "Showname") expect(file.season).to eq 2 expect(file.episode).to eq 1 - expect(file.tags).to eq %w(720P HDTV X264) + expect(file.tags).to eq %w[720P HDTV X264] expect(file.group).to eq "GROUP" - expect(file.distribution).to satisfy { |d| ["", "DISTRIBUTION"].include?(d) } + expect(file.distribution).to(satisfy { |d| ["", "DISTRIBUTION"].include?(d) }) end end @@ -152,14 +154,14 @@ describe "#inspect" do let(:expected) do - <<-EOS -Guesses for Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv: - show: Showname - season: 2 - episode: 1 - tags: ["720P", "HDTV", "X264"] - group: GROUP - distribution: DISTRIBUTION + <<~EOS + Guesses for Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv: + show: Showname + season: 2 + episode: 1 + tags: ["720P", "HDTV", "X264"] + group: GROUP + distribution: DISTRIBUTION EOS end diff --git a/spec/lib/addic7ed/services/check_compatibility_spec.rb b/spec/lib/addic7ed/services/check_compatibility_spec.rb index bec4dba..ee72743 100644 --- a/spec/lib/addic7ed/services/check_compatibility_spec.rb +++ b/spec/lib/addic7ed/services/check_compatibility_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::CheckCompatibility, "#call(subtitle, group)" do diff --git a/spec/lib/addic7ed/services/download_subtitle_spec.rb b/spec/lib/addic7ed/services/download_subtitle_spec.rb index faa70f0..ff8b559 100644 --- a/spec/lib/addic7ed/services/download_subtitle_spec.rb +++ b/spec/lib/addic7ed/services/download_subtitle_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::DownloadSubtitle do diff --git a/spec/lib/addic7ed/services/get_shows_list_spec.rb b/spec/lib/addic7ed/services/get_shows_list_spec.rb index 14fa0dd..3701d96 100644 --- a/spec/lib/addic7ed/services/get_shows_list_spec.rb +++ b/spec/lib/addic7ed/services/get_shows_list_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::GetShowsList do diff --git a/spec/lib/addic7ed/services/normalize_comment_spec.rb b/spec/lib/addic7ed/services/normalize_comment_spec.rb index 19c61d5..c143268 100644 --- a/spec/lib/addic7ed/services/normalize_comment_spec.rb +++ b/spec/lib/addic7ed/services/normalize_comment_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::NormalizeComment do diff --git a/spec/lib/addic7ed/services/normalize_version_spec.rb b/spec/lib/addic7ed/services/normalize_version_spec.rb index ce70e5c..b7abb65 100644 --- a/spec/lib/addic7ed/services/normalize_version_spec.rb +++ b/spec/lib/addic7ed/services/normalize_version_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::NormalizeVersion do diff --git a/spec/lib/addic7ed/services/parse_page_spec.rb b/spec/lib/addic7ed/services/parse_page_spec.rb index 2a68df5..ce39d34 100644 --- a/spec/lib/addic7ed/services/parse_page_spec.rb +++ b/spec/lib/addic7ed/services/parse_page_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::ParsePage do diff --git a/spec/lib/addic7ed/services/parse_subtitle_spec.rb b/spec/lib/addic7ed/services/parse_subtitle_spec.rb index f7a0847..63f0321 100644 --- a/spec/lib/addic7ed/services/parse_subtitle_spec.rb +++ b/spec/lib/addic7ed/services/parse_subtitle_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::ParseSubtitle do diff --git a/spec/lib/addic7ed/services/url_encode_show_name_spec.rb b/spec/lib/addic7ed/services/url_encode_show_name_spec.rb index 148a31e..408a52f 100644 --- a/spec/lib/addic7ed/services/url_encode_show_name_spec.rb +++ b/spec/lib/addic7ed/services/url_encode_show_name_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Addic7ed::URLEncodeShowName do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 40e7c67..e014b6c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + unless RUBY_ENGINE == "rbx" require "coveralls" Coveralls.wear! From 1541f45ddb9b07bed14c3fe42828987bf020e2ba Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 13:43:41 +0200 Subject: [PATCH 54/89] Run rubocop on Travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9c51777..3f27b17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,8 @@ rvm: before_install: - gem update --system - gem update bundler +before_script: + - bundle exec rubocop -D notifications: email: on_success: change From f40362ceb8b4f64c36ff98d570cd0464b09f0996 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 14:02:46 +0200 Subject: [PATCH 55/89] Fix specs --- lib/addic7ed/common.rb | 2 +- .../services/check_compatibility_spec.rb | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/addic7ed/common.rb b/lib/addic7ed/common.rb index 803a44c..727eeb5 100644 --- a/lib/addic7ed/common.rb +++ b/lib/addic7ed/common.rb @@ -4,7 +4,7 @@ module Addic7ed CONFIG_FILE = File.join(File.dirname(__FILE__), "config.json").freeze - CONFIG = JSON.parse(File.open(CONFIG_FILE), nil, symbolize_names: true).freeze + CONFIG = JSON.parse(File.read(CONFIG_FILE), symbolize_names: true).freeze LANGUAGES = CONFIG[:languages].freeze USER_AGENTS = CONFIG[:user_agents].freeze SHOWS_URL = CONFIG[:urls][:shows].freeze diff --git a/spec/lib/addic7ed/services/check_compatibility_spec.rb b/spec/lib/addic7ed/services/check_compatibility_spec.rb index ee72743..005731b 100644 --- a/spec/lib/addic7ed/services/check_compatibility_spec.rb +++ b/spec/lib/addic7ed/services/check_compatibility_spec.rb @@ -12,7 +12,7 @@ let(:group) { subtitle.version } it "returns true" do - expect(subject).to be true + expect(subject).to be_truthy end end @@ -21,7 +21,7 @@ let(:group) { "FOV" } it "returns true" do - expect(subject).to be true + expect(subject).to be_truthy end end @@ -29,7 +29,7 @@ let(:group) { "LOL" } it "returns true" do - expect(subject).to be true + expect(subject).to be_truthy end end @@ -38,7 +38,7 @@ let(:group) { "DIMENSION" } it "returns true" do - expect(subject).to be true + expect(subject).to be_truthy end end @@ -46,7 +46,7 @@ let(:group) { "EVOLVE" } it "returns false" do - expect(subject).to be false + expect(subject).to be_falsey end end @@ -57,7 +57,7 @@ let(:group) { "FOV" } it "returns true" do - expect(subject).to be true + expect(subject).to be_truthy end end @@ -65,7 +65,7 @@ let(:group) { "EVOLVE" } it "returns false" do - expect(subject).to be false + expect(subject).to be_falsey end end end @@ -75,7 +75,7 @@ let(:group) { "FOV" } it "returns false" do - expect(subject).to be false + expect(subject).to be_falsey end end @@ -84,7 +84,7 @@ let(:group) { "FOV" } it "returns false" do - expect(subject).to be false + expect(subject).to be_falsey end end end From db37e687ccec85d68b3f5a2ea5bb2e8b47e434fe Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 14:03:03 +0200 Subject: [PATCH 56/89] Remove useless dependency to the json gem --- addic7ed.gemspec | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addic7ed.gemspec b/addic7ed.gemspec index bd6d841..2151514 100644 --- a/addic7ed.gemspec +++ b/addic7ed.gemspec @@ -22,8 +22,7 @@ Gem::Specification.new do |s| s.add_development_dependency("inch") s.add_development_dependency("yard") - s.add_runtime_dependency("oga", "~> 2.7") - s.add_runtime_dependency("json", "~> 1.8.3") + s.add_runtime_dependency("oga", "~> 2.7") s.files = `git ls-files -z lib LICENSE.md`.split("\x0") s.require_paths = ["lib"] From f4de55ac5dc1255f09c31f74fc5208143ac7a171 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 15:44:56 +0200 Subject: [PATCH 57/89] Remove useless allowance of WebMock in bin/console --- bin/console | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/console b/bin/console index 1471566..1a70cf0 100755 --- a/bin/console +++ b/bin/console @@ -10,6 +10,4 @@ $LOAD_PATH.unshift File.expand_path("lib", APP_ROOT) require "addic7ed" -WebMock.allow_net_connect! - Pry.start From 350805fb2c2949b89795771fbec00a331b1c2603 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 15:49:50 +0200 Subject: [PATCH 58/89] :rocket: Speedup retrieval of the shows list (by avoiding to use an uber-slow CSS selector). It also massively speeds up the specs by 2000+% (from 93s to 4s on my computer) --- lib/addic7ed/services/get_shows_list.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/addic7ed/services/get_shows_list.rb b/lib/addic7ed/services/get_shows_list.rb index 9a20caa..fa4505c 100644 --- a/lib/addic7ed/services/get_shows_list.rb +++ b/lib/addic7ed/services/get_shows_list.rb @@ -11,13 +11,13 @@ def self.call end def call - @shows ||= homepage_body.css("select#qsShow option:not(:first-child)").map(&:text) + @shows ||= homepage_body.css("select#qsShow option").to_a[1..-1].map(&:text) end private def homepage_body - @homepage_body ||= Oga.parse_html(addic7ed_homepage.body) + Oga.parse_html(addic7ed_homepage.body) end def addic7ed_homepage From 6af88637a473332332809bf17960c7fd59daa488 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 16:15:58 +0200 Subject: [PATCH 59/89] Stop using Ruby 2.4+ Regexp.matches? for more backward-compatibility --- lib/addic7ed/services/check_compatibility.rb | 14 +++++++++++--- lib/addic7ed/services/download_subtitle.rb | 6 +++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/addic7ed/services/check_compatibility.rb b/lib/addic7ed/services/check_compatibility.rb index f917c84..c09bc05 100644 --- a/lib/addic7ed/services/check_compatibility.rb +++ b/lib/addic7ed/services/check_compatibility.rb @@ -26,13 +26,21 @@ def generally_compatible? end def commented_as_compatible? - return false if /(won'?t|doesn'?t|not) +work/i.match?(subtitle.comment) - return false if /resync +(from|of|for)/i.match?(subtitle.comment) + return false if comment_explicitely_wont_work? + return false if comment_is_a_resync? comment_matches_a_compatible_group? end def comment_matches_a_compatible_group? - Regexp.new("(#{compatible_groups.join("|")})", "i") =~ subtitle.comment + !Regexp.new("(#{compatible_groups.join("|")})", "i").match(subtitle.comment).nil? + end + + def comment_explicitely_wont_work? + !/(won'?t|doesn'?t|not) +work/i.match(subtitle.comment).nil? + end + + def comment_is_a_resync? + !/resync +(from|of|for)/i.match(subtitle.comment).nil? end def compatible_groups diff --git a/lib/addic7ed/services/download_subtitle.rb b/lib/addic7ed/services/download_subtitle.rb index 04f814f..5f362fa 100644 --- a/lib/addic7ed/services/download_subtitle.rb +++ b/lib/addic7ed/services/download_subtitle.rb @@ -17,7 +17,7 @@ def initialize(url, filename, referer, redirect_count = 0) def call raise DownloadError, "Too many HTTP redirections" if redirect_count >= HTTP_REDIRECT_LIMIT - raise DailyLimitExceeded, "Daily limit exceeded" if %r{^/downloadexceeded.php}.match?(url) + raise DailyLimitExceeded, "Daily limit exceeded" if download_limit_exceeded? return follow_redirection(response["location"]) if response.is_a? Net::HTTPRedirection write(response.body) end @@ -28,6 +28,10 @@ def uri @uri ||= URI(url) end + def download_limit_exceeded? + !%r{^/downloadexceeded.php}.match(url).nil? + end + def response @response ||= Net::HTTP.start(uri.hostname, uri.port) do |http| request = Net::HTTP::Get.new(uri.request_uri) From 0175a896bea6c42e88e616b6728bc5e5cf103ada Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 16:26:57 +0200 Subject: [PATCH 60/89] Stop using <<~EOS heredoc syntax for backward compatibility --- lib/addic7ed/version.rb | 2 +- spec/lib/addic7ed/models/video_file_spec.rb | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index d0bbda8..bc2fd90 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Addic7ed - VERSION = "4.0.0-beta.5" + VERSION = "4.0.0-beta.5".freeze # rubocop:disable Style/RedundantFreeze end diff --git a/spec/lib/addic7ed/models/video_file_spec.rb b/spec/lib/addic7ed/models/video_file_spec.rb index 926c185..5888ccc 100644 --- a/spec/lib/addic7ed/models/video_file_spec.rb +++ b/spec/lib/addic7ed/models/video_file_spec.rb @@ -154,14 +154,14 @@ describe "#inspect" do let(:expected) do - <<~EOS + <<-EOS Guesses for Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv: - show: Showname - season: 2 - episode: 1 - tags: ["720P", "HDTV", "X264"] - group: GROUP - distribution: DISTRIBUTION + show: Showname + season: 2 + episode: 1 + tags: ["720P", "HDTV", "X264"] + group: GROUP + distribution: DISTRIBUTION EOS end From 24b167433184dfbc6349fac77631655a0916526b Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 16:42:30 +0200 Subject: [PATCH 61/89] Update Rubinius on Travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3f27b17..9b79f1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ rvm: - ruby-2.2 - ruby-2.3 - ruby-2.4 - - rbx-3.70 + - rbx-3.84 - jruby before_install: - gem update --system From 23fef0813930df9547155eb36c4eee4e1d85ff8a Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 16:46:09 +0200 Subject: [PATCH 62/89] Just use the latest varsion of Rubinius on Travis, always --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9b79f1c..10b85c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ rvm: - ruby-2.2 - ruby-2.3 - ruby-2.4 - - rbx-3.84 + - rubinius - jruby before_install: - gem update --system From d28dc576452445d4a61809144a5c78e5b4f6629e Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 17:04:33 +0200 Subject: [PATCH 63/89] Try another syntax for rubinius in Travis, because the documented syntax just doesn't work --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 10b85c9..a30e32f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ rvm: - ruby-2.2 - ruby-2.3 - ruby-2.4 - - rubinius + - rbx - jruby before_install: - gem update --system From 33fbff8a36c6b46ea53979d45301ea59d20a59b6 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 17:08:43 +0200 Subject: [PATCH 64/89] =?UTF-8?q?Fix=20Rubinius=20syntax=20for=20Travis=20?= =?UTF-8?q?AGAIN=20(=E2=95=AF=C2=B0=E2=96=A1=C2=B0=EF=BC=89=E2=95=AF?= =?UTF-8?q?=EF=B8=B5=20=E2=94=BB=E2=94=81=E2=94=BB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a30e32f..9b79f1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ rvm: - ruby-2.2 - ruby-2.3 - ruby-2.4 - - rbx + - rbx-3.84 - jruby before_install: - gem update --system From 304549279034df6b55415c9662a4467590f37441 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 17:12:24 +0200 Subject: [PATCH 65/89] Minor README update --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 40db3f2..8f9eecf 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Fetch TV shows subtitles on Addic7ed using Ruby. * [x] move user agents and languages to a config file * [ ] add specs for parsing * [ ] Create a `Dockerfile` for the CLI +* [ ] Add a Bundler after-install hook to notify users that this gem is no longer providing a CLI ### Is it working ? @@ -119,6 +120,7 @@ This projet supports the following Ruby versions/implementations: * Ruby 2.1 (MRI) * Ruby 2.2 (MRI) * Ruby 2.3 (MRI) +* Ruby 2.4 (MRI) * Rubinius * JRuby From da3f58364b06f791c14828d71274f888665d52b9 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 18:47:10 +0200 Subject: [PATCH 66/89] Bump version --- lib/addic7ed/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index bc2fd90..33a725f 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Addic7ed - VERSION = "4.0.0-beta.5".freeze # rubocop:disable Style/RedundantFreeze + VERSION = "4.0.0-beta.6".freeze # rubocop:disable Style/RedundantFreeze end From 451190f9e6a3aeb8c8a60b714d9abcdf1ea87f27 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 20:11:47 +0200 Subject: [PATCH 67/89] Add a post-install message to warn that there's no more CLI binary provided by this gem --- addic7ed.gemspec | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/addic7ed.gemspec b/addic7ed.gemspec index 2151514..cdeb88f 100644 --- a/addic7ed.gemspec +++ b/addic7ed.gemspec @@ -5,14 +5,26 @@ $LOAD_PATH.push File.expand_path("../lib", __FILE__) require "addic7ed/version" Gem::Specification.new do |s| - s.name = "addic7ed" - s.version = Addic7ed::VERSION - s.platform = Gem::Platform::RUBY - s.summary = "Addic7ed auto-downloader" - s.description = "Ruby script (cli) to fetch subtitles on Addic7ed" - s.authors = ["Michael Baudino"] - s.email = "michael.baudino@alpine-lab.com" - s.homepage = "https://github.com/michaelbaudino/addic7ed-ruby" + s.name = "addic7ed" + s.version = Addic7ed::VERSION + s.platform = Gem::Platform::RUBY + s.summary = "Addic7ed auto-downloader" + s.description = "Ruby script (cli) to fetch subtitles on Addic7ed" + s.authors = ["Michael Baudino"] + s.email = "michael.baudino@alpine-lab.com" + s.homepage = "https://github.com/michaelbaudino/addic7ed-ruby" + s.files = `git ls-files -z lib LICENSE.md`.split("\x0") + s.require_paths = ["lib"] + s.license = "MIT" + s.post_install_message = <<-EOS + + Important update if you're upgrading from 3.x to 4.x: + + This gem is not providing a CLI tool anymore. + If you're using it as a Ruby API for Addic7ed, you're all good, ignore this message. + If you're expecting the `addic7ed` binary, we've moved it to the `addic7ed-cli` gem. + + EOS s.add_development_dependency("rspec") s.add_development_dependency("rake") @@ -23,8 +35,4 @@ Gem::Specification.new do |s| s.add_development_dependency("yard") s.add_runtime_dependency("oga", "~> 2.7") - - s.files = `git ls-files -z lib LICENSE.md`.split("\x0") - s.require_paths = ["lib"] - s.license = "MIT" end From a4c739d56d42321a76d5e054d172ea7bfedd030a Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sun, 13 Aug 2017 20:39:25 +0200 Subject: [PATCH 68/89] Update README --- CODE_OF_CONDUCT.md | 49 +++++++++++++++++++ README.md | 115 ++++++--------------------------------------- TODO.md | 36 ++++++++++++++ 3 files changed, 100 insertions(+), 100 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 TODO.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..6c6ef27 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,49 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating +documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to +fairly and consistently applying these principles to every aspect of managing +this project. Project maintainers who do not follow or enforce the Code of +Conduct may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting a project maintainer at michael.baudino@alpine-lab.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. Maintainers are +obligated to maintain confidentiality with regard to the reporter of an +incident. + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.3.0, available at +[http://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/3/0/ diff --git a/README.md b/README.md index 8f9eecf..d6537c2 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,3 @@ -> ## :information_source: Upcoming Design Breaking Changes -> -> I'm [currently working on a full rewrite](https://github.com/michaelbaudino/addic7ed-ruby/pull/30) which will introduce some heavily breaking changes, including: -> -> * a complete API refactor (almost a full rewrite) to enforce code quality, maintainability and single responsability principle -> * documentation! -> * the removal of any CLI feature (they will be moved to a separate `addic7ed-ruby-cli` gem) -> -> I'll publish some beta versions of the gem before merging the PR into `master` and releasing a final version (remember that the gem will no longer include a CLI tool). I'd love to receive some feedback about it, so feel free to [send me an email](mailto:michael.baudino@alpine-lab.com). - # addic7ed-ruby [![Build Status](https://api.travis-ci.org/michaelbaudino/addic7ed-ruby.svg?branch=full-rewrite)](https://travis-ci.org/michaelbaudino/addic7ed-ruby) @@ -18,103 +8,25 @@ [![security](https://hakiri.io/github/michaelbaudino/addic7ed-ruby/master.svg)](https://hakiri.io/github/michaelbaudino/addic7ed-ruby/master) [![Inline docs](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby.svg?branch=full-rewrite)](http://inch-ci.org/github/michaelbaudino/addic7ed-ruby?branch=full-rewrite) -Fetch TV shows subtitles on Addic7ed using Ruby. - -### Refactoring TODO list - -* [x] move download logic to a service object -* [x] add a new `Search` model -* [x] move compatibility logic to a `CheckCompatibility` service object -* [x] create a `SubtitlesCollection` class to hold and filter subtitles -* [x] move best subtitle logic to `SubtitlesCollection` -* [x] rename `Subtitle#via` to `Subtitle#source` -* [x] refactor how `Episode` holds `Subtitle`s -* [x] rename `ShowList` and make it a service object -* [x] write code documentation -* [x] Configure GitHub hook for [RubyDoc](http://www.rubydoc.info) -* [ ] move CLI to a separate gem (and use Thor or similar) -* [ ] update README to include only API stuff -* [x] add `bin/console` -* [x] remove `bin/addic7ed` -* [ ] move all `to_s` and `to_inspect` methods to the CLI -* [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) -* [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") -* [x] use symbols rather than strings for languages -* [x] add Rubocop -* [x] move user agents and languages to a config file -* [ ] add specs for parsing -* [ ] Create a `Dockerfile` for the CLI -* [ ] Add a Bundler after-install hook to notify users that this gem is no longer providing a CLI - -### Is it working ? - -As long as Addic7ed HTML/CSS structure remains the same: yes :smile: - -### How to use it ? - -1. Install it: - - ```bash - $ gem install addic7ed - ``` -2. Use it (e.g. to download a French subtitle for a "Californication" episode): - - ```bash - $ addic7ed -l fr /path/to/Californication.S06E07.720p.HDTV.x264-2HD.mkv - ``` -3. A wild `Californication.S06E07.720p.HDTV.x264-2HD.fr.srt` file appears -4. Enjoy your show :tv: - -### Are there any options ? - -Sure ! - -```bash -$ addic7ed -h -Usage: addic7ed [options] [, , ...] - -l, --language [LANGUAGE] Language code to look subtitles for (default: French) - --no-hi Only download subtitles without Hearing Impaired lines - -a, --all-subtitles Display all available subtitles - -n, --do-not-download Do not download the subtitle - -f, --force Overwrite existing subtitle - -u, --untagged Do not include language code in subtitle filename - -v, --[no-]verbose Run verbosely - -q, --quiet Run without output (cron-mode) - -d, --debug Debug mode [do not use] - -h, --help Show this message - -L, --list-languages List all available languages - -V, --version Show version number -``` - -### How to contribute ? - -Feel free to submit a Pull Request, I'd be glad to review/merge it. +A Ruby API for [Addic7ed](http://www.addic7ed.com), the best TV subtitles community in the world. -Also, if you like the awesome work done by the Addic7ed team, please consider [donating to them](http://www.addic7ed.com) ! +## Usage -### Notes +> :books: This needs to be written... -Addic7ed restricts the number of subtitle download to 15 per 24h (30 per 24h for registered users, and 55 for VIP users). +## Notes -Don't get mad, they have to pay for their servers, you know. +Addic7ed restricts the number of subtitle download to 15 per 24h (30 per 24h for registered users, and 55 for VIP users). Don't get mad, they have to pay for their servers, you know. Ho, and by the way, please, **please**: do not hammer their servers, play fair! -Ho, and by the way, please, **please**: do not hammer their servers, play fair ! +## Contribute -### Contribution ideas +Feel free to submit a Pull Request, I'd be glad to review/merge it. -There's some work remaining: -- Support registered users -- Support directory parsing -- Support "hearing impaired" versions -- Document code -- Test cli behaviour -- Colorize output -- Write doc for cron usage -- Write doc for iwatch usage +Also, if you like the awesome work done by the Addic7ed team, please consider [donating to them](http://www.addic7ed.com) ! ### Supported Ruby versions -This projet supports the following Ruby versions/implementations: +This project [supports](https://github.com/michaelbaudino/addic7ed-ruby/blob/full-rewrite/.travis.yml) the following Ruby versions/implementations: * Ruby 2.0 (MRI) * Ruby 2.1 (MRI) @@ -130,7 +42,10 @@ This projet supports the following Ruby versions/implementations: $ gem install rubysl ``` -### License +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/michaelbaudino/addic7ed-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct (see `CODE_OF_CONDUCT.md` file). + +## License -This project is released under the terms of the MIT license. -See `LICENSE.md` file for details. +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT) (see `LICENSE.md` file). diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..fd62b4f --- /dev/null +++ b/TODO.md @@ -0,0 +1,36 @@ +# TODO + +Here is a list of future features I'd like to implement. + +> :innocent: It's also an interesting list of tasks to pickup if you'd like to contribute. + +## For 4.0 (the full-rewrite) + +* [x] move download logic to a service object +* [x] add a new `Search` model +* [x] move compatibility logic to a `CheckCompatibility` service object +* [x] create a `SubtitlesCollection` class to hold and filter subtitles +* [x] move best subtitle logic to `SubtitlesCollection` +* [x] rename `Subtitle#via` to `Subtitle#source` +* [x] refactor how `Episode` holds `Subtitle`s +* [x] rename `ShowList` and make it a service object +* [x] write code documentation +* [x] Configure GitHub hook for [RubyDoc](http://www.rubydoc.info) +* [x] add `bin/console` +* [x] remove `bin/addic7ed` +* [x] use symbols rather than strings for languages +* [x] add Rubocop +* [x] move user agents and languages to a config file +* [x] Add a Bundler post-install message to notify users that this gem is no longer providing a CLI +* [ ] update README to include only API stuff +* [ ] move CLI to a separate gem (and use Thor or similar) +* [ ] move all `to_s` and `to_inspect` methods to the CLI +* [ ] update links/badges to the `full-rewrite` branch in `README.md` to use `master` +* [ ] release `4.0` :champagne: + +## More features + +* [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) +* [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") +* [ ] add specs for HTML parsing +* [ ] support registered users (to avoid download throttle) From 35a97d325ebd50157c56c3a3b0cbc46ab3abfa8c Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 22 Aug 2017 20:50:17 +0200 Subject: [PATCH 69/89] Change SubtitlesCollection to a proper custom Enumerable --- lib/addic7ed/models/subtitles_collection.rb | 28 +++++++++++++------ .../models/subtitles_collection_spec.rb | 4 +-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/addic7ed/models/subtitles_collection.rb b/lib/addic7ed/models/subtitles_collection.rb index 7e89e9d..9494733 100644 --- a/lib/addic7ed/models/subtitles_collection.rb +++ b/lib/addic7ed/models/subtitles_collection.rb @@ -3,9 +3,25 @@ module Addic7ed # Represents a collection of {Subtitle} objects. # - # This collection inherits from {Array} so it behaves exaclty like it, - # but it also provides some methods to filter, order and choose the best subtitle. - class SubtitlesCollection < Array + # This collection is a custom {Enumerable} which provides + # some methods to filter, order and choose the best subtitle. + class SubtitlesCollection + include Enumerable + + # @!visibility private + def each(&block) + @subtitles.each(&block) + end + + # Creates a filterable, sortable collection of subtitles + # @param subtitles [Enumerable] List of subtitles to initialize the collection with + # + # @return [SubtitleCollection] the initialized subtitles collection + + def initialize(subtitles = []) + @subtitles = subtitles + end + # Returns only subtitles that are compatible with +group+. # @param group [String] Release group we want the returned subtitles to be compatible with # @@ -55,11 +71,5 @@ def completed def most_popular sort_by(&:downloads).last end - - private - - def select - SubtitlesCollection.new(super) - end end end diff --git a/spec/lib/addic7ed/models/subtitles_collection_spec.rb b/spec/lib/addic7ed/models/subtitles_collection_spec.rb index 73a47d8..061ebba 100644 --- a/spec/lib/addic7ed/models/subtitles_collection_spec.rb +++ b/spec/lib/addic7ed/models/subtitles_collection_spec.rb @@ -3,8 +3,8 @@ require "spec_helper" describe Addic7ed::SubtitlesCollection do - it "inherits from Array" do - expect(described_class.superclass).to eq Array + it "is an Enumerable" do + expect(subject).to be_a Enumerable end end From 378e57a755afea3ef79e554a6d72d661a037d672 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 22 Aug 2017 21:56:40 +0200 Subject: [PATCH 70/89] Allow Episode#page_url to return the URL of the page with subtitles for all languages --- lib/addic7ed/models/episode.rb | 11 ++++++----- spec/lib/addic7ed/models/episode_spec.rb | 12 ++++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index ba1b54f..285c5f3 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -78,8 +78,8 @@ def subtitles(language) @subtitles[language] ||= Addic7ed::ParsePage.call(page_url(language)) end - # Returns the URL of the Addic7ed webpage listing subtitles for this {Episode} - # in the given +language+. + # Returns the URL of the Addic7ed webpage listing subtitles for this {Episode}. + # If +language+ is given, it returns the URL of the page with subtitles for this language only. # # @param language [String] Language code we want the webpage to list subtitles in. # @@ -89,7 +89,8 @@ def subtitles(language) # Addic7ed::Episode.new("Game of Thrones", 6, 9).page_url # #=> "http://www.addic7ed.com/serie/Game_of_Thrones/6/9/8" - def page_url(language) + def page_url(language = nil) + return localized_url(0) if language.nil? localized_urls[language] end @@ -103,8 +104,8 @@ def url_encoded_showname @url_encoded_showname ||= URLEncodeShowName.call(showname) end - def localized_url(lang) - "http://www.addic7ed.com/serie/#{url_encoded_showname}/#{season}/#{episode}/#{lang}" + def localized_url(lang_id) + "http://www.addic7ed.com/serie/#{url_encoded_showname}/#{season}/#{episode}/#{lang_id}" end def languages_hash(&block) diff --git a/spec/lib/addic7ed/models/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb index 36191aa..a9cec2e 100644 --- a/spec/lib/addic7ed/models/episode_spec.rb +++ b/spec/lib/addic7ed/models/episode_spec.rb @@ -82,7 +82,15 @@ episode.page_url(:fr) end - it "raises LanguageNotSupported given an unsupported language code" do - expect { episode.page_url(:aa) }.to raise_error Addic7ed::LanguageNotSupported + context "when given an unsupported language code" do + it "raises LanguageNotSupported" do + expect { episode.page_url(:aa) }.to raise_error Addic7ed::LanguageNotSupported + end + end + + context "when not given a language code" do + it "returns the URL of the page with subtitles from all languages" do + expect(episode.page_url).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/0" + end end end From f76db564c81a394c4021c13564337a737c65c0d4 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 22 Aug 2017 22:58:51 +0200 Subject: [PATCH 71/89] Make filtering methods of SubtitlesCollection return a SubtitlesCollection again (so that they stay chainable) --- lib/addic7ed/models/subtitles_collection.rb | 6 ++++++ spec/lib/addic7ed/models/subtitles_collection_spec.rb | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/addic7ed/models/subtitles_collection.rb b/lib/addic7ed/models/subtitles_collection.rb index 9494733..93bce57 100644 --- a/lib/addic7ed/models/subtitles_collection.rb +++ b/lib/addic7ed/models/subtitles_collection.rb @@ -71,5 +71,11 @@ def completed def most_popular sort_by(&:downloads).last end + + private + + def select + self.class.new(super) + end end end diff --git a/spec/lib/addic7ed/models/subtitles_collection_spec.rb b/spec/lib/addic7ed/models/subtitles_collection_spec.rb index 061ebba..fd5b185 100644 --- a/spec/lib/addic7ed/models/subtitles_collection_spec.rb +++ b/spec/lib/addic7ed/models/subtitles_collection_spec.rb @@ -23,6 +23,10 @@ subject { described_class.new([compatible_subtitle, incompatible_subtitle]) } + it "returns an Addic7ed::SubtitlesCollection" do + expect(subject.compatible_with("group")).to be_a described_class + end + it "uses Addic7ed::CheckCompatibility" do subject.compatible_with("group") expect(Addic7ed::CheckCompatibility).to have_received(:call).twice @@ -43,6 +47,10 @@ subject { described_class.new([completed_subtitle, incomplete_subtitle]) } + it "returns an Addic7ed::SubtitlesCollection" do + expect(subject.completed).to be_a described_class + end + it "keeps completed subtitles" do expect(subject.completed).to include completed_subtitle end From 086870d52ffe29a9f59487025d561c06dfc6096e Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Wed, 23 Aug 2017 09:00:46 +0200 Subject: [PATCH 72/89] Stop overriding select behaviour in SubtitlesCollection --- lib/addic7ed/models/subtitles_collection.rb | 8 ++++---- spec/lib/addic7ed/models/subtitles_collection_spec.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/addic7ed/models/subtitles_collection.rb b/lib/addic7ed/models/subtitles_collection.rb index 93bce57..c07166c 100644 --- a/lib/addic7ed/models/subtitles_collection.rb +++ b/lib/addic7ed/models/subtitles_collection.rb @@ -37,7 +37,7 @@ def initialize(subtitles = []) # #=> [#, #] def compatible_with(group) - select { |subtitle| CheckCompatibility.call(subtitle, group) } + chainable(select { |subtitle| CheckCompatibility.call(subtitle, group) }) end # Returns only completed subtitles. @@ -53,7 +53,7 @@ def compatible_with(group) # #=> [#] def completed - select(&:completed?) + chainable(select(&:completed?)) end # Returns the most downloaded subtitle. @@ -74,8 +74,8 @@ def most_popular private - def select - self.class.new(super) + def chainable(array) + self.class.new(array) end end end diff --git a/spec/lib/addic7ed/models/subtitles_collection_spec.rb b/spec/lib/addic7ed/models/subtitles_collection_spec.rb index fd5b185..5192aa5 100644 --- a/spec/lib/addic7ed/models/subtitles_collection_spec.rb +++ b/spec/lib/addic7ed/models/subtitles_collection_spec.rb @@ -23,7 +23,7 @@ subject { described_class.new([compatible_subtitle, incompatible_subtitle]) } - it "returns an Addic7ed::SubtitlesCollection" do + it "is chainable" do expect(subject.compatible_with("group")).to be_a described_class end @@ -47,7 +47,7 @@ subject { described_class.new([completed_subtitle, incomplete_subtitle]) } - it "returns an Addic7ed::SubtitlesCollection" do + it "is chainable" do expect(subject.completed).to be_a described_class end From a2080913b1d7570c179e66f3601edde87816b460 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 30 Dec 2017 00:32:25 +0100 Subject: [PATCH 73/89] Major refactor of models and parsing Parsing is now based on the multi-languages page of the episode to prevent random behaviour when no subtitle is found for a given language. Parsing has been refactored for code clarity (more readable, more SRP), it is now closer to the webpage DOM hierarchy, it better supports HI (not only the absence of HI) and it adds support for subtitles marked as corrected. The `Search` object has been removed in favour of a more intuitive class hierarchy. This is a major breakthrough on the road to 4.0 :tada: --- README.md | 78 ++++++++++++++- TODO.md | 11 ++- lib/addic7ed/models/episode.rb | 96 ++++++++---------- lib/addic7ed/models/search.rb | 92 ------------------ lib/addic7ed/models/subtitle.rb | 23 +++-- lib/addic7ed/models/subtitles_collection.rb | 17 ++++ lib/addic7ed/services/download_subtitle.rb | 2 + lib/addic7ed/services/parse_page.rb | 4 +- lib/addic7ed/services/parse_subtitle.rb | 81 ---------------- lib/addic7ed/services/parse_subtitle_node.rb | 33 +++++++ .../parse_subtitle_node_child_fields.rb | 52 ++++++++++ .../parse_subtitle_node_root_fields.rb | 42 ++++++++ spec/lib/addic7ed/models/episode_spec.rb | 97 ++++--------------- spec/lib/addic7ed/models/search_spec.rb | 41 -------- .../models/subtitles_collection_spec.rb | 22 +++++ .../services/parse_subtitle_node_spec.rb | 7 ++ .../addic7ed/services/parse_subtitle_spec.rb | 7 -- 17 files changed, 334 insertions(+), 371 deletions(-) delete mode 100644 lib/addic7ed/models/search.rb delete mode 100644 lib/addic7ed/services/parse_subtitle.rb create mode 100644 lib/addic7ed/services/parse_subtitle_node.rb create mode 100644 lib/addic7ed/services/parse_subtitle_node_child_fields.rb create mode 100644 lib/addic7ed/services/parse_subtitle_node_root_fields.rb delete mode 100644 spec/lib/addic7ed/models/search_spec.rb create mode 100644 spec/lib/addic7ed/services/parse_subtitle_node_spec.rb delete mode 100644 spec/lib/addic7ed/services/parse_subtitle_spec.rb diff --git a/README.md b/README.md index d6537c2..666f530 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,83 @@ A Ruby API for [Addic7ed](http://www.addic7ed.com), the best TV subtitles community in the world. -## Usage +## Installation -> :books: This needs to be written... +Add this line to your application's `Gemfile`: + +```ruby +gem "addic7ed" +``` + +Then execute: + +```shell +$ bundle +``` + +## Usage examples + +> :books: Check out the [API reference](http://www.rubydoc.info/github/michaelbaudino/addic7ed-ruby) for full documentation :books: + +### `Addic7ed::Episode` + +An `Episode` object represents an episode with a show name, season number and episode number: + +```ruby +episode = Addic7ed::Episode.new(show: "Game of Thrones", season: 6, number: 9) +#=> # +``` + +It provides a `subtitles` method that returns all available subtitles for this episode: + +```ruby +episode.subtitles +#=> #, +# #, +# # +# ] +``` + +### `Addic7ed::SubtitlesCollection` + +A `SubtitlesCollection` is an enumerable class that provides several filtering methods: +* `compatible_with(group)` which returns only subtitles compatible with the given `group` releases +* `completed` which returns only completed subtitles +* `for_language(language)` which returns only subtitles in the given `language` +* `most_popular` which returns the most downloaded subtitle + +Those methods are chainable, which lets you, for example: + +* select subtitles completed and compatible with a given release group: + + ```ruby + good_subtitles = episode.subtitles.completed.compatible_with("KILLERS") + ``` + +* find the most popular subtitle among those: + + ```ruby + best_subtitle = good_subtitles.most_popular + ``` + +A `SubtitlesCollection` instance can be filtered using any method from `Enumerable`, including your good friends `each`, `map`, `select`, `reject`, `find`, `group_by`, `any?`, `count`, `inject`, `sort`, `reduce`, ... + +### `Addic7ed::VideoFile` + +The `VideoFile` class lets you extract and guess relevant information from a video file name: + +```ruby +video = Addic7ed::VideoFile.new("~/Downloads/Game.of.Thrones.S06E09.720p.HDTV.x264-AVS[eztv].mkv") +#=> Guesses for ~/Downloads/Game.of.Thrones.S06E09.720p.HDTV.x264-AVS[eztv].mkv: +# show: Game.of.Thrones +# season: 6 +# episode: 9 +# tags: ["720P", "HDTV", "X264"] +# group: AVS +# distribution: EZTV +``` ## Notes diff --git a/TODO.md b/TODO.md index fd62b4f..f45c514 100644 --- a/TODO.md +++ b/TODO.md @@ -15,15 +15,18 @@ Here is a list of future features I'd like to implement. * [x] refactor how `Episode` holds `Subtitle`s * [x] rename `ShowList` and make it a service object * [x] write code documentation -* [x] Configure GitHub hook for [RubyDoc](http://www.rubydoc.info) +* [x] configure GitHub hook for [RubyDoc](http://www.rubydoc.info) * [x] add `bin/console` * [x] remove `bin/addic7ed` * [x] use symbols rather than strings for languages * [x] add Rubocop * [x] move user agents and languages to a config file -* [x] Add a Bundler post-install message to notify users that this gem is no longer providing a CLI -* [ ] update README to include only API stuff -* [ ] move CLI to a separate gem (and use Thor or similar) +* [x] add a Bundler post-install message to notify users that this gem is no longer providing a CLI +* [x] switch to using the language-neutral page +* [x] update README to include only API stuff +* [ ] add support for MRI 2.5 +* [ ] move CLI to a separate gem (including `DownloadSubtitle`) using Thor +* [ ] Use `HTTParty` or `Faraday` instead of custom HTTP download code * [ ] move all `to_s` and `to_inspect` methods to the CLI * [ ] update links/badges to the `full-rewrite` branch in `README.md` to use `master` * [ ] release `4.0` :champagne: diff --git a/lib/addic7ed/models/episode.rb b/lib/addic7ed/models/episode.rb index 285c5f3..f358a41 100644 --- a/lib/addic7ed/models/episode.rb +++ b/lib/addic7ed/models/episode.rb @@ -6,80 +6,38 @@ module Addic7ed # Represents a TV show episode. # - # @attr showname [String] TV show name. + # @attr show [String] TV show name. # @attr season [Numeric] Season number. - # @attr episode [Numeric] Episode number in the season. + # @attr number [Numeric] Episode number in the season. class Episode - attr_reader :showname, :season, :episode + attr_reader :show, :season, :number # Creates a new instance of {Episode}. # - # @param showname [String] TV show name, as extracted from the video file name + # @param show [String] TV show name # (_e.g._ both +"Game.of.Thrones"+ and +"Game of Thrones"+ are valid) # @param season [Numeric] Season number - # @param episode [Numeric] Episode number in the season + # @param number [Numeric] Episode number in the season # # @example - # Addic7ed::Episode.new("Game of Thrones", 6, 9) + # Addic7ed::Episode.new(show: "Game of Thrones", season: 6, number: 9) # #=> #nil, :ar=>nil, :az=>nil, ..., :th=>nil, :tr=>nil, :vi=>nil} + # # @show="Game.of.Thrones" # # > - def initialize(showname, season, episode) - @showname = showname - @season = season - @episode = episode - @subtitles = languages_hash { |code, _| { code => nil } } - end - - # Returns a list of all available {Subtitle}s for this {Episode} in the given +language+. - # - # @param language [String] Language code we want returned {Subtitle}s to be in. - # - # @return [Array] List of {Subtitle}s available on Addic7ed for the given +language+. - # - # @example - # Addic7ed::Episode.new("Game.of.Thrones", 3, 9).subtitles(:fr) - # #=> [ - # # #, - # # #, - # # # - # # ] - - def subtitles(language) - @subtitles[language] ||= Addic7ed::ParsePage.call(page_url(language)) + def initialize(show:, season:, number:) + @show = show + @season = season + @number = number end # Returns the URL of the Addic7ed webpage listing subtitles for this {Episode}. # If +language+ is given, it returns the URL of the page with subtitles for this language only. + # (_warning:_ despite requesting a language, Addic7ed may display subtitles in all languages + # if the requested language has no subtitle) # # @param language [String] Language code we want the webpage to list subtitles in. # @@ -87,6 +45,9 @@ def subtitles(language) # # @example # Addic7ed::Episode.new("Game of Thrones", 6, 9).page_url + # #=> "http://www.addic7ed.com/serie/Game_of_Thrones/6/9/0" + # + # Addic7ed::Episode.new("Game of Thrones", 6, 9).page_url(:fr) # #=> "http://www.addic7ed.com/serie/Game_of_Thrones/6/9/8" def page_url(language = nil) @@ -94,6 +55,25 @@ def page_url(language = nil) localized_urls[language] end + # Returns all available subtitles for this episode. + # + # @return [SubtitlesCollection] the collection of subtitles. + # + # @example + # Addic7ed::Episode.new("Game of Thrones", 6, 9) + # #=> #, + # # #, + # # # + # # ] + + def subtitles + @subtitles ||= SubtitlesCollection.new( + Addic7ed::ParsePage.call(page_url) + ) + end + private def localized_urls @@ -101,11 +81,11 @@ def localized_urls end def url_encoded_showname - @url_encoded_showname ||= URLEncodeShowName.call(showname) + @url_encoded_showname ||= URLEncodeShowName.call(show) end def localized_url(lang_id) - "http://www.addic7ed.com/serie/#{url_encoded_showname}/#{season}/#{episode}/#{lang_id}" + "http://www.addic7ed.com/serie/#{url_encoded_showname}/#{season}/#{number}/#{lang_id}" end def languages_hash(&block) diff --git a/lib/addic7ed/models/search.rb b/lib/addic7ed/models/search.rb deleted file mode 100644 index 635cff9..0000000 --- a/lib/addic7ed/models/search.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -module Addic7ed - # Represents a subtitle search for a +video_filename+ in a +language+ with - # multiple search +criterias+. - # - # @attr video_filename [String] Video file name we're searching subtitles for. - # @attr language [String] ISO code of language (_e.g._ "en", "fr", "es"). - # @attr criterias [Hash] List of search criterias as a {Hash}. - - class Search - attr_reader :video_filename, :language, :criterias - - # Creates a new instance of {Search}. - # - # Currently supported search criterias are: - # * +no_hi+: do not include hearing impaired subtitles (defaults to +false+) - # - # @param video_filename [String] Path to the video file for which we search subtitles - # (either relative or absolute path) - # @param language [String] ISO code of target subtitles language - # @param criterias [Hash] List of search criterias the subtitles must match - # (will be merged with default criterias as described above) - # - # @example - # Addic7ed::Search.new("Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv", :fr) - # #=> #false}, - # # @language=:fr, - # # @video_filename="Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv" - # # > - - def initialize(video_filename, language, criterias = {}) - @video_filename = video_filename - @language = language - @criterias = default_criterias.merge(criterias) - end - - # Returns the {VideoFile} object representing the video file we're searching subtitles for. - # - # @return [VideoFile] the {VideoFile} associated with this {Search} - - def video_file - @video_file ||= VideoFile.new(video_filename) - end - - # Returns the {Episode} object we're searching subtitles for. - # - # @return [Episode] the {Episode} associated with this {Search} - - def episode - @episode ||= Episode.new(video_file.showname, video_file.season, video_file.episode) - end - - # Returns the list of all available subtitles for this search, - # regardless of completeness, compatibility or search +criterias+. - # - # @return [SubtitlesCollection] all subtitles for the searched episode - - def all_subtitles - @all_subtitles ||= SubtitlesCollection.new(episode.subtitles(language)) - end - - # Returns the list of subtitles completed and compatible with the episode we're searching - # subtitles for, regardless of search +criterias+. - # - # @return [SubtitlesCollection] subtitles completed and compatible with the searched episode - - def matching_subtitles - @matching_subtitles ||= all_subtitles.completed.compatible_with(video_file.group) - end - - # Returns the best subtitle for the episode we're searching subtitles for. - # - # The best subtitle means the more popular (_i.e._ most downloaded) subtitle among completed, - # compatible subtitles for the episode we're searching subtitles for. - # - # @return [SubtitlesCollection] subtitles completed and compatible with the searched episode - - def best_subtitle - @best_subtitle ||= matching_subtitles.most_popular - end - - private - - def default_criterias - { - no_hi: false - } - end - end -end diff --git a/lib/addic7ed/models/subtitle.rb b/lib/addic7ed/models/subtitle.rb index 6c20937..c9bb3ef 100644 --- a/lib/addic7ed/models/subtitle.rb +++ b/lib/addic7ed/models/subtitle.rb @@ -16,13 +16,14 @@ module Addic7ed # @attr downloads [Numeric] Number of times this subtitle has been downloaded. # @attr comment [String] Comment manually added by the subtitle author/publisher # (usually related to extra-compatibilities or resync source). - # @attr hi [Boolean] Hearing-impaired support. + # @attr corrected [Boolean] Indicates if the subtitle has been corrected. + # @attr hi [Boolean] Indicates if the subtitle embeds hearing-impaired sequences. # @attr url [String] Download URL of actual subtitle file (warning: Addic7ed servers # won't serve a subtite file without a proper +Referer+ HTTP header which can be # retrieved from +episode.page_url+). class Subtitle - attr_reader :version, :language, :status, :source, :downloads, :comment, :hi + attr_reader :version, :language, :status, :source, :downloads, :comment, :corrected, :hi attr_accessor :url # Creates a new instance of {Subtitle} created using +options+, @@ -35,7 +36,8 @@ class Subtitle # * +source+: URL of website the subtitle was originally published on # * +downloads+: number of times the subtitle has been downloaded # * +comment+: manually added comment from the author or publisher - # * +hi+: hearing-impaired support + # * +corrected+: indicates if subtitle has been corrected + # * +hi+: indicates if subtitle embeds hearing-impaired sequences # * +url+: download URL for the subtitle file # # @param options [Hash] subtitle information as a {Hash} @@ -48,17 +50,19 @@ class Subtitle # source: "http://sous-titres.eu", # downloads: 10335, # comment: "works with 1080p.BATV", + # corrected: true, # hi: false, # url: "http://www.addic7ed.com/original/113643/4" # ) # #=> # @@ -68,6 +72,7 @@ def initialize(options = {}) @status = options[:status] @url = options[:url] @source = options[:source] + @corrected = options[:corrected] @hi = options[:hi] @downloads = options[:downloads].to_i || 0 @comment = NormalizeComment.call(options[:comment]) diff --git a/lib/addic7ed/models/subtitles_collection.rb b/lib/addic7ed/models/subtitles_collection.rb index c07166c..9a62db2 100644 --- a/lib/addic7ed/models/subtitles_collection.rb +++ b/lib/addic7ed/models/subtitles_collection.rb @@ -56,6 +56,23 @@ def completed chainable(select(&:completed?)) end + # Returns only subtitles for given +language_code+. + # + # @return [SubtitleCollection] Copy of collection filtered by +language+ + # + # @example + # english_subtitle = Addic7ed::Subtitle.new(language: "English") + # french_subtitle = Addic7ed::Subtitle.new(language: "French") + # collection = Addic7ed::SubtitlesCollection.new([english_subtitle, french_subtitle]) + # + # collection.for_language(:en) + # #=> [#] + + def for_language(language_code) + raise LanguageNotSupported unless LANGUAGES.key? language_code.to_sym + chainable(select { |subtitle| subtitle.language == LANGUAGES[language_code.to_sym][:name] }) + end + # Returns the most downloaded subtitle. # # @return [Subtitle] Subtitle of the collection with the more downloads diff --git a/lib/addic7ed/services/download_subtitle.rb b/lib/addic7ed/services/download_subtitle.rb index 5f362fa..ddb3707 100644 --- a/lib/addic7ed/services/download_subtitle.rb +++ b/lib/addic7ed/services/download_subtitle.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +# TODO: move to the CLI codebase and use HTTParty + module Addic7ed class DownloadSubtitle include Service diff --git a/lib/addic7ed/services/parse_page.rb b/lib/addic7ed/services/parse_page.rb index 2943d2a..f5e9370 100644 --- a/lib/addic7ed/services/parse_page.rb +++ b/lib/addic7ed/services/parse_page.rb @@ -16,7 +16,9 @@ def initialize(url) def call raise NoSubtitleFound unless subtitles_found? - subtitles_nodes.map { |subtitle_node| Addic7ed::ParseSubtitle.call(subtitle_node) } + subtitles_nodes.map do |subtitle_node| + Addic7ed::ParseSubtitleNode.call(subtitle_node) + end.flatten end private diff --git a/lib/addic7ed/services/parse_subtitle.rb b/lib/addic7ed/services/parse_subtitle.rb deleted file mode 100644 index eeea572..0000000 --- a/lib/addic7ed/services/parse_subtitle.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -require "oga" - -module Addic7ed - class ParseSubtitle - include Service - - attr_reader :subtitle_node - - FIELDS = %i[version language status url source hi downloads comment].freeze - - def initialize(subtitle_node) - @subtitle_node = subtitle_node - end - - def call - Addic7ed::Subtitle.new(extract_fields) - end - - private - - def extract_fields - FIELDS.map do |field| - { field => send(:"extract_#{field}") } - end.reduce(:merge) - end - - def extract_field(selector, options = { required: true }) - node = subtitle_node.css(selector).first - raise Addic7ed::ParsingError if options[:required] && node.nil? - yield node - end - - def extract_version - extract_field(".NewsTitle", &:text) - end - - def extract_language - extract_field(".language") do |node| - node.text.gsub(/\A\W*/, "").gsub(/[^\w\)]*\z/, "") - end - end - - def extract_status - extract_field("tr:nth-child(3) td:nth-child(4) b") do |node| - node.text.strip - end - end - - def extract_url - extract_field("a.buttonDownload:last-of-type") do |node| - "http://www.addic7ed.com#{node["href"]}" - end - end - - def extract_source - extract_field("tr:nth-child(3) td:first-child a", required: false) do |node| - node["href"] unless node.nil? - end - end - - def extract_hi - extract_field("tr:nth-child(4) td.newsDate img:last-of-type") do |node| - node.attribute("title") == "Hearing Impaired" - end - end - - def extract_downloads - extract_field("tr:nth-child(4) td.newsDate") do |node| - /(?\d*) Downloads/.match(node.text)[:downloads] - end - end - - def extract_comment - extract_field("tr:nth-child(2) td.newsDate") do |node| - node.text.gsub(/]+\>/i, "") - end - end - end -end diff --git a/lib/addic7ed/services/parse_subtitle_node.rb b/lib/addic7ed/services/parse_subtitle_node.rb new file mode 100644 index 0000000..3ac75fb --- /dev/null +++ b/lib/addic7ed/services/parse_subtitle_node.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "oga" + +module Addic7ed + class ParseSubtitleNode + include Service + + attr_reader :subtitle_node + + def initialize(subtitle_node) + @subtitle_node = subtitle_node + end + + def call + children_fields.map do |child_fields| + Addic7ed::Subtitle.new(root_fields.merge(child_fields)) + end + end + + private + + def root_fields + @root_fields ||= ParseSubtitleNodeRootFields.call(subtitle_node) + end + + def children_fields + @children_fields ||= subtitle_node.css(".language").map do |language_node| + ParseSubtitleNodeChildFields.call(language_node) + end + end + end +end diff --git a/lib/addic7ed/services/parse_subtitle_node_child_fields.rb b/lib/addic7ed/services/parse_subtitle_node_child_fields.rb new file mode 100644 index 0000000..71a7b42 --- /dev/null +++ b/lib/addic7ed/services/parse_subtitle_node_child_fields.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Addic7ed + class ParseSubtitleNodeChildFields + include Service + + attr_reader :language_node + + FIELDS = %i[language status url corrected hi downloads].freeze + + def initialize(language_node) + @language_node = language_node + end + + def call + FIELDS.map do |field| + { field => send(field) } + end.reduce(:merge) + end + + private + + def language + @language ||= language_node.text.gsub(/\A\W*/, "").gsub(/[^\w\)]*\z/, "") + end + + def status + @status ||= language_node.css("~ td b").first.text.strip + end + + def url + @url ||= "http://www.addic7ed.com" + language_node.css("~ td a.buttonDownload").first[:href] + end + + def corrected + return @corrected if defined? @corrected + @corrected = language_node.parent.css("~ tr td.newsDate img")[0][:title] == "Corrected" + end + + def hi + return @hi if defined? @hi + @hi = language_node.parent.css("~ tr td.newsDate img")[1][:title] == "Hearing Impaired" + end + + def downloads + @downloads ||= begin + text = language_node.parent.css("~ tr td.newsDate").text + /(?\d*) Downloads/.match(text)[:downloads] + end + end + end +end diff --git a/lib/addic7ed/services/parse_subtitle_node_root_fields.rb b/lib/addic7ed/services/parse_subtitle_node_root_fields.rb new file mode 100644 index 0000000..1040c2b --- /dev/null +++ b/lib/addic7ed/services/parse_subtitle_node_root_fields.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Addic7ed + class ParseSubtitleNodeRootFields + include Service + + attr_reader :subtitle_node + + FIELDS = %i[version source comment].freeze + + def initialize(subtitle_node) + @subtitle_node = subtitle_node + end + + def call + FIELDS.map do |field| + { field => send(field) } + end.reduce(:merge) + end + + private + + def version + return @version if defined? @version + @version = subtitle_node.css(".NewsTitle").first.text + end + + def source + return @source if defined? @source + @source = subtitle_node.css("tr:nth-child(3) td:first-child a").first.tap do |node| + break node[:href] if node + end + end + + def comment + return @comment if defined? @comment + @comment = subtitle_node.css("tr:nth-child(2) td.newsDate").first.tap do |node| + break node.text.strip if node + end + end + end +end diff --git a/spec/lib/addic7ed/models/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb index a9cec2e..2c19b52 100644 --- a/spec/lib/addic7ed/models/episode_spec.rb +++ b/spec/lib/addic7ed/models/episode_spec.rb @@ -2,95 +2,40 @@ require "spec_helper" -describe Addic7ed::Episode, "#subtitles" do +describe Addic7ed::Episode do let(:showname) { "The.Walking.Dead" } let(:season) { 3 } let(:episode_nbr) { 2 } - let(:episode) { described_class.new(showname, season, episode_nbr) } + let(:episode) { described_class.new(show: showname, season: season, number: episode_nbr) } - before do - %i[fr en it].each do |lang| - lang_id = Addic7ed::LANGUAGES[lang][:id] - stub_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/#{lang_id}") - .to_return File.new("spec/responses/walking-dead-3-2-#{lang_id}.http") - end - end - - it "returns an array of Addic7ed::Subtitle given valid episode and language" do - expect(episode.subtitles(:fr).size).to eq 4 - expect(episode.subtitles(:en).size).to eq 3 - expect(episode.subtitles(:it).size).to eq 1 - end - - it "raises LanguageNotSupported given an unsupported language code" do - expect { episode.subtitles(:aa) }.to raise_error Addic7ed::LanguageNotSupported - end - it "memoizes results to minimize network requests" do - 2.times { episode.subtitles(:en) } - expect( - a_request(:get, "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/1") - ).to have_been_made.times(1) - end - - context "when episode does not exist" do - let(:episode_nbr) { 42 } - - before do - stub_request( - :get, - "http://www.addic7ed.com/serie/The_Walking_Dead/3/42/8" - ).to_return File.new("spec/responses/walking-dead-3-42-8.http") + describe "#page_url(lang)" do + it "returns an episode page URL for given language" do + expect(episode.page_url(:fr)).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" + expect(episode.page_url(:es)).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4" end - it "raises EpisodeNotFound" do - expect do - described_class.new(showname, season, episode_nbr).subtitles(:fr) - end.to raise_error Addic7ed::EpisodeNotFound + it "uses URLEncodeShowName to generate the localized URLs" do + expect( + Addic7ed::URLEncodeShowName + ).to receive(:call).with(showname).and_return("The_Walking_Dead") + episode.page_url(:fr) end - end - context "when episode exists but has no subtitles" do - before do - stub_request( - :get, - "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/48" - ).to_return File.new("spec/responses/walking-dead-3-2-48.http") + context "when given an unsupported language code" do + it "raises LanguageNotSupported" do + expect { episode.page_url(:aa) }.to raise_error Addic7ed::LanguageNotSupported + end end - it "raises NoSubtitleFound" do - expect { episode.subtitles(:az) }.to raise_error Addic7ed::NoSubtitleFound + context "when not given a language code" do + it "returns the URL of the page with subtitles from all languages" do + expect(episode.page_url).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/0" + end end end -end -describe Addic7ed::Episode, "#page_url(lang)" do - let(:showname) { "The.Walking.Dead" } - let(:season) { 3 } - let(:episode_nbr) { 2 } - let(:episode) { described_class.new(showname, season, episode_nbr) } - - it "returns an episode page URL for given language" do - expect(episode.page_url(:fr)).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" - expect(episode.page_url(:es)).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/4" - end - - it "uses URLEncodeShowName to generate the localized URLs" do - expect( - Addic7ed::URLEncodeShowName - ).to receive(:call).with(showname).and_return("The_Walking_Dead") - episode.page_url(:fr) - end - - context "when given an unsupported language code" do - it "raises LanguageNotSupported" do - expect { episode.page_url(:aa) }.to raise_error Addic7ed::LanguageNotSupported - end - end - - context "when not given a language code" do - it "returns the URL of the page with subtitles from all languages" do - expect(episode.page_url).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/0" - end + describe "#subtitles" do + xit "should be tested" end end diff --git a/spec/lib/addic7ed/models/search_spec.rb b/spec/lib/addic7ed/models/search_spec.rb deleted file mode 100644 index 45b44ca..0000000 --- a/spec/lib/addic7ed/models/search_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe Addic7ed::Search do - let(:video_filename) { "Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv" } - let(:language) { "fr" } - let(:options) { {} } - let(:subtitles_fr) { [] } - let(:episode) { double(:episode) } - let(:video_file) do - double(:video_file, showname: "Game.of.Thrones", season: 6, episode: 9, group: "AVS") - end - - before do - allow(Addic7ed::VideoFile).to receive(:new).and_return(video_file) - allow(Addic7ed::Episode).to receive(:new).and_return(episode) - allow(episode).to receive(:subtitles).with(language).and_return(subtitles_fr) - end - - subject { described_class.new(video_filename, language, options) } - - describe "#video_file" do - it "returns the associated Addic7ed::VideoFile" do - subject.video_file - expect(Addic7ed::VideoFile).to have_received(:new).with(video_filename) - end - - it "is memoized" do - 2.times { subject.video_file } - expect(Addic7ed::VideoFile).to have_received(:new).exactly(1) - end - end - - describe "#episode" do - it "returns the associated Addic7ed::Episode object" do - subject.episode - expect(Addic7ed::Episode).to have_received(:new).with("Game.of.Thrones", 6, 9) - end - end -end diff --git a/spec/lib/addic7ed/models/subtitles_collection_spec.rb b/spec/lib/addic7ed/models/subtitles_collection_spec.rb index 5192aa5..3a61f4f 100644 --- a/spec/lib/addic7ed/models/subtitles_collection_spec.rb +++ b/spec/lib/addic7ed/models/subtitles_collection_spec.rb @@ -60,6 +60,28 @@ end end +describe Addic7ed::SubtitlesCollection, "#for_language(language)" do + let(:english_subtitle) { double(:english_subtitle, language: "English") } + let(:french_subtitle) { double(:french_subtitle, language: "French") } + let(:spanish_subtitle) { double(:spanish_subtitle, language: "Spanish") } + let(:collection) { described_class.new([english_subtitle, french_subtitle, spanish_subtitle]) } + + subject { collection.for_language(:en) } + + it "is chainable" do + expect(subject).to be_a described_class + end + + it "keeps requested language subtitles" do + expect(subject).to include english_subtitle + end + + it "filters out other languages subtitles" do + expect(subject).to_not include french_subtitle + expect(subject).to_not include spanish_subtitle + end +end + describe Addic7ed::SubtitlesCollection, "#most_popular" do let(:popular_subtitle) { double(:popular_subtitle, downloads: 100) } let(:unpopular_subtitle) { double(:popular_subtitle, downloads: 20) } diff --git a/spec/lib/addic7ed/services/parse_subtitle_node_spec.rb b/spec/lib/addic7ed/services/parse_subtitle_node_spec.rb new file mode 100644 index 0000000..3b444b0 --- /dev/null +++ b/spec/lib/addic7ed/services/parse_subtitle_node_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Addic7ed::ParseSubtitleNode do + xit "should be tested" +end diff --git a/spec/lib/addic7ed/services/parse_subtitle_spec.rb b/spec/lib/addic7ed/services/parse_subtitle_spec.rb deleted file mode 100644 index 63f0321..0000000 --- a/spec/lib/addic7ed/services/parse_subtitle_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe Addic7ed::ParseSubtitle do - it "should be tested" -end From 51591be9cfdec102c802222381bea4c8bf38239a Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 30 Dec 2017 00:37:09 +0100 Subject: [PATCH 74/89] Explicit some dependencies versions --- Gemfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 0cb79bb..abe0374 100644 --- a/Gemfile +++ b/Gemfile @@ -9,9 +9,9 @@ group :test do end platforms :rbx do - gem "iconv" - gem "json" - gem "psych" - gem "racc" - gem "rubysl" + gem "iconv", "~> 1.0" + gem "json", "~> 2.1" + gem "psych", "~> 2.2" + gem "racc", "~> 1.4" + gem "rubysl", "~> 2.2" end From 59c9de44f8329a706dc254fdf7517b2dfa947faf Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Sat, 30 Dec 2017 00:41:15 +0100 Subject: [PATCH 75/89] Add a few items to the TODO list --- TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.md b/TODO.md index f45c514..35854e9 100644 --- a/TODO.md +++ b/TODO.md @@ -24,6 +24,8 @@ Here is a list of future features I'd like to implement. * [x] add a Bundler post-install message to notify users that this gem is no longer providing a CLI * [x] switch to using the language-neutral page * [x] update README to include only API stuff +* [ ] maybe remove `SHOWS_URL` and `EPISODES_URL` +* [ ] mention documentation generation in `README` * [ ] add support for MRI 2.5 * [ ] move CLI to a separate gem (including `DownloadSubtitle`) using Thor * [ ] Use `HTTParty` or `Faraday` instead of custom HTTP download code From 681bf155b7105696de4d1754f43c039ef2cdf6fd Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 15 Jan 2018 13:03:59 +0100 Subject: [PATCH 76/89] Add support for MRI 2.5 --- .travis.yml | 1 + TODO.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9b79f1c..533452f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ rvm: - ruby-2.2 - ruby-2.3 - ruby-2.4 + - ruby-2.5 - rbx-3.84 - jruby before_install: diff --git a/TODO.md b/TODO.md index 35854e9..d60b0a3 100644 --- a/TODO.md +++ b/TODO.md @@ -26,7 +26,7 @@ Here is a list of future features I'd like to implement. * [x] update README to include only API stuff * [ ] maybe remove `SHOWS_URL` and `EPISODES_URL` * [ ] mention documentation generation in `README` -* [ ] add support for MRI 2.5 +* [x] add support for MRI 2.5 * [ ] move CLI to a separate gem (including `DownloadSubtitle`) using Thor * [ ] Use `HTTParty` or `Faraday` instead of custom HTTP download code * [ ] move all `to_s` and `to_inspect` methods to the CLI From cb85386d0b72fbec9a0006504ab27bd735990e4a Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 15 Jan 2018 13:12:40 +0100 Subject: [PATCH 77/89] Fix some Rubocop issues --- TODO.md | 2 +- addic7ed.gemspec | 12 ++++++------ lib/addic7ed/services/download_subtitle.rb | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/TODO.md b/TODO.md index d60b0a3..3e29b70 100644 --- a/TODO.md +++ b/TODO.md @@ -36,6 +36,6 @@ Here is a list of future features I'd like to implement. ## More features * [ ] refactor errors (to match Ruby errors hierarchy and maybe allow both bang-erroring and not-erroring versions of public API methods) -* [ ] refactor how HI works (allow both "no HI", "force HI" and "don't care") +* [x] refactor how HI works (allow both "no HI", "force HI" and "don't care") * [ ] add specs for HTML parsing * [ ] support registered users (to avoid download throttle) diff --git a/addic7ed.gemspec b/addic7ed.gemspec index cdeb88f..b657a81 100644 --- a/addic7ed.gemspec +++ b/addic7ed.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |s| s.files = `git ls-files -z lib LICENSE.md`.split("\x0") s.require_paths = ["lib"] s.license = "MIT" - s.post_install_message = <<-EOS + s.post_install_message = <<-POST_INSTALL_MESSAGE Important update if you're upgrading from 3.x to 4.x: @@ -24,14 +24,14 @@ Gem::Specification.new do |s| If you're using it as a Ruby API for Addic7ed, you're all good, ignore this message. If you're expecting the `addic7ed` binary, we've moved it to the `addic7ed-cli` gem. - EOS + POST_INSTALL_MESSAGE - s.add_development_dependency("rspec") - s.add_development_dependency("rake") - s.add_development_dependency("webmock") + s.add_development_dependency("inch") s.add_development_dependency("pry") + s.add_development_dependency("rake") + s.add_development_dependency("rspec") s.add_development_dependency("rubocop") - s.add_development_dependency("inch") + s.add_development_dependency("webmock") s.add_development_dependency("yard") s.add_runtime_dependency("oga", "~> 2.7") diff --git a/lib/addic7ed/services/download_subtitle.rb b/lib/addic7ed/services/download_subtitle.rb index ddb3707..7b91fea 100644 --- a/lib/addic7ed/services/download_subtitle.rb +++ b/lib/addic7ed/services/download_subtitle.rb @@ -41,20 +41,20 @@ def response request["User-Agent"] = USER_AGENTS.sample http.request(request) end - rescue + rescue StandardError raise DownloadError, "A network error occured" end def follow_redirection(location_header) # Addic7ed is serving redirection URL not-encoded, # but Ruby does not support it (see http://bugs.ruby-lang.org/issues/7396) - new_url = URI.escape(location_header) + new_url = CGI.escape(location_header) DownloadSubtitle.call(new_url, filename, url, redirect_count + 1) end def write(content) Kernel.open(filename, "w") { |f| f << content } - rescue + rescue StandardError raise DownloadError, "Cannot write to disk" end end From 9952328517f43f38c9c4620170ad229383aa5d9a Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 15 Jan 2018 13:15:39 +0100 Subject: [PATCH 78/89] Add specs placeholders --- .../services/parse_subtitle_node_child_fields_spec.rb | 7 +++++++ .../services/parse_subtitle_node_root_fields_spec.rb | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 spec/lib/addic7ed/services/parse_subtitle_node_child_fields_spec.rb create mode 100644 spec/lib/addic7ed/services/parse_subtitle_node_root_fields_spec.rb diff --git a/spec/lib/addic7ed/services/parse_subtitle_node_child_fields_spec.rb b/spec/lib/addic7ed/services/parse_subtitle_node_child_fields_spec.rb new file mode 100644 index 0000000..2eb1c7e --- /dev/null +++ b/spec/lib/addic7ed/services/parse_subtitle_node_child_fields_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Addic7ed::ParseSubtitleNodeChildFields do + xit "should be tested" +end diff --git a/spec/lib/addic7ed/services/parse_subtitle_node_root_fields_spec.rb b/spec/lib/addic7ed/services/parse_subtitle_node_root_fields_spec.rb new file mode 100644 index 0000000..26fe8f7 --- /dev/null +++ b/spec/lib/addic7ed/services/parse_subtitle_node_root_fields_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Addic7ed::ParseSubtitleNodeRootFields do + xit "should be tested" +end From 56ccb7c518c30500edb38b4358cd62ba33d50289 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 15 Jan 2018 13:19:42 +0100 Subject: [PATCH 79/89] Release 4.0 beta 7 --- lib/addic7ed/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/addic7ed/version.rb b/lib/addic7ed/version.rb index 33a725f..8855498 100644 --- a/lib/addic7ed/version.rb +++ b/lib/addic7ed/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Addic7ed - VERSION = "4.0.0-beta.6".freeze # rubocop:disable Style/RedundantFreeze + VERSION = "4.0.0-beta.7".freeze # rubocop:disable Style/RedundantFreeze end From ff53c4eb068f3b1d23f128d36d05627f07c39fb6 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Mon, 15 Jan 2018 13:53:53 +0100 Subject: [PATCH 80/89] Fix more Rubocop issues --- spec/lib/addic7ed/models/episode_spec.rb | 1 - spec/lib/addic7ed/models/subtitles_collection_spec.rb | 4 ++-- spec/lib/addic7ed/models/video_file_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/lib/addic7ed/models/episode_spec.rb b/spec/lib/addic7ed/models/episode_spec.rb index 2c19b52..05b89ca 100644 --- a/spec/lib/addic7ed/models/episode_spec.rb +++ b/spec/lib/addic7ed/models/episode_spec.rb @@ -8,7 +8,6 @@ let(:episode_nbr) { 2 } let(:episode) { described_class.new(show: showname, season: season, number: episode_nbr) } - describe "#page_url(lang)" do it "returns an episode page URL for given language" do expect(episode.page_url(:fr)).to eq "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" diff --git a/spec/lib/addic7ed/models/subtitles_collection_spec.rb b/spec/lib/addic7ed/models/subtitles_collection_spec.rb index 3a61f4f..d983f75 100644 --- a/spec/lib/addic7ed/models/subtitles_collection_spec.rb +++ b/spec/lib/addic7ed/models/subtitles_collection_spec.rb @@ -64,9 +64,9 @@ let(:english_subtitle) { double(:english_subtitle, language: "English") } let(:french_subtitle) { double(:french_subtitle, language: "French") } let(:spanish_subtitle) { double(:spanish_subtitle, language: "Spanish") } - let(:collection) { described_class.new([english_subtitle, french_subtitle, spanish_subtitle]) } + let(:all_subtitles) { [english_subtitle, french_subtitle, spanish_subtitle] } - subject { collection.for_language(:en) } + subject { described_class.new(all_subtitles).for_language(:en) } it "is chainable" do expect(subject).to be_a described_class diff --git a/spec/lib/addic7ed/models/video_file_spec.rb b/spec/lib/addic7ed/models/video_file_spec.rb index 5888ccc..f9d2ae5 100644 --- a/spec/lib/addic7ed/models/video_file_spec.rb +++ b/spec/lib/addic7ed/models/video_file_spec.rb @@ -154,7 +154,7 @@ describe "#inspect" do let(:expected) do - <<-EOS + <<-EXPECTED_OUTPUT Guesses for Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv: show: Showname season: 2 @@ -162,7 +162,7 @@ tags: ["720P", "HDTV", "X264"] group: GROUP distribution: DISTRIBUTION -EOS +EXPECTED_OUTPUT end subject { Addic7ed::VideoFile.new("Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv") } From 4b95279f33d335ee9e050b38b5a178b30bae1bcc Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 16 Jan 2018 11:17:49 +0100 Subject: [PATCH 81/89] More enhancements in README --- README.md | 84 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 666f530..fef70ee 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ A Ruby API for [Addic7ed](http://www.addic7ed.com), the best TV subtitles community in the world. +> :information_source: This is a Ruby wrapper only: if you're looking for the CLI tool, please see [addic7ed-cli](michaelbaudino/addic7ed-cli). + ## Installation Add this line to your application's `Gemfile`: @@ -24,7 +26,7 @@ Then execute: $ bundle ``` -## Usage examples +## Usage > :books: Check out the [API reference](http://www.rubydoc.info/github/michaelbaudino/addic7ed-ruby) for full documentation :books: @@ -37,7 +39,7 @@ episode = Addic7ed::Episode.new(show: "Game of Thrones", season: 6, number: 9) #=> # ``` -It provides a `subtitles` method that returns all available subtitles for this episode: +It provides a `subtitles` instance method that returns all available subtitles for this episode: ```ruby episode.subtitles @@ -49,9 +51,21 @@ episode.subtitles # ] ``` +It also provides a `page_url` instance method that returns the URL of the page listing all subtitles (or all those for a given `language`, if passed as argument) on Addic7ed: + +```ruby +episode.page_url #=> "http://www.addic7ed.com/serie/Game_of_Thrones/6/9/0" +episode.page_url(:fr) #=> "http://www.addic7ed.com/serie/Game_of_Thrones/6/9/8" +``` + +> :information_source: This is used internally to list available subtitles (see `subtitles` method above) but is also useful when later downloading a subtitle file because it can be used as a referrer (which is a required HTTP header to download subtitle files). + +> :boom: It raises `Addic7ed::LanguageNotSupported` if an unknown language code is passed to `page_url`. + ### `Addic7ed::SubtitlesCollection` A `SubtitlesCollection` is an enumerable class that provides several filtering methods: + * `compatible_with(group)` which returns only subtitles compatible with the given `group` releases * `completed` which returns only completed subtitles * `for_language(language)` which returns only subtitles in the given `language` @@ -71,32 +85,71 @@ Those methods are chainable, which lets you, for example: best_subtitle = good_subtitles.most_popular ``` -A `SubtitlesCollection` instance can be filtered using any method from `Enumerable`, including your good friends `each`, `map`, `select`, `reject`, `find`, `group_by`, `any?`, `count`, `inject`, `sort`, `reduce`, ... +> :boom: It raises `LanguageNotSupported` when `for_language` is called with an unknown/unsupported language code. + +> :bulb: A `SubtitlesCollection` instance can be filtered using any method from `Enumerable` (including your well-known friends `each`, `map`, `select`, `reject`, `find`, `group_by`, `any?`, `count`, `inject`, `sort`, `reduce`, ...). + +### `Addic7ed::Subtitle` + +A `Subtitle` object represents a subtitle file available on Addic7ed. It has several attributes: + +```ruby +subtitle = Addic7ed::Subtitle.new( + version: "Version KILLERS, 720p AVS, 0.00 MBs", + language: "French", + status: "Completed", + source: "http://sous-titres.eu", + downloads: 10335, + comment: "works with 1080p.BATV", + corrected: true, + hi: false, + url: "http://www.addic7ed.com/original/113643/4" +) + +subtitle.version #=> "Version KILLERS, 720p AVS, 0.00 MBs" +subtitle.language #=> "French" +subtitle.status #=> "Completed" +subtitle.source #=> "http://sous-titres.eu" +subtitle.downloads #=> 10335 +subtitle.comment #=> "works with 1080p.BATV" +subtitle.corrected #=> true +subtitle.hi #=> false +subtitle.url #=> "http://www.addic7ed.com/original/113643/4" +``` + +It also has a special `completed?` instance method that returns a boolean (`true` if `status` is `"Completed"`, `false` otherwise): + +```ruby +subtitle.completed? #=> true +``` ### `Addic7ed::VideoFile` -The `VideoFile` class lets you extract and guess relevant information from a video file name: +The `VideoFile` class lets you extract and guess relevant information from a video file name, then provides them as instance methods: ```ruby video = Addic7ed::VideoFile.new("~/Downloads/Game.of.Thrones.S06E09.720p.HDTV.x264-AVS[eztv].mkv") -#=> Guesses for ~/Downloads/Game.of.Thrones.S06E09.720p.HDTV.x264-AVS[eztv].mkv: -# show: Game.of.Thrones -# season: 6 -# episode: 9 -# tags: ["720P", "HDTV", "X264"] -# group: AVS -# distribution: EZTV + +video.showname #=> "Game.Of.Thrones" +video.season #=> 6 +video.episode #=> 9 +video.tags #=> ["720P", "HDTV", "X264"] +video.group #=> "AVS" +video.distribution #=> "EZTV" +video.basename #=> "Game.of.Thrones.S06E09.720p.HDTV.x264-AVS[eztv].mkv" ``` +> :boom: It raises `InvalidFilename` when it fails to infer any information from the file name :cry: + ## Notes -Addic7ed restricts the number of subtitle download to 15 per 24h (30 per 24h for registered users, and 55 for VIP users). Don't get mad, they have to pay for their servers, you know. Ho, and by the way, please, **please**: do not hammer their servers, play fair! +Addic7ed restricts the number of subtitles downloads to 15 per 24h (30 per 24h for registered users, and 55 for VIP users). Don't get mad, they have to pay for their servers, you know. Ho, and by the way, please, **please**: do not hammer their servers, play fair! ## Contribute -Feel free to submit a Pull Request, I'd be glad to review/merge it. +Feel free to submit a [pull request](michaelbaudino/addic7ed-ruby/pulls), I'd be glad to review/merge it. -Also, if you like the awesome work done by the Addic7ed team, please consider [donating to them](http://www.addic7ed.com) ! +Also, if you like the awesome work done by the Addic7ed team, please consider [donating to them](http://www.addic7ed.com) :moneybag: ### Supported Ruby versions @@ -107,6 +160,7 @@ This project [supports](https://github.com/michaelbaudino/addic7ed-ruby/blob/ful * Ruby 2.2 (MRI) * Ruby 2.3 (MRI) * Ruby 2.4 (MRI) +* Ruby 2.5 (MRI) * Rubinius * JRuby @@ -122,4 +176,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/michae ## License -The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT) (see `LICENSE.md` file). +This gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT) (see `LICENSE.md` file). From 9cbc8909069f12582c84c3aa6e1b098a84d73405 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 16 Jan 2018 11:20:04 +0100 Subject: [PATCH 82/89] Remove all to_s and inspect overrides --- TODO.md | 2 +- lib/addic7ed/models/subtitle.rb | 22 ---------------------- lib/addic7ed/models/video_file.rb | 27 --------------------------- 3 files changed, 1 insertion(+), 50 deletions(-) diff --git a/TODO.md b/TODO.md index 3e29b70..2c54d50 100644 --- a/TODO.md +++ b/TODO.md @@ -29,7 +29,7 @@ Here is a list of future features I'd like to implement. * [x] add support for MRI 2.5 * [ ] move CLI to a separate gem (including `DownloadSubtitle`) using Thor * [ ] Use `HTTParty` or `Faraday` instead of custom HTTP download code -* [ ] move all `to_s` and `to_inspect` methods to the CLI +* [x] remove all `to_s` and `to_inspect` methods * [ ] update links/badges to the `full-rewrite` branch in `README.md` to use `master` * [ ] release `4.0` :champagne: diff --git a/lib/addic7ed/models/subtitle.rb b/lib/addic7ed/models/subtitle.rb index c9bb3ef..e7a6e79 100644 --- a/lib/addic7ed/models/subtitle.rb +++ b/lib/addic7ed/models/subtitle.rb @@ -78,28 +78,6 @@ def initialize(options = {}) @comment = NormalizeComment.call(options[:comment]) end - # Returns a human-friendly readable string representing the {Subtitle} - # - # TODO: move to the CLI codebase - # - # @return [String] - # - # @example - # Addic7ed::Subtitle.new( - # version: "Version 720p AVS, 0.00 MBs", - # language: "French", - # status: "Completed", - # downloads: 42, - # url: "http://www.addic7ed.com/original/113643/4" - # ).to_s - # #=> "http://www.addic7ed.com/original/113643/4\t->\tAVS (French, Completed) [42 downloads]" - - def to_s - str = "#{url}\t->\t#{version} (#{language}, #{status}) [#{downloads} downloads]" - str += " (source #{source})" if source - str - end - # Completeness status of the {Subtitle}. # # @return [Boolean] Returns +true+ if {Subtitle} is complete, diff --git a/lib/addic7ed/models/video_file.rb b/lib/addic7ed/models/video_file.rb index 3024533..ad7f6fd 100644 --- a/lib/addic7ed/models/video_file.rb +++ b/lib/addic7ed/models/video_file.rb @@ -118,32 +118,5 @@ def distribution def basename @basename ||= File.basename(filename) end - - # Returns the video file name as passed to {#initialize}. - # - # @return [String] File name - # - # @example - # video_file = Addic7ed::VideoFile.new("../Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv") - # video_file.to_s #=> "../Game.of.Thrones.S06E09.720p.HDTV.x264-AVS.mkv" - - def to_s - filename - end - - # Returns a multiline, human-readable breakdown of the file name - # including all detected attributes (show name, season, episode, tags, ...). - # - # @return [String] a human-readable breakdown of the file name - - def inspect - "Guesses for #{filename}:\n" \ - " show: #{showname}\n" \ - " season: #{season}\n" \ - " episode: #{episode}\n" \ - " tags: #{tags}\n" \ - " group: #{group}\n" \ - " distribution: #{distribution}" - end end end From 435d97aa8aef7dd24646269185d42fdd4a6d6f78 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 16 Jan 2018 11:29:54 +0100 Subject: [PATCH 83/89] Add documentation generation instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, use emoji characters directly in README so they work within the Yard-generated doc 🎉 --- README.md | 42 +++++++++++++++++++++++++----------------- TODO.md | 2 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index fef70ee..a751242 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A Ruby API for [Addic7ed](http://www.addic7ed.com), the best TV subtitles community in the world. -> :information_source: This is a Ruby wrapper only: if you're looking for the CLI tool, please see [addic7ed-cli](michaelbaudino/addic7ed-cli). +> ℹ️ This is a Ruby wrapper only: if you're looking for the CLI tool, please see [addic7ed-cli](michaelbaudino/addic7ed-cli). ## Installation @@ -28,7 +28,7 @@ $ bundle ## Usage -> :books: Check out the [API reference](http://www.rubydoc.info/github/michaelbaudino/addic7ed-ruby) for full documentation :books: +> 📚 Check out the [API reference](http://www.rubydoc.info/github/michaelbaudino/addic7ed-ruby) for full documentation. ### `Addic7ed::Episode` @@ -58,9 +58,9 @@ episode.page_url #=> "http://www.addic7ed.com/serie/Game_of_Thrones/6/9/0" episode.page_url(:fr) #=> "http://www.addic7ed.com/serie/Game_of_Thrones/6/9/8" ``` -> :information_source: This is used internally to list available subtitles (see `subtitles` method above) but is also useful when later downloading a subtitle file because it can be used as a referrer (which is a required HTTP header to download subtitle files). +> ℹ️ This is used internally to list available subtitles (see `subtitles` method above) but is also useful when later downloading a subtitle file because it can be used as a referrer (which is a required HTTP header to download subtitle files). -> :boom: It raises `Addic7ed::LanguageNotSupported` if an unknown language code is passed to `page_url`. +> 💥 It raises `Addic7ed::LanguageNotSupported` if an unknown language code is passed to `page_url`. ### `Addic7ed::SubtitlesCollection` @@ -85,9 +85,9 @@ Those methods are chainable, which lets you, for example: best_subtitle = good_subtitles.most_popular ``` -> :boom: It raises `LanguageNotSupported` when `for_language` is called with an unknown/unsupported language code. +> 💥 It raises `LanguageNotSupported` when `for_language` is called with an unknown/unsupported language code. -> :bulb: A `SubtitlesCollection` instance can be filtered using any method from `Enumerable` (including your well-known friends `each`, `map`, `select`, `reject`, `find`, `group_by`, `any?`, `count`, `inject`, `sort`, `reduce`, ...). +> 💡 A `SubtitlesCollection` instance can be filtered using any method from `Enumerable` (including your well-known friends `each`, `map`, `select`, `reject`, `find`, `group_by`, `any?`, `count`, `inject`, `sort`, `reduce`, ...). ### `Addic7ed::Subtitle` @@ -139,19 +139,13 @@ video.distribution #=> "EZTV" video.basename #=> "Game.of.Thrones.S06E09.720p.HDTV.x264-AVS[eztv].mkv" ``` -> :boom: It raises `InvalidFilename` when it fails to infer any information from the file name :cry: +> 💥 It raises `InvalidFilename` when it fails to infer any information from the file name 😢 -## Notes +## Fair use Addic7ed restricts the number of subtitles downloads to 15 per 24h (30 per 24h for registered users, and 55 for VIP users). Don't get mad, they have to pay for their servers, you know. Ho, and by the way, please, **please**: do not hammer their servers, play fair! -## Contribute - -Feel free to submit a [pull request](michaelbaudino/addic7ed-ruby/pulls), I'd be glad to review/merge it. - -Also, if you like the awesome work done by the Addic7ed team, please consider [donating to them](http://www.addic7ed.com) :moneybag: - -### Supported Ruby versions +## Supported Ruby versions This project [supports](https://github.com/michaelbaudino/addic7ed-ruby/blob/full-rewrite/.travis.yml) the following Ruby versions/implementations: @@ -164,15 +158,29 @@ This project [supports](https://github.com/michaelbaudino/addic7ed-ruby/blob/ful * Rubinius * JRuby -:warning: Rubinius users may have to manually install [RubySL](https://github.com/RubySL) before they can use `addic7ed-ruby`: +⚠️ Rubinius users may have to manually install [RubySL](https://github.com/RubySL) before they can use `addic7ed-ruby`: ```shell $ gem install rubysl ``` +## Documentation + +The API reference can be generated locally using Yard: + +```shell +bundle exec yard +``` + +It is then available as static HTML files in the `doc` directory of this codebase (point your browser to `doc/index.html`). + ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/michaelbaudino/addic7ed-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct (see `CODE_OF_CONDUCT.md` file). +[Bug reports](michaelbaudino/addic7ed-ruby/issues) and [pull requests](michaelbaudino/addic7ed-ruby/pulls) are welcome on GitHub. + +This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct (see `CODE_OF_CONDUCT.md` file) 🤗 + +Also, if you like the awesome work done by the Addic7ed team, please consider [donating to them](http://www.addic7ed.com) 💰 ## License diff --git a/TODO.md b/TODO.md index 2c54d50..da955b8 100644 --- a/TODO.md +++ b/TODO.md @@ -25,7 +25,7 @@ Here is a list of future features I'd like to implement. * [x] switch to using the language-neutral page * [x] update README to include only API stuff * [ ] maybe remove `SHOWS_URL` and `EPISODES_URL` -* [ ] mention documentation generation in `README` +* [x] mention documentation generation in `README` * [x] add support for MRI 2.5 * [ ] move CLI to a separate gem (including `DownloadSubtitle`) using Thor * [ ] Use `HTTParty` or `Faraday` instead of custom HTTP download code From 4e73a96983d44248f64c0a08c159fccc95ece043 Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 16 Jan 2018 11:42:34 +0100 Subject: [PATCH 84/89] Remove obsolete configs related to fetching shows and episodes via AJAX --- TODO.md | 2 +- lib/addic7ed/common.rb | 2 -- lib/addic7ed/config.json | 4 ---- spec/lib/addic7ed/common_spec.rb | 8 -------- 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/TODO.md b/TODO.md index da955b8..b111dbd 100644 --- a/TODO.md +++ b/TODO.md @@ -24,7 +24,7 @@ Here is a list of future features I'd like to implement. * [x] add a Bundler post-install message to notify users that this gem is no longer providing a CLI * [x] switch to using the language-neutral page * [x] update README to include only API stuff -* [ ] maybe remove `SHOWS_URL` and `EPISODES_URL` +* [x] remove `SHOWS_URL` and `EPISODES_URL` * [x] mention documentation generation in `README` * [x] add support for MRI 2.5 * [ ] move CLI to a separate gem (including `DownloadSubtitle`) using Thor diff --git a/lib/addic7ed/common.rb b/lib/addic7ed/common.rb index 727eeb5..2b18bad 100644 --- a/lib/addic7ed/common.rb +++ b/lib/addic7ed/common.rb @@ -7,8 +7,6 @@ module Addic7ed CONFIG = JSON.parse(File.read(CONFIG_FILE), symbolize_names: true).freeze LANGUAGES = CONFIG[:languages].freeze USER_AGENTS = CONFIG[:user_agents].freeze - SHOWS_URL = CONFIG[:urls][:shows].freeze - EPISODES_URL = CONFIG[:urls][:episodes].freeze COMPATIBILITY_720P = { "LOL" => "DIMENSION", diff --git a/lib/addic7ed/config.json b/lib/addic7ed/config.json index fb1a683..15e5590 100644 --- a/lib/addic7ed/config.json +++ b/lib/addic7ed/config.json @@ -1,8 +1,4 @@ { - "urls": { - "shows": "http://www.addic7ed.com/ajax_getShows.php", - "episodes": "http://www.addic7ed.com/ajax_getEpisodes.php" - }, "languages": { "fr": { "id": 8, diff --git a/spec/lib/addic7ed/common_spec.rb b/spec/lib/addic7ed/common_spec.rb index 03c567d..982e4ca 100644 --- a/spec/lib/addic7ed/common_spec.rb +++ b/spec/lib/addic7ed/common_spec.rb @@ -3,14 +3,6 @@ require "spec_helper" describe Addic7ed do - it "defines SHOWS_URL" do - expect(Addic7ed::SHOWS_URL).to_not be_nil - end - - it "defines EPISODES_URL" do - expect(Addic7ed::EPISODES_URL).to_not be_nil - end - it "defines LANGUAGES" do expect(Addic7ed::LANGUAGES).to_not be_nil expect(Addic7ed::LANGUAGES).to include fr: { name: "French", id: 8 } From ad54fce36e6e1fce7bedc551e4c9bb8272b87e9c Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 16 Jan 2018 11:44:24 +0100 Subject: [PATCH 85/89] Remove specs forgotten in 9cbc8909069f12582c84c3aa6e1b098a84d73405 --- spec/lib/addic7ed/models/subtitle_spec.rb | 20 --------------- spec/lib/addic7ed/models/video_file_spec.rb | 28 --------------------- 2 files changed, 48 deletions(-) diff --git a/spec/lib/addic7ed/models/subtitle_spec.rb b/spec/lib/addic7ed/models/subtitle_spec.rb index 6b1d61e..4f0b3a9 100644 --- a/spec/lib/addic7ed/models/subtitle_spec.rb +++ b/spec/lib/addic7ed/models/subtitle_spec.rb @@ -19,26 +19,6 @@ end end -describe Addic7ed::Subtitle, "#to_s" do - let(:subtitle) do - Addic7ed::Subtitle.new( - version: "DIMENSION", - language: "fr", - status: "Completed", - url: "http://some.fancy.url", - source: "http://addic7ed.com", - downloads: "42" - ) - end - let(:expected) do - "http://some.fancy.url\t->\tDIMENSION (fr, Completed) [42 downloads] (source http://addic7ed.com)" # rubocop:disable Metrics/LineLength - end - - it "prints a human readable version" do - expect(subtitle.to_s).to eq(expected) - end -end - describe Addic7ed::Subtitle, "#completed?" do it "returns true when 'status' is 'Completed'" do expect(Addic7ed::Subtitle.new(status: "Completed").completed?).to be true diff --git a/spec/lib/addic7ed/models/video_file_spec.rb b/spec/lib/addic7ed/models/video_file_spec.rb index f9d2ae5..feaf7f9 100644 --- a/spec/lib/addic7ed/models/video_file_spec.rb +++ b/spec/lib/addic7ed/models/video_file_spec.rb @@ -143,32 +143,4 @@ expect(subject.basename).to eq "Showname.S02E01.720p.HDTV.x264-GROUP.mkv" end end - - describe "#to_s" do - subject { Addic7ed::VideoFile.new("/full/path/to/Showname.S02E01.720p.HDTV.x264-GROUP.mkv") } - - it "returns file name as a string" do - expect(subject.to_s).to eq "/full/path/to/Showname.S02E01.720p.HDTV.x264-GROUP.mkv" - end - end - - describe "#inspect" do - let(:expected) do - <<-EXPECTED_OUTPUT - Guesses for Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv: - show: Showname - season: 2 - episode: 1 - tags: ["720P", "HDTV", "X264"] - group: GROUP - distribution: DISTRIBUTION -EXPECTED_OUTPUT - end - - subject { Addic7ed::VideoFile.new("Showname.S02E01.720p.HDTV.x264-GROUP[DISTRIBUTION].mkv") } - - it "prints a human-readable detailed version" do - expect(subject.inspect).to eq expected.strip - end - end end From b523132f9680cde2c5517d93e74da621dace897b Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 16 Jan 2018 13:13:51 +0100 Subject: [PATCH 86/89] Remove DowloadSubtitle service: it belongs to the CLI --- TODO.md | 4 +- lib/addic7ed/services/download_subtitle.rb | 61 -------------- .../services/download_subtitle_spec.rb | 82 ------------------- 3 files changed, 2 insertions(+), 145 deletions(-) delete mode 100644 lib/addic7ed/services/download_subtitle.rb delete mode 100644 spec/lib/addic7ed/services/download_subtitle_spec.rb diff --git a/TODO.md b/TODO.md index b111dbd..46c108b 100644 --- a/TODO.md +++ b/TODO.md @@ -27,9 +27,9 @@ Here is a list of future features I'd like to implement. * [x] remove `SHOWS_URL` and `EPISODES_URL` * [x] mention documentation generation in `README` * [x] add support for MRI 2.5 -* [ ] move CLI to a separate gem (including `DownloadSubtitle`) using Thor -* [ ] Use `HTTParty` or `Faraday` instead of custom HTTP download code +* [x] remove `DownloadSubtitle` * [x] remove all `to_s` and `to_inspect` methods +* [ ] add tools to develop offline (at least, without relying on Addic7ed website) * [ ] update links/badges to the `full-rewrite` branch in `README.md` to use `master` * [ ] release `4.0` :champagne: diff --git a/lib/addic7ed/services/download_subtitle.rb b/lib/addic7ed/services/download_subtitle.rb deleted file mode 100644 index 7b91fea..0000000 --- a/lib/addic7ed/services/download_subtitle.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -# TODO: move to the CLI codebase and use HTTParty - -module Addic7ed - class DownloadSubtitle - include Service - - HTTP_REDIRECT_LIMIT = 8 - - attr_reader :url, :filename, :referer, :redirect_count - - def initialize(url, filename, referer, redirect_count = 0) - @url = url - @filename = filename - @referer = referer - @redirect_count = redirect_count - end - - def call - raise DownloadError, "Too many HTTP redirections" if redirect_count >= HTTP_REDIRECT_LIMIT - raise DailyLimitExceeded, "Daily limit exceeded" if download_limit_exceeded? - return follow_redirection(response["location"]) if response.is_a? Net::HTTPRedirection - write(response.body) - end - - private - - def uri - @uri ||= URI(url) - end - - def download_limit_exceeded? - !%r{^/downloadexceeded.php}.match(url).nil? - end - - def response - @response ||= Net::HTTP.start(uri.hostname, uri.port) do |http| - request = Net::HTTP::Get.new(uri.request_uri) - request["Referer"] = referer # Addic7ed requires the Referer to be correct - request["User-Agent"] = USER_AGENTS.sample - http.request(request) - end - rescue StandardError - raise DownloadError, "A network error occured" - end - - def follow_redirection(location_header) - # Addic7ed is serving redirection URL not-encoded, - # but Ruby does not support it (see http://bugs.ruby-lang.org/issues/7396) - new_url = CGI.escape(location_header) - DownloadSubtitle.call(new_url, filename, url, redirect_count + 1) - end - - def write(content) - Kernel.open(filename, "w") { |f| f << content } - rescue StandardError - raise DownloadError, "Cannot write to disk" - end - end -end diff --git a/spec/lib/addic7ed/services/download_subtitle_spec.rb b/spec/lib/addic7ed/services/download_subtitle_spec.rb deleted file mode 100644 index ff8b559..0000000 --- a/spec/lib/addic7ed/services/download_subtitle_spec.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -describe Addic7ed::DownloadSubtitle do - let(:url) { "http://www.addic7ed.com/original/68018/4" } - let(:referer) { "http://www.addic7ed.com/serie/The_Walking_Dead/3/2/8" } - let(:filename) { "The.Walking.Dead.S03E02.720p.HDTV.x264-EVOLVE.fr.srt" } - - subject { described_class.new(url, filename, referer, 0) } - - describe "#call" do - let!(:http_stub) do - stub_request(:get, url) - .to_return File.new("spec/responses/walking-dead-3-2-8_best_subtitle.http") - end - - before { allow_any_instance_of(described_class).to receive(:write) } - - it "fetches the subtitle" do - subject.call - expect(http_stub).to have_been_requested - end - - it "writes the subtitle to disk" do - subject.call - expect(subject).to have_received(:write) - end - - context "when the HTTP request returns a HTTP redirection" do - let!(:redirect_url) { "http://www.addic7ed.com/original/68018/4.redirected" } - let!(:http_stub) do - stub_request(:get, url).to_return File.new("spec/responses/basic_redirection.http") - end - let!(:redirect_stub) do - stub_request(:get, redirect_url) - .to_return File.new("spec/responses/walking-dead-3-2-8_best_subtitle.http") - end - - it "follows the redirection by calling itself with redirection URL" do - subject.call - expect(http_stub).to have_been_requested.once - expect(redirect_stub).to have_been_requested.once - end - - context "when it gets stuck in a redirection loop" do - let!(:redirect_stub) do - stub_request(:get, redirect_url) - .to_return File.new("spec/responses/basic_redirection.http") - end - - it "follows up to HTTP_REDIRECT_LIMIT redirections then raises a Addic7ed::DownloadError" do - expect(described_class).to receive(:new).exactly(9).times.and_call_original - expect { subject.call }.to raise_error Addic7ed::DownloadError - end - end - end - - context "when a network error occurs" do - let!(:http_stub) { stub_request(:get, url).to_timeout } - - it "raises Addic7ed::DownloadError" do - expect { subject.call }.to raise_error Addic7ed::DownloadError - end - end - end - - describe "#write(content)" do - it "creates a new file on disk" do - expect(Kernel).to receive(:open).with(filename, "w") - subject.send(:write, "some content") - end - - context "when an error occurs" do - before { allow(Kernel).to receive(:open).and_raise(IOError) } - - it "raises a Addic7ed::DownloadError error" do - expect { subject.send(:write, "some content") }.to raise_error Addic7ed::DownloadError - end - end - end -end From 6087568f70d00bf50701525a2b777cdc87bcf69a Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Tue, 16 Jan 2018 13:31:38 +0100 Subject: [PATCH 87/89] Fix a Rubocop complexity issue --- lib/addic7ed/models/subtitle.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/addic7ed/models/subtitle.rb b/lib/addic7ed/models/subtitle.rb index e7a6e79..34717f4 100644 --- a/lib/addic7ed/models/subtitle.rb +++ b/lib/addic7ed/models/subtitle.rb @@ -67,14 +67,11 @@ class Subtitle # # > def initialize(options = {}) - @version = NormalizeVersion.call(options[:version]) - @language = options[:language] - @status = options[:status] - @url = options[:url] - @source = options[:source] - @corrected = options[:corrected] - @hi = options[:hi] + %i[language status url source corrected hi].each do |opt| + instance_variable_set(:"@#{opt}", options[opt]) + end @downloads = options[:downloads].to_i || 0 + @version = NormalizeVersion.call(options[:version]) @comment = NormalizeComment.call(options[:comment]) end From ef6e2f652b226f508efd8437bc7945ec55fdfeff Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Thu, 18 Jan 2018 23:44:16 +0100 Subject: [PATCH 88/89] Add mocks for easy local (and eventually offline) development --- README.md | 30 +- TODO.md | 4 +- bin/console | 4 + mocks/got690.http | 2311 +++++++++++++++++++++++++++++++++++++++++++++ mocks/home.http | 893 ++++++++++++++++++ mocks/mocks.rb | 38 + 6 files changed, 3271 insertions(+), 9 deletions(-) create mode 100644 mocks/got690.http create mode 100644 mocks/home.http create mode 100644 mocks/mocks.rb diff --git a/README.md b/README.md index a751242..87cb9f6 100644 --- a/README.md +++ b/README.md @@ -164,23 +164,37 @@ This project [supports](https://github.com/michaelbaudino/addic7ed-ruby/blob/ful $ gem install rubysl ``` -## Documentation +## Contributing -The API reference can be generated locally using Yard: +[Bug reports](michaelbaudino/addic7ed-ruby/issues) and [pull requests](michaelbaudino/addic7ed-ruby/pulls) are welcome on GitHub. + +This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct (see `CODE_OF_CONDUCT.md` file) 🤗 + +Also, if you like the awesome work done by the Addic7ed team, please consider [donating to them](http://www.addic7ed.com) 💰 + +### Local development + +When developing locally, we provide a handy REPL console with all the code already loaded: ```shell -bundle exec yard +bin/console ``` -It is then available as static HTML files in the `doc` directory of this codebase (point your browser to `doc/index.html`). +There's even a mode where you don't rely on Addic7ed to be reachable (all network connections are blocked, and a page is mocked to work on): -## Contributing +```shell +bin/console --mock +``` -[Bug reports](michaelbaudino/addic7ed-ruby/issues) and [pull requests](michaelbaudino/addic7ed-ruby/pulls) are welcome on GitHub. +### Documentation -This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct (see `CODE_OF_CONDUCT.md` file) 🤗 +The API reference can be generated locally using Yard: -Also, if you like the awesome work done by the Addic7ed team, please consider [donating to them](http://www.addic7ed.com) 💰 +```shell +bundle exec yard +``` + +It is then available as static HTML files in the `doc` directory of this codebase (point your browser to `doc/index.html`). ## License diff --git a/TODO.md b/TODO.md index 46c108b..4c1b5fc 100644 --- a/TODO.md +++ b/TODO.md @@ -29,7 +29,9 @@ Here is a list of future features I'd like to implement. * [x] add support for MRI 2.5 * [x] remove `DownloadSubtitle` * [x] remove all `to_s` and `to_inspect` methods -* [ ] add tools to develop offline (at least, without relying on Addic7ed website) +* [x] add tools to develop offline (at least, without relying on Addic7ed website) +* [ ] add a `download` instance method to `Subtitle` +* [ ] clean up `spec/responses` * [ ] update links/badges to the `full-rewrite` branch in `README.md` to use `master` * [ ] release `4.0` :champagne: diff --git a/bin/console b/bin/console index 1a70cf0..180457b 100755 --- a/bin/console +++ b/bin/console @@ -10,4 +10,8 @@ $LOAD_PATH.unshift File.expand_path("lib", APP_ROOT) require "addic7ed" +if ARGV.delete "--mock" + Dir[File.join(__dir__, "..", "mocks", "mocks.rb")].each { |file| require file } +end + Pry.start diff --git a/mocks/got690.http b/mocks/got690.http new file mode 100644 index 0000000..39221eb --- /dev/null +++ b/mocks/got690.http @@ -0,0 +1,2311 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Fri, 29 Dec 2017 15:30:44 GMT +Content-Type: text/html +Transfer-Encoding: chunked +Connection: keep-alive +X-Powered-By: PHP/5.3.3 +Set-Cookie: PHPSESSID=29c96nr0u9jm90d8l53708kav2; path=/ +Expires: Thu, 19 Nov 1981 08:52:00 GMT +Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 +Pragma: no-cache + + + + + + + + Download Game of Thrones - 06x09 - Battle of the Bastards subtitles from the source! - Addic7ed.com + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + +
Addic7ed.com - Quality Subtitles for TV Shows and movies
+

Download free subtitles for TV Shows and Movies.  +

+
+ +
+ + + Twitter + IRC + + +
+ + +
+ 60% +
+
+
+
+
+
+ +
+ + + + + + + + +
+ Clear Search Terms +Quick search +   + + +   + + +   +  
+
+Search subtitle   +   +
+
+ +
+
+ +
Loading...
+ +
+ + + + +
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Game of Thrones - 06x09 - Battle of the Bastards Subtitle +
+
+
+ + + + +
+
+
 Game of Thrones, Season 6, Episode 9 subtitles + +

Game of Thrones - 9.5/10
Episode list and air dates +
+
 Viewed +
+
+ 00:57:14 + + 209192 + +
  Multi Download + + + + +
+
 
 
+
+

+ + Sort versions alphabetically by language - Filter Language:
+

+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version 720p.HDTV.x264-AVS, 0.00 MBs  + + New translation uploaded by Luretrix2k 557 days ago + +
+ +
    + Danish + Completed + + + Download +
0 times edited · 3878 Downloads · 352 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version AVS, 0.00 MBs + + New translation uploaded by (del_131626) 556 days ago + +
+ Vertaling: +Quality over Quantity (QoQ) Releases +
    + Dutch + Completed + + + Download +
0 times edited · 6463 Downloads · 362 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version DON, 0.00 MBs + + New translation uploaded by (del_131626) 556 days ago + +
+ Vertaling: +Quality over Quantity (QoQ) Releases +
    + Dutch + Completed + + + Download +
0 times edited · 935 Downloads · 388 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEB-DL.NTb, 0.00 MBs  + + New translation uploaded by (del_131626) 556 days ago + +
+ Vertaling: +Quality over Quantity (QoQ) Releases +
    + Dutch + Completed + + + Download +
0 times edited · 6262 Downloads · 362 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version BluRay.RETAiL, 0.00 MBs  + + New translation uploaded by assenza 416 days ago + +
+ Retail door CyTSuNee +
    + Dutch + Completed + + + Download +
0 times edited · 1142 Downloads · 411 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEB-DL.RETAiL, 2292.77 MBs  + + New translation uploaded by assenza 388 days ago + +
+ Retail door CyTSuNee +
    + Dutch + Completed + + + Download +
0 times edited · 1305 Downloads · 411 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version 1080p.HDTV.x264-BATV[rartv], 0.00 MBs  + + New translation uploaded by mj1973 557 days ago + +
+ Vertaling Belgowul...Resync en langer duurtijd om te lezen:MJ1973..ook voor versies,Game of Thrones.S06E09.HDTV.XviD-...Game.of.Thrones.S06E09.HDTV.x264-AVS...Game of Thrones.S06E09.720p.HDTV.x264-AVS...Game of Thrones.S06E09.480p.HDTV.x264-mSD...Game of Thrones - 06x09 - Battle of the Bastards.1080p.HDTV......Game of Thrones.S06E09.HDTV.x264-KiLLERSrnGame.of.Thrones.S06E09.720p.HDTV.x264-AVS...Game.of.Thrones.S06E09.720p.HDTV.x264-KILLERS +
    + Dutch + Completed + + + Download +
0 times edited · 8073 Downloads · 407 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version KILLERS, 0.00 MBs  + + New translation uploaded by honeybunny 557 days ago + +
+ works with 720p.AVS/1080p.BATV +
    + English + Completed + + + Download +
0 times edited · 74377 Downloads · 569 sequences + view & edit 
    + French + Completed + + + Download +
152 times edited · 14646 Downloads · 569 sequences + view & edit edited 200 days ago +
 
  + Translated on Addic7ed.com by + ZeBlinkMaster (92.1%), Scarlaty (7.9%),
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEBRip-NoGrp, 0.00 MBs  + + New translation uploaded by GoldenBeard 557 days ago + +
+ Re-sync of honeybunny's - Duration: 01:08:17 +
    + English + Completed + + + Download +
0 times edited · 5533 Downloads · 569 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEB-DL, 2180.00 MBs  + + New translation uploaded by jaideejung007 556 days ago + +
+ Resync of KILLERS (honeybunny) version | WORK WITH "WEB-DL-NTb" | CONTAINS HARDCODED LINES _ FOR TRANSLATORS +
    + English + Completed + + + Download +
0 times edited · 6039 Downloads · 569 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version KILLERS, 0.00 MBs  + + New translation uploaded by honeybunny 557 days ago + +
+ works with 720p.AVS/1080p.BATV +
    + English + Completed + + + Download +
0 times edited · 31417 Downloads · 457 sequences + view & edit 
    + German + Completed + + + Download +
120 times edited · 1282 Downloads · 457 sequences + view & edit edited 284 days ago +
 
  + Translated on Addic7ed.com by + nesium (51.0%), emeline-whovian (3.1%), horstfujimoto (0.9%), Bujemeister (7.0%), toran2002 (7.7%), XXBrain (30.4%),
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEBRip-NoGrp, 0.00 MBs  + + New translation uploaded by GoldenBeard 557 days ago + +
+ Re-sync of honeybunny's - Duration: 01:08:17 +
    + English + Completed + + + Download +
0 times edited · 3168 Downloads · 457 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version BATV, 0.00 MBs  + + New translation uploaded by IuliusMonea 557 days ago + +
+ +
    + English + Completed + + + Download +
0 times edited · 9285 Downloads · 404 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version CtrlHD, 5345.28 MBs  + + New translation uploaded by Faisal.B 557 days ago + +
+ Resync from honeybunny's subs! Works with DON! +
    + English + Completed + + + Download +
0 times edited · 1876 Downloads · 456 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEB-DL, 2180.00 MBs  + + New translation uploaded by jaideejung007 556 days ago + +
+ Resync of KILLERS (honeybunny) version | WORK WITH "WEB-DL-NTb" | CONTAINS HARDCODED LINES _ FOR TRANSLATORS +
    + English + Completed + + + Download +
0 times edited · 9551 Downloads · 457 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version KILLERS-(No italics-Cuvio), 0.00 MBs  + + New translation uploaded by Skymist 557 days ago + +
+ Non-hearing-impaired, no italics or special characters. +
    + English + Completed + + + Download +
0 times edited · 2981 Downloads · 459 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version KILLERS - 720p AVS, 0.00 MBs  + + New translation uploaded by Hellie 557 days ago + +
+ DVDRip.Z2 // Adaptation : Clotilde Maville +
    + French + Completed + + + Download +
0 times edited · 11888 Downloads · 452 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version 1080p.DON, 0.00 MBs  + + New translation uploaded by FABIOBO 557 days ago + +
+ DVDRip.Z2 // Adaptation : Clotilde Maville +
    + French + Completed + + + Download +
0 times edited · 2188 Downloads · 452 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEB-DL-NTb, 0.00 MBs + + New translation uploaded by derrick45 555 days ago + +
+ DVDRip.Z2 // Adaptation : Clotilde Maville +
    + French + Completed + + + Download +
0 times edited · 1885 Downloads · 452 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version BATV, 0.00 MBs  + + New translation uploaded by IuliusMonea 556 days ago + +
+ SUB by pfefferkuchen & Godmode +
    + German + Completed + + + Download +
0 times edited · 143 Downloads · 403 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version CtrlHD, 0.00 MBs  + + New translation uploaded by IuliusMonea 556 days ago + +
+ SUB by pfefferkuchen & Godmode +
    + German + Completed + + + Download +
0 times edited · 87 Downloads · 403 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version KILLERS, 0.00 MBs  + + New translation uploaded by Black-Rabbit 557 days ago + +
+ works with 720p.AVS/1080p.BATV +
    + Italian + Completed + + + Download +
0 times edited · 1139 Downloads · 407 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEB-DL, 0.00 MBs  + + New translation uploaded by Kal-Earth2 556 days ago + +
+ +
    + Italian + Completed + + + Download +
0 times edited · 207 Downloads · 408 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version 1080i.CtrlHD, 0.00 MBs  + + New translation uploaded by markos988 550 days ago + +
+ +
    + Italian + Completed + + + Download +
0 times edited · 62 Downloads · 436 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version 720p.HDTV.x264-AVS, 0.00 MBs  + + New translation uploaded by Luretrix2k 557 days ago + +
+ +
    + Norwegian + Completed + + + Download +
0 times edited · 754 Downloads · 288 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version Bluray, 0.00 MBs  + + New translation uploaded by btsix 313 days ago + +
+ +
    + Portuguese (Brazilian) + Completed + + + Download +
0 times edited · 161 Downloads · 443 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version WEB-DL, 0.00 MBs  + + New translation uploaded by btsix 313 days ago + +
+ Por: Equipe LegendasTV +
    + Portuguese (Brazilian) + Completed + + + Download +
0 times edited · 134 Downloads · 447 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version AVS-KILLERS-mSD-AFG, 0.00 MBs + + New translation uploaded by azure91 557 days ago + +
+ LEGENDA HBO BR | @EquipePa | Funciona com: AVS / KILLERS / mSD / AFG +
    + Portuguese (Brazilian) + Completed + + + Download +
0 times edited · 1714 Downloads · 465 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + + +
Version KILLERS, 0.00 MBs  + + New translation uploaded by volverine73 557 days ago + +
+ works with / работает с 720p AVS; 1080p BATV. +Перевели Opel и Volverine +
    + Russian + Completed + + + original + most updated +
1 times edited · 154 Downloads · 450 sequences + view & edit edited 556 days ago +
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version 1080p.HDTV.BATV, 4382.00 MBs  + + New translation uploaded by torrfan 556 days ago + +
+ 7kingdoms' +
    + Russian + Completed + + + Download +
0 times edited · 147 Downloads · 450 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version 1080p.WEB-DL.NTb, 2293.00 MBs  + + New translation uploaded by torrfan 555 days ago + +
+ 7kingdoms' also relevant for 720p NTb +
    + Russian + Completed + + + Download +
0 times edited · 62 Downloads · 450 sequences + view & edit 
+
 
 
+
+
+ + + + + + + + + + + + + + + + +
 
  + + + + + + + + + + + + + + + + + +
Version BATV, 0.00 MBs  + + New translation uploaded by veselodum 557 days ago + +
+ 23.976 fps, without "Previously on Game of Thrones" segment. +
    + Ukrainian + Completed + + + Download +
0 times edited · 133 Downloads · 421 sequences + view & edit 
+
 
 
+
+

+

+

 

+
+ + +
+ +
+
+ Loading... +
+ +
+ +
+
+ + + + + + + + + + + + + + + + + +
 
  + + +  
 
+
+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TV Addic7edTV Popular ShowsTV UsefulTV Forums
Support Us
RSS Feeds
Premium Accounts
TV Tutorials
Frequently Asked Questions
What Are Subtitles
New Translation Tutorial
Upload a New Subtitle Tutorial
TV Stats
. +
+
+
+ + + + + + + +build time: 0.62222719192505
+ + + + + diff --git a/mocks/home.http b/mocks/home.http new file mode 100644 index 0000000..3673d84 --- /dev/null +++ b/mocks/home.http @@ -0,0 +1,893 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Fri, 29 Dec 2017 15:29:20 GMT +Content-Type: text/html +Transfer-Encoding: chunked +Connection: keep-alive +X-Powered-By: PHP/5.3.3 +Set-Cookie: PHPSESSID=1nasjbjmoclgseagk8466bm7e5; path=/ +Expires: Thu, 19 Nov 1981 08:52:00 GMT +Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 +Pragma: no-cache + + + + + + + + + + + + + + + + +Addic7ed.com - The source of latest TV subtitles + + + + + + + + + +

+ + + + + + + + + + + + + + + +
Addic7ed.com - Quality Subtitles for TV Shows and movies
+

Download free subtitles for TV Shows and Movies.  +

+
+ +
+ + + Twitter + IRC + + +
+ + +
+ 60% +
+
+
+
+
+
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
   
  65 days ago NewsWe're looking for syncers! Read our message: http://www.sub-talk.net/topic/6961-recruitment/ 
  86 days ago NewsYou can now support us monthly via Patreon: https://lc.cx/pGQm 
  124 days ago NewsIf you want to help us, please check this out: https://lc.cx/c2bs 
+
+
+ +
Loading...
+
+ + + + + +
+ Clear Search Terms +Quick search + + + +   + + +   +  
+
+Search subtitle   +   +
+
+ +
+ +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 New Releases
invisible
 Las Chicas Del Cable - 02x01 - Chapter 9: The Choice   Great News - 02x09 - Love Is Dead   Black Mirror (2011) - 04x01 - USS Callister
  English · uploaded by PopcornAWH   English · uploaded by louvette   English · uploaded by VitoSilans
 The Grand Tour - 02x04 - Unscripted   LA to Vegas - 01x01 - Pilot   Van Helsing - 02x12 - Crooked Falls
  English · uploaded by emeline-whovian   English · uploaded by louvette   English · uploaded by susinz
 Damnation - 01x07 - A Different Species   Little Women (2017) - 01x03 - Part 3   Still Open All Hours - 04x01 - Episode 1
  English · uploaded by PopcornAWH   English · uploaded by bien-mignon-mais   English · uploaded by BINairLADEN
+
+
+
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  Latest new versions
TV Series Episode Subtitle  Gomorra (2014) - 03x01 - Episodio 1 TV Series Episode Subtitle Young Sheldon - 01x08 - Cape Canaveral, Shrodinger's Cat and Cyndi Lauper's Hair TV Series Episode Subtitle Young Sheldon - 01x07 - A Brisket, Voodoo and Cannonball Run
  French · REPACK.1080p.HDTVRip.DD5.1.x264-NovaRip · uploaded by erik44   Russian · SVA · uploaded by mdh77   Russian · SVA · uploaded by mdh77
TV Series Episode Subtitle  Young Sheldon - 01x09 - Spock, Kirk and Testicular Hernia TV Series Episode Subtitle The Grand Tour - 02x04 - Unscripted TV Series Episode Subtitle The Grand Tour - 02x04 - Unscripted
  Russian · SVA · uploaded by mdh77   Spanish (Latin America) · WEBRip.RARBG-AMZN.WEBRip-WEBDL.NTb · uploaded by menoyos   Spanish (Spain) · WEBRip.RARBG-AMZN.WEBRip-WEBDL.NTb · uploaded by menoyos
TV Series Episode Subtitle  Las Chicas Del Cable - 02x01 - Chapter 9: The Choice TV Series Episode Subtitle Las Chicas Del Cable - 02x01 - Chapter 9: The Choice TV Series Episode Subtitle Las Chicas Del Cable - 02x01 - Chapter 9: The Choice
  Turkish · WEBRip.x264-BRiNK · uploaded by PopcornAWH   Swedish · WEBRip.x264-BRiNK · uploaded by PopcornAWH   Portuguese (Brazilian) · WEBRip.x264-BRiNK · uploaded by PopcornAWH
+
+
+ +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Latest uploaded episodes
invisible
 Turbo F.A.S.T. - 01x12 - Curse of the Cicadas - Beat-A Fajita   That's So Raven - 04x22 - Where There's Smoke   That's So Raven - 04x21 - The Way We Were
  English · uploaded by f1nc0   English · uploaded by maggimw   English · uploaded by maggimw
 That's So Raven - 04x20 - Teacher's Pet   That's So Raven - 04x19 - The Dress is Always Greener   The Big Fat Quiz of the Year - 01x15 - The Big Fat Quiz of the Year 2017
  English · uploaded by maggimw   English · uploaded by maggimw   English · uploaded by Aaru Bui
 That's So Raven - 04x18 - Rae of Sunshine   That's So Raven - 04x17 - The Ice Girl Cometh   That's So Raven - 04x16 - Members Only
  English · uploaded by maggimw   English · uploaded by maggimw   English · uploaded by maggimw
+
+
+
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Latest translations completed on Addic7ed.com
TV Series Episode Subtitle  Young Herriot - 01x03 - Episode 3  TV Series Episode Subtitle Supernatural - 02x01 - In my time of dying TV Series Episode Subtitle Young Herriot - 01x02 - Episode 2
  Bulgarian   Bulgarian   Bulgarian
TV Series Episode Subtitle  Absentia - 01x08 - Brave Boy  Movie Subtitle Harry Potter: A History of Magic (2017) TV Series Episode Subtitle A Place To Call Home - 05x08 - Cloud Break
  Bulgarian   French   French
TV Series Episode Subtitle  Shades Of Blue - 02x04 - Daddy's Girl  TV Series Episode Subtitle Victoria - 02x09 - Comfort and Joy TV Series Episode Subtitle Vikings - 05x06 - The Message
  French   French   French
+
+
+ +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  Latest uploaded movies
 Topaz [Alfred Hitchcock's Topaz] [L'Étau] (1969)    Jumanji: Welcome to the Jungle (2017)   The Death and Life of Marsha P. Johnson (2017)
  English · uploaded by miclar1   English · uploaded by mimti   English · uploaded by chamallow
 Slack Bay (2016)    The Hound of the Baskervilles [Le Chien des Baskerville] (1939)   Michael Jackson's Halloween (2017)
  English · uploaded by PopcornAWH   English · uploaded by miclar1   English · uploaded by f1nc0
 Blade Runner 2049 (2017)    The Hitcher [L'Auto-Stoppeur] (1986)   Mark Felt: The Man Who Brought Down the White House (2017)
  Dutch · uploaded by McGarrett   English · uploaded by miclar1   English · uploaded by thegateway
+
+
+ +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  Latest started translations
TV Series Episode Subtitle  Cassandra French's Finishing School - 01x01 - Breaking Ground TV Series Episode Subtitle The Middle - 09x03 - Meet the Parents TV Series Episode Subtitle A Place To Call Home - 05x09 - All That Lies Ahead
  French · 0.33% Completed   Bulgarian · 3.68% Completed   French · 18.97% Completed
TV Series Episode Subtitle  Shades Of Blue - 02x05 - Sweet Caroline TV Series Episode Subtitle Travelers - 02x03 - Jacob TV Series Episode Subtitle Blindspot - 03x08 - City Folks Under Wraps
  French · 2.98% Completed   Polish · 3.57% Completed   French · 24.01% Completed
TV Series Episode Subtitle  Knightfall - 01x04 - He Who Discovers His Own Self Discovers God TV Series Episode Subtitle Graves - 02x05 - Delights of My Suffering TV Series Episode Subtitle The Mick - 02x09 - The Divorce
  French · 15.88% Completed   French · 66.09% Completed   French · 11.01% Completed
+
+
+

+

+ +
+

+ +
+ +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Finishing translations
TV Series Episode Subtitle  Killjoys - 03x01 - Boondoggie  TV Series Episode Subtitle Killjoys - 03x02 - A Skinner, Darkley TV Series Episode Subtitle Killjoys - 03x05 - Attack the Rack
  Bulgarian · 99.76% Completed   Bulgarian · 99.35% Completed   Bulgarian · 99.26% Completed
TV Series Episode Subtitle  The Good Place - 02x03 - Team Cockroach  TV Series Episode Subtitle The Good Place - 02x04 - Existential Crisis TV Series Episode Subtitle Foyle's War - 06x02 - Broken Souls
  Bulgarian · 99.09% Completed   Bulgarian · 98.96% Completed   Romanian · 98.86% Completed
TV Series Episode Subtitle  The Good Place - 02x02 - Dance Dance Resolution  TV Series Episode Subtitle Killjoys - 03x03 - The Hullen Have Eyes TV Series Episode Subtitle CSI: Crime Scene Investigation - 16x01 - Immortality (1)
  Bulgarian · 98.85% Completed   Bulgarian · 98.79% Completed   Bulgarian · 98.71% Completed
+
+
+ +
+ + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Most downloaded today
TV Series Episode Subtitle  Vikings - 05x06 - The Message  TV Series Episode Subtitle Knightfall - 01x04 - He Who Discovers His Own Self Discovers God TV Series Episode Subtitle Marvel's Runaways - 01x06 - Metamorphosis
  9991 Downloads   2304 Downloads   2187 Downloads
TV Series Episode Subtitle  Marvel's Runaways - 01x08 - Tsunami  TV Series Episode Subtitle Black Mirror (2011) - 04x01 - USS Callister TV Series Episode Subtitle Marvel's Runaways - 01x07 - Refraction
  2080 Downloads   1844 Downloads   1780 Downloads
TV Series Episode Subtitle  Marvel's Runaways - 01x03 - Destiny  TV Series Episode Subtitle The Librarians (2014) - 04x05 - And the Bleeding Crown TV Series Episode Subtitle Marvel's Runaways - 01x01 - Reunion
  1678 Downloads   1622 Downloads   1592 Downloads
+
+

+ +
  +
  +
  +
  + + + + + + + + + + + diff --git a/mocks/mocks.rb b/mocks/mocks.rb new file mode 100644 index 0000000..93d3eee --- /dev/null +++ b/mocks/mocks.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "webmock" + +module Addic7ed + module Mocks + WELCOME_MESSAGE = <<~WELCOME_MESSAGE + Welcome to the mocked mode. + All network requests are forbidden (like in test mode). + An Addic7ed page is mocked for your convenience for 🛡 Game Of Thrones ⚔️ s06e09 🐲 + Try this: + episode = Addic7ed::Episode.new(show: "Game of Thrones", season: 6, number: 9) + episode.subtitles + episode.sutitles.completed.for_language(:fr).most_popular + WELCOME_MESSAGE + + def self.included(base) + base.send(:include, WebMock::API) + WebMock.enable! + mock_addic7ed(path: "/", response: "home") + mock_addic7ed(path: "/serie/Game_of_Thrones/6/9/0", response: "got690") + puts WELCOME_MESSAGE + end + + def self.mock_addic7ed(path:, response:) + stub_request( + :get, + URI.join("http://www.addic7ed.com/", path) + ).to_return(response_content(response)) + end + + def self.response_content(response) + File.new(File.join(__dir__, "#{response}.http")) + end + end +end + +include Addic7ed::Mocks From 3337a7df0404048e7ed6de915762b64061d4a50b Mon Sep 17 00:00:00 2001 From: Michael Baudino Date: Fri, 21 Sep 2018 14:07:26 +0200 Subject: [PATCH 89/89] Update TODO.md --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index 4c1b5fc..b489e0e 100644 --- a/TODO.md +++ b/TODO.md @@ -41,3 +41,4 @@ Here is a list of future features I'd like to implement. * [x] refactor how HI works (allow both "no HI", "force HI" and "don't care") * [ ] add specs for HTML parsing * [ ] support registered users (to avoid download throttle) +* [ ] use https://github.com/p0deje/yard-doctest to enforce documentation maintainance